import random
class SlideMover:
def __init__(self, puzzle):
self.puzzle = puzzle
self.empty_pos = 0
for i, itm in enumerate(self.puzzle):
if itm == 0:
self.empty_pos = i
break
self.step = []
def shuffle(self, cnt):
inc = 0
while inc < cnt:
r_dir = random.randint(0, 3)
if self.move('URDL'[r_dir]):
inc += 1
def move(self, dir):
delta = 0
if dir == 'U':
if self.empty_pos in [0,1,2,3]:
return False
delta = -4
elif dir == 'R':
if self.empty_pos in [3,7,11,15]:
return False
delta = 1
elif dir == 'D':
if self.empty_pos in [12,13,14,15]:
return False
delta = 4
elif dir == 'L':
if self.empty_pos in [0,4,8,12]:
return False
delta = -1
# swap
tmp = self.puzzle[self.empty_pos]
self.puzzle[self.empty_pos] = self.puzzle[self.empty_pos + delta]
self.puzzle[self.empty_pos + delta] = tmp
# update empty position
self.empty_pos += delta
# record step
self.step.append(dir)
return True
# exception 래퍼
def emove(self, dir):
if self.move(dir):
pass
else:
raise Exception('cant move empty_pos:%d, dir:%s'%(self.empty_pos, dir) )
# 빈블록 수평이동
def move_col(self, col):
while True:
e_row, e_col = divmod(self.empty_pos, 4)
c = col - e_col
if c > 0:
self.emove('R')
elif c < 0:
self.emove('L')
else:
break
# 빈블록 수직이동
def move_row(self, row):
while True:
e_row, e_col = divmod(self.empty_pos, 4)
r = row - e_row
if r > 0:
self.emove('D')
elif r < 0:
self.emove('U')
else:
break
# 수평이동 -> 수직이동
def move_hv(self, dst_pos):
if self.empty_pos == dst_pos:
return
d_row, d_col = divmod(dst_pos, 4)
self.move_col(d_col)
self.move_row(d_row)
# 수직이동 -> 수평이동
def move_vh(self, dst_pos):
if self.empty_pos == dst_pos:
return
d_row, d_col = divmod(dst_pos, 4)
self.move_row(d_row)
self.move_col(d_col)
# 수직(벽을 고려 아래로)-> 수평이동 -> 수직이동
def move_hv_with_wall(self, dst_pos, wall_pos):
if self.empty_pos == dst_pos:
return
e_row, e_col = divmod(self.empty_pos, 4)
if wall_pos < 0:
self.move_hv(dst_pos)
else:
w_row, w_col = divmod(wall_pos,4)
min_row = w_row + 1
if e_row < min_row:
self.move_row(min_row)
self.move_hv(dst_pos)
def print_puzzle(puzzle):
for i, itm in enumerate(puzzle):
print('{:>2}, '.format(itm), end='')
if i in [3,7,11,15]:
print(' ') # enter
print('-'*16)
# 무식한 풀이법 (휴리스틱)
class MusicSolver:
def __init__(self, puzzle, mover):
self.puzzle = puzzle
self.mover = mover # type: SlideMover
def solve(self):
self.normal_set_block(1, 0, True)
self.normal_set_block(2, 1, True)
self.normal_set_block(4, 2, True)
self.row_set_block(0)
self.normal_set_block(5, 4, True)
self.normal_set_block(6, 5, True)
self.normal_set_block(8, 6, True)
self.row_set_block(1)
self.normal_set_block(13, 8, True)
self.col_set_block(0)
self.normal_set_block(14, 9, True)
self.col_set_block(1)
self.rotate_block()
# 원하는 숫자 위치 찾음
def find_num_pos(self, num):
for i, itm in enumerate(self.puzzle):
if itm == num:
return i
# 원하는 number를 원하는 위치 dst_pos 로 이동
def normal_set_block(self, num, dst_pos, left_wall):
# 일단 목표지점에 빈블록을 갖다놓음
self.mover.move_hv(dst_pos)
# 원하는 목표숫자의 위치를 찾음
num_pos = self.find_num_pos(num)
# 빈블록을 목표숫자 주변으로 이동 (위, 왼쪽, 오른쪽)
self.attach_empty_block(num_pos, left_wall)
# 위쪽에 붙은 경우 일단 한번 위로 이동한다.
if num_pos - self.mover.empty_pos == 4:
self.mover.emove('D')
num_pos -= 4
# column을 맞춘다.
d_row, d_col = divmod(dst_pos, 4)
while True:
n_row, n_col = divmod(num_pos, 4)
if d_col < n_col:
self.move_target('L', num_pos)
num_pos += -1
elif d_col > n_col:
self.move_target('R', num_pos)
num_pos += 1
else:
break
# row 를 맞춘다.
while True:
n_row, n_col = divmod(num_pos, 4)
if d_row < n_row:
self.move_target('U', num_pos)
num_pos += -4
elif d_row > n_row:
self.move_target('D', num_pos)
num_pos += 4
else:
break
# 빈블록을 목표지점 주변에 붙인다.
def attach_empty_block(self, num_pos, left_wall):
o_row, o_col = divmod(self.mover.empty_pos, 4)
t_row, t_col = divmod(num_pos,4)
d_row = 0
d_col = 0
l_limit = 0
if left_wall:
l_limit = 1
if o_col - t_col > 0:
if t_row-l_limit > o_row:
d_row = t_row-1 # UP
d_col = t_col
else:
d_row = t_row #RIGHT
d_col = t_col + 1
else:
if t_row == o_row:
d_row = t_row # LEFT
d_col = t_col - 1
else:
d_row = t_row-1 # UP
d_col = t_col
dst_pos = d_row*4 + d_col
self.mover.move_vh(dst_pos)
# 타겟 이동 (attach 이후)
def move_target(self, dir, num_pos):
# key : 타겟의 방향
# value-key : empty 가 붙어있는 위치
scripts = {
'U': {'U': 'D', 'R': 'ULD', 'D': 'RUULD', 'L': 'DRRUULD'},
'L': {'U': 'RDDLLUR', 'R': 'DLLUR', 'D': 'LUR', 'L': 'R'},
'R': {'U': 'LDDRRUL', 'R': 'L', 'D': 'RUL', 'L': 'DRRUL'},
}
diff = num_pos - self.mover.empty_pos
k = ''
if diff == 4: # UP
k = 'U'
elif diff == 1: # LEFT
k = 'L'
elif diff == -1: #RIGHT
k = 'R'
elif diff == -4: #DOWN
k = 'D'
else:
raise Exception('cant move target')
script = scripts[dir][k]
for d in script:
self.mover.emove(d)
# row 끝쪽 특수 패턴 체크
def check_row_pattern(self, row):
dtec = False
if row == 0:
if self.puzzle[2] == 4 and self.puzzle[3] == 3:
# detect pattern
dtec = True
if row == 1:
if self.puzzle[6] == 8 and self.puzzle[7] == 7:
# detect pattern
dtec = True
if dtec:
for d in 'RULDRDLUURDLD':
self.mover.emove(d)
return dtec
# row 0 인 경우 1,2,4 일단 맞추고 난뒤
# row 1 인 경우 5,6,8 맞추고 난뒤
# row 끝 부분 처리
def row_set_block(self, row):
dst_pos = (row+1) * 4 + 2
# 일단 빈블록을 다음 ROW 3번쨰 칸에 위치 시킴
self.mover.move_hv_with_wall(dst_pos, dst_pos-4)
# 특수 패턴 체크
if self.check_row_pattern(row):
pass # 이동을 이미 시킴
else:
self.normal_set_block(row*4 + 3, row*4 + 6, False)
# 빈블록을 끝으로 이동
self.mover.move_hv_with_wall(row*4 + 3, row*4 + 6)
# Row 맞춤
for d in 'LD':
self.mover.emove(d)
# row 끝쪽 특수 패턴 체크
def check_col_pattern(self, col):
dtec = False
if col == 0:
if self.puzzle[8] == 13 and self.puzzle[12] == 9:
# detect pattern
dtec = True
if col == 1:
if self.puzzle[9] == 14 and self.puzzle[13] == 10:
# detect pattern
dtec = True
if dtec:
for d in 'DLURDRULLDRUR':
self.mover.emove(d)
return dtec
def col_set_block(self, col):
dst_pos = 9 + col
self.mover.move_hv_with_wall(dst_pos, dst_pos-4)
# 특수 패턴 체크
if self.check_col_pattern(col):
pass # 이동을 이미 시킴
else:
self.normal_set_block(dst_pos, dst_pos, True)
# 빈블록을 끝으로 이동
self.mover.move_hv_with_wall(12 + col, dst_pos)
# Row 맞춤
for d in 'UR':
self.mover.emove(d)
# 마지막 부분 회전
def rotate_block(self):
for d in 'DRULDRULDRULDRUL':
if self.puzzle[10] == 11 and self.puzzle[11] == 12 and self.puzzle[14] == 15:
break
self.mover.emove(d)
if __name__ == '__main__':
puzzle = list(range(1, 16)) + [0]
mover = SlideMover(puzzle)
mover.shuffle(80)
mover.step = []
print_puzzle(puzzle)
#
solver = MusicSolver(puzzle, mover)
solver.solve()
print(len(mover.step), ''.join(mover.step))
print_puzzle(puzzle)
aW1wb3J0IHJhbmRvbQoKCmNsYXNzIFNsaWRlTW92ZXI6CiAgICBkZWYgX19pbml0X18oc2VsZiwgcHV6emxlKToKICAgICAgICBzZWxmLnB1enpsZSA9IHB1enpsZQogICAgICAgIHNlbGYuZW1wdHlfcG9zID0gMAogICAgICAgIGZvciBpLCBpdG0gaW4gZW51bWVyYXRlKHNlbGYucHV6emxlKToKICAgICAgICAgICAgaWYgaXRtID09IDA6CiAgICAgICAgICAgICAgICBzZWxmLmVtcHR5X3BvcyA9IGkKICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgc2VsZi5zdGVwID0gW10KCiAgICBkZWYgc2h1ZmZsZShzZWxmLCBjbnQpOgogICAgICAgIGluYyA9IDAKICAgICAgICB3aGlsZSBpbmMgPCBjbnQ6CiAgICAgICAgICAgIHJfZGlyID0gcmFuZG9tLnJhbmRpbnQoMCwgMykKICAgICAgICAgICAgaWYgc2VsZi5tb3ZlKCdVUkRMJ1tyX2Rpcl0pOgogICAgICAgICAgICAgICAgaW5jICs9IDEKCiAgICBkZWYgbW92ZShzZWxmLCBkaXIpOgogICAgICAgIGRlbHRhID0gMAogICAgICAgIGlmIGRpciA9PSAnVSc6CiAgICAgICAgICAgIGlmIHNlbGYuZW1wdHlfcG9zIGluIFswLDEsMiwzXToKICAgICAgICAgICAgICAgIHJldHVybiBGYWxzZQogICAgICAgICAgICBkZWx0YSA9IC00CiAgICAgICAgZWxpZiBkaXIgPT0gJ1InOgogICAgICAgICAgICBpZiBzZWxmLmVtcHR5X3BvcyBpbiBbMyw3LDExLDE1XToKICAgICAgICAgICAgICAgIHJldHVybiBGYWxzZQogICAgICAgICAgICBkZWx0YSA9IDEKICAgICAgICBlbGlmIGRpciA9PSAnRCc6CiAgICAgICAgICAgIGlmIHNlbGYuZW1wdHlfcG9zIGluIFsxMiwxMywxNCwxNV06CiAgICAgICAgICAgICAgICByZXR1cm4gRmFsc2UKICAgICAgICAgICAgZGVsdGEgPSA0CiAgICAgICAgZWxpZiBkaXIgPT0gJ0wnOgogICAgICAgICAgICBpZiBzZWxmLmVtcHR5X3BvcyBpbiBbMCw0LDgsMTJdOgogICAgICAgICAgICAgICAgcmV0dXJuIEZhbHNlCiAgICAgICAgICAgIGRlbHRhID0gLTEKCiAgICAgICAgIyBzd2FwCiAgICAgICAgdG1wID0gc2VsZi5wdXp6bGVbc2VsZi5lbXB0eV9wb3NdCiAgICAgICAgc2VsZi5wdXp6bGVbc2VsZi5lbXB0eV9wb3NdID0gc2VsZi5wdXp6bGVbc2VsZi5lbXB0eV9wb3MgKyBkZWx0YV0KICAgICAgICBzZWxmLnB1enpsZVtzZWxmLmVtcHR5X3BvcyArIGRlbHRhXSA9IHRtcAoKICAgICAgICAjIHVwZGF0ZSBlbXB0eSBwb3NpdGlvbgogICAgICAgIHNlbGYuZW1wdHlfcG9zICs9IGRlbHRhCgogICAgICAgICMgcmVjb3JkIHN0ZXAKICAgICAgICBzZWxmLnN0ZXAuYXBwZW5kKGRpcikKCiAgICAgICAgcmV0dXJuIFRydWUKCiAgICAjIGV4Y2VwdGlvbiDrnpjtjbwKICAgIGRlZiBlbW92ZShzZWxmLCBkaXIpOgogICAgICAgIGlmIHNlbGYubW92ZShkaXIpOgogICAgICAgICAgICBwYXNzCiAgICAgICAgZWxzZToKICAgICAgICAgICAgcmFpc2UgRXhjZXB0aW9uKCdjYW50IG1vdmUgZW1wdHlfcG9zOiVkLCBkaXI6JXMnJShzZWxmLmVtcHR5X3BvcywgZGlyKSAgKQoKICAgICMg67mI67iU66GdIOyImO2PieydtOuPmQogICAgZGVmIG1vdmVfY29sKHNlbGYsIGNvbCk6CiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgZV9yb3csIGVfY29sID0gZGl2bW9kKHNlbGYuZW1wdHlfcG9zLCA0KQogICAgICAgICAgICBjID0gY29sIC0gZV9jb2wKICAgICAgICAgICAgaWYgYyA+IDA6CiAgICAgICAgICAgICAgICBzZWxmLmVtb3ZlKCdSJykKICAgICAgICAgICAgZWxpZiBjIDwgMDoKICAgICAgICAgICAgICAgIHNlbGYuZW1vdmUoJ0wnKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgYnJlYWsKICAgICMg67mI67iU66GdIOyImOyngeydtOuPmQogICAgZGVmIG1vdmVfcm93KHNlbGYsIHJvdyk6CiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgZV9yb3csIGVfY29sID0gZGl2bW9kKHNlbGYuZW1wdHlfcG9zLCA0KQogICAgICAgICAgICByID0gcm93IC0gZV9yb3cKICAgICAgICAgICAgaWYgciA+IDA6CiAgICAgICAgICAgICAgICBzZWxmLmVtb3ZlKCdEJykKICAgICAgICAgICAgZWxpZiByIDwgMDoKICAgICAgICAgICAgICAgIHNlbGYuZW1vdmUoJ1UnKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgYnJlYWsKICAgICMg7IiY7Y+J7J2064+ZIC0+IOyImOyngeydtOuPmQogICAgZGVmIG1vdmVfaHYoc2VsZiwgZHN0X3Bvcyk6CiAgICAgICAgaWYgc2VsZi5lbXB0eV9wb3MgPT0gZHN0X3BvczoKICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgZF9yb3csIGRfY29sID0gZGl2bW9kKGRzdF9wb3MsIDQpCiAgICAgICAgc2VsZi5tb3ZlX2NvbChkX2NvbCkKICAgICAgICBzZWxmLm1vdmVfcm93KGRfcm93KQogICAgIyDsiJjsp4HsnbTrj5kgLT4g7IiY7Y+J7J2064+ZCiAgICBkZWYgbW92ZV92aChzZWxmLCBkc3RfcG9zKToKICAgICAgICBpZiBzZWxmLmVtcHR5X3BvcyA9PSBkc3RfcG9zOgogICAgICAgICAgICByZXR1cm4KICAgICAgICBkX3JvdywgZF9jb2wgPSBkaXZtb2QoZHN0X3BvcywgNCkKICAgICAgICBzZWxmLm1vdmVfcm93KGRfcm93KQogICAgICAgIHNlbGYubW92ZV9jb2woZF9jb2wpCgogICAgIyAg7IiY7KeBKOuyveydhCDqs6DroKQg7JWE656Y66GcKS0+IOyImO2PieydtOuPmSAtPiDsiJjsp4HsnbTrj5kKICAgIGRlZiBtb3ZlX2h2X3dpdGhfd2FsbChzZWxmLCBkc3RfcG9zLCB3YWxsX3Bvcyk6CiAgICAgICAgaWYgc2VsZi5lbXB0eV9wb3MgPT0gZHN0X3BvczoKICAgICAgICAgICAgcmV0dXJuCiAgICAgICAgZV9yb3csIGVfY29sID0gZGl2bW9kKHNlbGYuZW1wdHlfcG9zLCA0KQogICAgICAgIGlmIHdhbGxfcG9zIDwgMDoKICAgICAgICAgICAgc2VsZi5tb3ZlX2h2KGRzdF9wb3MpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgd19yb3csIHdfY29sID0gZGl2bW9kKHdhbGxfcG9zLDQpCiAgICAgICAgICAgIG1pbl9yb3cgPSB3X3JvdyArIDEKICAgICAgICAgICAgaWYgZV9yb3cgPCBtaW5fcm93OgogICAgICAgICAgICAgICAgc2VsZi5tb3ZlX3JvdyhtaW5fcm93KQogICAgICAgICAgICBzZWxmLm1vdmVfaHYoZHN0X3BvcykKCgpkZWYgcHJpbnRfcHV6emxlKHB1enpsZSk6CiAgICBmb3IgaSwgaXRtIGluIGVudW1lcmF0ZShwdXp6bGUpOgogICAgICAgIHByaW50KCd7Oj4yfSwgJy5mb3JtYXQoaXRtKSwgZW5kPScnKQogICAgICAgIGlmIGkgaW4gWzMsNywxMSwxNV06CiAgICAgICAgICAgIHByaW50KCcgJykgIyBlbnRlcgogICAgcHJpbnQoJy0nKjE2KQoKCgojIOustOyLne2VnCDtkoDsnbTrspUgKO2ctOumrOyKpO2LsSkKY2xhc3MgTXVzaWNTb2x2ZXI6CiAgICBkZWYgX19pbml0X18oc2VsZiwgcHV6emxlLCBtb3Zlcik6CiAgICAgICAgc2VsZi5wdXp6bGUgPSBwdXp6bGUKICAgICAgICBzZWxmLm1vdmVyID0gbW92ZXIgIyB0eXBlOiBTbGlkZU1vdmVyCgogICAgZGVmIHNvbHZlKHNlbGYpOgogICAgICAgIHNlbGYubm9ybWFsX3NldF9ibG9jaygxLCAwLCBUcnVlKQogICAgICAgIHNlbGYubm9ybWFsX3NldF9ibG9jaygyLCAxLCBUcnVlKQogICAgICAgIHNlbGYubm9ybWFsX3NldF9ibG9jayg0LCAyLCBUcnVlKQogICAgICAgIHNlbGYucm93X3NldF9ibG9jaygwKQogICAgICAgIHNlbGYubm9ybWFsX3NldF9ibG9jayg1LCA0LCBUcnVlKQogICAgICAgIHNlbGYubm9ybWFsX3NldF9ibG9jayg2LCA1LCBUcnVlKQogICAgICAgIHNlbGYubm9ybWFsX3NldF9ibG9jayg4LCA2LCBUcnVlKQogICAgICAgIHNlbGYucm93X3NldF9ibG9jaygxKQogICAgICAgIHNlbGYubm9ybWFsX3NldF9ibG9jaygxMywgOCwgVHJ1ZSkKICAgICAgICBzZWxmLmNvbF9zZXRfYmxvY2soMCkKICAgICAgICBzZWxmLm5vcm1hbF9zZXRfYmxvY2soMTQsIDksIFRydWUpCiAgICAgICAgc2VsZi5jb2xfc2V0X2Jsb2NrKDEpCiAgICAgICAgc2VsZi5yb3RhdGVfYmxvY2soKQoKCiAgICAjIOybkO2VmOuKlCDsiKvsnpAg7JyE7LmYIOywvuydjAogICAgZGVmIGZpbmRfbnVtX3BvcyhzZWxmLCBudW0pOgogICAgICAgIGZvciBpLCBpdG0gaW4gZW51bWVyYXRlKHNlbGYucHV6emxlKToKICAgICAgICAgICAgaWYgaXRtID09IG51bToKICAgICAgICAgICAgICAgIHJldHVybiBpCgogICAgIyDsm5DtlZjripQgbnVtYmVy66W8IOybkO2VmOuKlCDsnITsuZggZHN0X3BvcyDroZwg7J2064+ZCiAgICBkZWYgbm9ybWFsX3NldF9ibG9jayhzZWxmLCBudW0sIGRzdF9wb3MsIGxlZnRfd2FsbCk6CiAgICAgICAgIyDsnbzri6gg66qp7ZGc7KeA7KCQ7JeQIOu5iOu4lOuhneydhCDqsJbri6TrhpPsnYwKICAgICAgICBzZWxmLm1vdmVyLm1vdmVfaHYoZHN0X3BvcykKCiAgICAgICAgIyDsm5DtlZjripQg66qp7ZGc7Iir7J6Q7J2YIOychOy5mOulvCDssL7snYwKICAgICAgICBudW1fcG9zID0gc2VsZi5maW5kX251bV9wb3MobnVtKQoKICAgICAgICAjIOu5iOu4lOuhneydhCDrqqntkZzsiKvsnpAg7KO867OA7Jy866GcIOydtOuPmSAo7JyELCDsmbzsqr0sIOyYpOuluOyqvSkKICAgICAgICBzZWxmLmF0dGFjaF9lbXB0eV9ibG9jayhudW1fcG9zLCBsZWZ0X3dhbGwpCgogICAgICAgICMg7JyE7Kq97JeQIOu2meydgCDqsr3smrAg7J2864uoICDtlZzrsogg7JyE66GcIOydtOuPme2VnOuLpC4KICAgICAgICBpZiBudW1fcG9zIC0gc2VsZi5tb3Zlci5lbXB0eV9wb3MgPT0gNDoKICAgICAgICAgICAgc2VsZi5tb3Zlci5lbW92ZSgnRCcpCiAgICAgICAgICAgIG51bV9wb3MgLT0gNAoKICAgICAgICAjIGNvbHVtbuydhCDrp57stpjri6QuCiAgICAgICAgZF9yb3csIGRfY29sID0gZGl2bW9kKGRzdF9wb3MsIDQpCiAgICAgICAgd2hpbGUgVHJ1ZToKICAgICAgICAgICAgbl9yb3csIG5fY29sID0gZGl2bW9kKG51bV9wb3MsIDQpCiAgICAgICAgICAgIGlmIGRfY29sIDwgbl9jb2w6CiAgICAgICAgICAgICAgICBzZWxmLm1vdmVfdGFyZ2V0KCdMJywgbnVtX3BvcykKICAgICAgICAgICAgICAgIG51bV9wb3MgKz0gLTEKICAgICAgICAgICAgZWxpZiBkX2NvbCA+IG5fY29sOgogICAgICAgICAgICAgICAgc2VsZi5tb3ZlX3RhcmdldCgnUicsIG51bV9wb3MpCiAgICAgICAgICAgICAgICBudW1fcG9zICs9IDEKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgIyByb3cg66W8IOunnuy2mOuLpC4KICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICBuX3Jvdywgbl9jb2wgPSBkaXZtb2QobnVtX3BvcywgNCkKICAgICAgICAgICAgaWYgZF9yb3cgPCBuX3JvdzoKICAgICAgICAgICAgICAgIHNlbGYubW92ZV90YXJnZXQoJ1UnLCBudW1fcG9zKQogICAgICAgICAgICAgICAgbnVtX3BvcyArPSAtNAogICAgICAgICAgICBlbGlmIGRfcm93ID4gbl9yb3c6CiAgICAgICAgICAgICAgICBzZWxmLm1vdmVfdGFyZ2V0KCdEJywgbnVtX3BvcykKICAgICAgICAgICAgICAgIG51bV9wb3MgKz0gNAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgYnJlYWsKCgogICAgIyDruYjruJTroZ3snYQg66qp7ZGc7KeA7KCQIOyjvOuzgOyXkCDrtpnsnbjri6QuCiAgICBkZWYgYXR0YWNoX2VtcHR5X2Jsb2NrKHNlbGYsIG51bV9wb3MsIGxlZnRfd2FsbCk6CiAgICAgICAgb19yb3csIG9fY29sID0gZGl2bW9kKHNlbGYubW92ZXIuZW1wdHlfcG9zLCA0KQogICAgICAgIHRfcm93LCB0X2NvbCA9IGRpdm1vZChudW1fcG9zLDQpCiAgICAgICAgZF9yb3cgPSAwCiAgICAgICAgZF9jb2wgPSAwCiAgICAgICAgbF9saW1pdCA9IDAKICAgICAgICBpZiBsZWZ0X3dhbGw6CiAgICAgICAgICAgIGxfbGltaXQgPSAxCiAgICAgICAgaWYgb19jb2wgLSB0X2NvbCA+IDA6CiAgICAgICAgICAgIGlmIHRfcm93LWxfbGltaXQgPiBvX3JvdzoKICAgICAgICAgICAgICAgIGRfcm93ID0gdF9yb3ctMSAjIFVQCiAgICAgICAgICAgICAgICBkX2NvbCA9IHRfY29sCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBkX3JvdyA9IHRfcm93ICNSSUdIVAogICAgICAgICAgICAgICAgZF9jb2wgPSB0X2NvbCArIDEKICAgICAgICBlbHNlOgogICAgICAgICAgICBpZiB0X3JvdyA9PSBvX3JvdzoKICAgICAgICAgICAgICAgIGRfcm93ID0gdF9yb3cgICMgTEVGVAogICAgICAgICAgICAgICAgZF9jb2wgPSB0X2NvbCAtIDEKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIGRfcm93ID0gdF9yb3ctMSAjIFVQCiAgICAgICAgICAgICAgICBkX2NvbCA9IHRfY29sCgogICAgICAgIGRzdF9wb3MgPSBkX3Jvdyo0ICsgZF9jb2wKICAgICAgICBzZWxmLm1vdmVyLm1vdmVfdmgoZHN0X3BvcykKCiAgICAjIO2DgOqynyDsnbTrj5kgKGF0dGFjaCDsnbTtm4QpCiAgICBkZWYgbW92ZV90YXJnZXQoc2VsZiwgZGlyLCBudW1fcG9zKToKICAgICAgICAjIGtleSA6IO2DgOqyn+ydmCDrsKntlqUKICAgICAgICAjIHZhbHVlLWtleSA6IGVtcHR5IOqwgCDrtpnslrTsnojripQg7JyE7LmYCiAgICAgICAgc2NyaXB0cyA9IHsKICAgICAgICAgICAgJ1UnOiB7J1UnOiAnRCcsICdSJzogJ1VMRCcsICdEJzogJ1JVVUxEJywgJ0wnOiAnRFJSVVVMRCd9LAogICAgICAgICAgICAnTCc6IHsnVSc6ICdSRERMTFVSJywgJ1InOiAnRExMVVInLCAnRCc6ICdMVVInLCAnTCc6ICdSJ30sCiAgICAgICAgICAgICdSJzogeydVJzogJ0xERFJSVUwnLCAnUic6ICdMJywgJ0QnOiAnUlVMJywgJ0wnOiAnRFJSVUwnfSwKICAgICAgICB9CiAgICAgICAgZGlmZiA9IG51bV9wb3MgLSBzZWxmLm1vdmVyLmVtcHR5X3BvcwogICAgICAgIGsgPSAnJwogICAgICAgIGlmIGRpZmYgPT0gNDogIyBVUAogICAgICAgICAgICBrID0gJ1UnCiAgICAgICAgZWxpZiBkaWZmID09IDE6ICMgTEVGVAogICAgICAgICAgICBrID0gJ0wnCiAgICAgICAgZWxpZiBkaWZmID09IC0xOiAjUklHSFQKICAgICAgICAgICAgayA9ICdSJwogICAgICAgIGVsaWYgZGlmZiA9PSAtNDogI0RPV04KICAgICAgICAgICAgayA9ICdEJwogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHJhaXNlIEV4Y2VwdGlvbignY2FudCBtb3ZlIHRhcmdldCcpCiAgICAgICAgc2NyaXB0ID0gc2NyaXB0c1tkaXJdW2tdCiAgICAgICAgZm9yIGQgaW4gc2NyaXB0OgogICAgICAgICAgICBzZWxmLm1vdmVyLmVtb3ZlKGQpCgogICAgIyByb3cg64Gd7Kq9IO2KueyImCDtjKjthLQg7LK07YGsCiAgICBkZWYgY2hlY2tfcm93X3BhdHRlcm4oc2VsZiwgcm93KToKICAgICAgICBkdGVjID0gRmFsc2UKICAgICAgICBpZiByb3cgPT0gMDoKICAgICAgICAgICAgaWYgc2VsZi5wdXp6bGVbMl0gPT0gNCBhbmQgc2VsZi5wdXp6bGVbM10gPT0gMzoKICAgICAgICAgICAgICAgICMgZGV0ZWN0IHBhdHRlcm4KICAgICAgICAgICAgICAgIGR0ZWMgPSBUcnVlCiAgICAgICAgaWYgcm93ID09IDE6CiAgICAgICAgICAgIGlmIHNlbGYucHV6emxlWzZdID09IDggYW5kIHNlbGYucHV6emxlWzddID09IDc6CiAgICAgICAgICAgICAgICAjIGRldGVjdCBwYXR0ZXJuCiAgICAgICAgICAgICAgICBkdGVjID0gVHJ1ZQogICAgICAgIGlmIGR0ZWM6CiAgICAgICAgICAgIGZvciBkIGluICdSVUxEUkRMVVVSRExEJzoKICAgICAgICAgICAgICAgIHNlbGYubW92ZXIuZW1vdmUoZCkKICAgICAgICByZXR1cm4gZHRlYwoKICAgICMgcm93IDAg7J24IOqyveyasCAxLDIsNCDsnbzri6gg66ee7LaU6rOgIOuCnOuSpAogICAgIyByb3cgMSDsnbgg6rK97JqwIDUsNiw4IOunnuy2lOqzoCDrgpzrkqQKICAgICMgcm93IOuBnSDrtoDrtoQg7LKY66asCiAgICBkZWYgcm93X3NldF9ibG9jayhzZWxmLCByb3cpOgogICAgICAgIGRzdF9wb3MgPSAocm93KzEpICogNCArIDIKICAgICAgICAjIOydvOuLqCDruYjruJTroZ3snYQg64uk7J2MIFJPVyAz67KI7KiwIOy5uOyXkCDsnITsuZgg7Iuc7YK0CiAgICAgICAgc2VsZi5tb3Zlci5tb3ZlX2h2X3dpdGhfd2FsbChkc3RfcG9zLCBkc3RfcG9zLTQpCgogICAgICAgICMg7Yq57IiYIO2MqO2EtCDssrTtgawKICAgICAgICBpZiBzZWxmLmNoZWNrX3Jvd19wYXR0ZXJuKHJvdyk6CiAgICAgICAgICAgIHBhc3MgIyDsnbTrj5nsnYQg7J2066+4IOyLnO2CtAogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHNlbGYubm9ybWFsX3NldF9ibG9jayhyb3cqNCArIDMsIHJvdyo0ICsgNiwgRmFsc2UpCgogICAgICAgICMg67mI67iU66Gd7J2EIOuBneycvOuhnCDsnbTrj5kKICAgICAgICBzZWxmLm1vdmVyLm1vdmVfaHZfd2l0aF93YWxsKHJvdyo0ICsgMywgcm93KjQgKyA2KQoKICAgICAgICAjIFJvdyDrp57stqQKICAgICAgICBmb3IgZCBpbiAnTEQnOgogICAgICAgICAgICBzZWxmLm1vdmVyLmVtb3ZlKGQpCgogICAgIyByb3cg64Gd7Kq9IO2KueyImCDtjKjthLQg7LK07YGsCiAgICBkZWYgY2hlY2tfY29sX3BhdHRlcm4oc2VsZiwgY29sKToKICAgICAgICBkdGVjID0gRmFsc2UKICAgICAgICBpZiBjb2wgPT0gMDoKICAgICAgICAgICAgaWYgc2VsZi5wdXp6bGVbOF0gPT0gMTMgYW5kIHNlbGYucHV6emxlWzEyXSA9PSA5OgogICAgICAgICAgICAgICAgIyBkZXRlY3QgcGF0dGVybgogICAgICAgICAgICAgICAgZHRlYyA9IFRydWUKICAgICAgICBpZiBjb2wgPT0gMToKICAgICAgICAgICAgaWYgc2VsZi5wdXp6bGVbOV0gPT0gMTQgYW5kIHNlbGYucHV6emxlWzEzXSA9PSAxMDoKICAgICAgICAgICAgICAgICMgZGV0ZWN0IHBhdHRlcm4KICAgICAgICAgICAgICAgIGR0ZWMgPSBUcnVlCiAgICAgICAgaWYgZHRlYzoKICAgICAgICAgICAgZm9yIGQgaW4gJ0RMVVJEUlVMTERSVVInOgogICAgICAgICAgICAgICAgc2VsZi5tb3Zlci5lbW92ZShkKQogICAgICAgIHJldHVybiBkdGVjCgogICAgZGVmIGNvbF9zZXRfYmxvY2soc2VsZiwgY29sKToKICAgICAgICBkc3RfcG9zID0gOSArIGNvbAogICAgICAgIHNlbGYubW92ZXIubW92ZV9odl93aXRoX3dhbGwoZHN0X3BvcywgZHN0X3Bvcy00KQoKICAgICAgICAjIO2KueyImCDtjKjthLQg7LK07YGsCiAgICAgICAgaWYgc2VsZi5jaGVja19jb2xfcGF0dGVybihjb2wpOgogICAgICAgICAgICBwYXNzICAjIOydtOuPmeydhCDsnbTrr7gg7Iuc7YK0CiAgICAgICAgZWxzZToKICAgICAgICAgICAgc2VsZi5ub3JtYWxfc2V0X2Jsb2NrKGRzdF9wb3MsIGRzdF9wb3MsIFRydWUpCgogICAgICAgICMg67mI67iU66Gd7J2EIOuBneycvOuhnCDsnbTrj5kKICAgICAgICBzZWxmLm1vdmVyLm1vdmVfaHZfd2l0aF93YWxsKDEyICsgY29sLCBkc3RfcG9zKQoKICAgICAgICAjIFJvdyDrp57stqQKICAgICAgICBmb3IgZCBpbiAnVVInOgogICAgICAgICAgICBzZWxmLm1vdmVyLmVtb3ZlKGQpCgoKICAgICMg66eI7KeA66eJIOu2gOu2hCDtmozsoIQKICAgIGRlZiByb3RhdGVfYmxvY2soc2VsZik6CiAgICAgICAgZm9yIGQgaW4gJ0RSVUxEUlVMRFJVTERSVUwnOgogICAgICAgICAgICBpZiBzZWxmLnB1enpsZVsxMF0gPT0gMTEgYW5kIHNlbGYucHV6emxlWzExXSA9PSAxMiBhbmQgc2VsZi5wdXp6bGVbMTRdID09IDE1OgogICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgc2VsZi5tb3Zlci5lbW92ZShkKQoKCgppZiBfX25hbWVfXyA9PSAnX19tYWluX18nOgogICAgcHV6emxlID0gbGlzdChyYW5nZSgxLCAxNikpICsgWzBdCiAgICBtb3ZlciA9IFNsaWRlTW92ZXIocHV6emxlKQogICAgbW92ZXIuc2h1ZmZsZSg4MCkKICAgIG1vdmVyLnN0ZXAgPSBbXQoKICAgIHByaW50X3B1enpsZShwdXp6bGUpCgogICAgIwogICAgc29sdmVyID0gTXVzaWNTb2x2ZXIocHV6emxlLCBtb3ZlcikKICAgIHNvbHZlci5zb2x2ZSgpCgogICAgcHJpbnQobGVuKG1vdmVyLnN0ZXApLCAnJy5qb2luKG1vdmVyLnN0ZXApKQogICAgcHJpbnRfcHV6emxlKHB1enpsZSkKCg==