#!/usr/bin/python
#coding: utf-8
import Tkinter as Tk
import Dialog as D
import ScrolledText as ST
import random, time
import sys
BLACK, WHITE, NONE = range(3)
def get_pos(i):
return i % 8, i / 8
class Reversi:
def __init__(self):
self.xlist = (-1, 0, 1, -1, 1, -1, 0, 1)
self.ylist = (-1, -1, -1, 0, 0, 1, 1, 1)
self.init_game()
def init_game(self):
global board
board = [NONE for i in range(64)]
board[8*3+3] = board[8*4+4] = WHITE
board[8*4+3] = board[8*3+4] = BLACK
global COUNT
COUNT = 1
def put_stone(self, x, y, turn, count=True):
global COUNT
if board[8*y+x] != NONE:
return False
elif self.check_put(x, y, turn):
if count: COUNT += 1
self.put_stones(x, y, turn)
return True
else:
return False
def check_put(self, x, y, turn):
""" (x,y)の位置に石を置けるか判定する """
revnum = 0
for i in range(8):
ax = self.xlist[i] + x
ay = self.ylist[i] + y
prevnum = 0
while True:
if not(0 <= ax <= 7 and 0 <= ay <= 7):
break
if board[8*ay+ax] == (not turn):
prevnum += 1
elif board[8*ay+ax] == turn and prevnum:
revnum += prevnum
break
else:
break
ax += self.xlist[i]
ay += self.ylist[i]
return revnum
def put_stones(self, x, y, turn):
""" (x, y)に石を置く """
board[8*y+x] = turn
for i in range(8):
ax = self.xlist[i] + x
ay = self.ylist[i] + y
check_flag = put_flag = False
px = ax; py = ay
while (0 <= px <= 7 and 0 <= py <= 7):
if board[8*py+px] == (not turn):
check_flag = True
elif board[8*py+px] == turn and check_flag:
put_flag = True
else:
break
px += self.xlist[i]
py += self.ylist[i]
while put_flag:
if board[8*ay+ax] == (not turn):
board[8*ay+ax] = turn
else:
break
ax += self.xlist[i]
ay += self.ylist[i]
def get_nummoves(self, turn):
""" そのターンの手数をリストにする """
nummoves = []
for i in range(64):
x, y = get_pos(i)
if board[8*y+x] == NONE and self.check_put(x, y, turn):
nummoves.append((x, y))
return nummoves
def judge_remain(self, state):
""" 白、黒が全滅、置く所がなくなったか判定する """
return state not in board
def judge_end(self):
""" どちらも置けなくなって終局になるか判定する """
for stone in (BLACK, WHITE):
if self.get_nummoves(stone):
return False
return True
class AI(Reversi):
def __init__(self, stone):
Reversi.__init__(self)
self.stone = stone
"""
weights = (120, -20, 20, 5, 5, 20, -20, 120, -20, -40, -5, -5,
-5, -5, -40, -20, 20, -5, 15, 3, 3, 15, -5, 20, 5,
-5, 3, 3, 3, 3, -5, 20)
"""
weights = (30,-12,0,-1,-1,0,-12,30,-12,-15,-3,-3,-3,-3,-15,-12,
0,-3,0,-1,-1,0,-3,0,-1,-3,-1,-1,-1,-1,-3,-1)
self.wboard = [0 for i in range(64)]
for i in range(len(weights)):
self.wboard[i] = weights[i]
self.wboard[63-i] = weights[i]
def put(self):
self.nummoves = self.get_nummoves(self.stone)
x, y = self.thinking()
return x, y
def calc_weights(self):
score = 0
for i in range(64):
if board[i] == self.stone:
score += self.wboard[i]
elif board[i] == (not self.stone):
score -= self.wboard[i]
return score
def judge_score(self):
global board
score_list = []
for x, y in self.nummoves:
backup = board[:]
if self.put_stone(x, y, self.stone, count=False):
score_list.append((self.calc_weights(), x, y))
board = backup[:]
x, y = max(score_list)[1:]
return x, y
def thinking(self):
# 鼠定石にならないようにする
if self.stone == WHITE and COUNT == 2:
nummoves = []
for x, y in self.nummoves:
count = 0
for ax, ay in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if board[(y+ay)*8+(x+ax)] == (not self.stone):
count += 1
if count != 2:
nummoves.append((x, y))
else:
random.choice(nummoves)
return self.judge_score()
if COUNT <= 15: #序盤
pass
elif COUNT <= 40: #中盤
pass
else: #終盤
pass
class Board(Tk.Canvas):
cs = 50
def __init__(self, master, msg):
Tk.Canvas.__init__(self, master, bg="white",
width=self.cs*8, height=self.cs*8)
self.msg = msg
self.init()
self.bind_all("<Button-1>", self.clicked)
self.after(0, self.loop)
def init(self):
self.cpp_id = None
self.cpp = (-1, -1)
self.ply_turn = random.randrange(2)
if len(sys.argv) >= 2:
self.ply_turn = int(sys.argv[1])
self.com_turn = not self.ply_turn
self.reversi = Reversi()
self.ai = AI(self.com_turn)
self.turn = BLACK
self.draw_board()
self.draw_stone()
self.msg.update("プレイヤーの番です" if self.ply_turn == BLACK else "")
self.player_time = time.time()
def game_set(self):
self.end_game()
for i in self.find_all(): self.delete(i)
self.init()
def draw_board(self):
self.create_rectangle(0, 0, self.cs*8, self.cs*8, fill="#008B00")
for pos in range(1,8):
pos *= self.cs
self.create_line(pos, 0, pos, self.cs*8)
self.create_line(0, pos, self.cs*8, pos)
wid = 3
for x, y in (2, 2), (2, 6), (6, 2), (6, 6):
self.create_oval(self.cs*x-wid, self.cs*y-wid,
self.cs*x+wid, self.cs*y+wid, fill="black")
def draw_stone(self):
for y in range(8):
for x in range(8):
x0 = self.cs * x
y0 = self.cs * y
if board[8*y+x] == BLACK:
self.create_oval(x0+3, y0+3, x0+self.cs-3,
y0+self.cs-3, fill="#000000", width=1)
elif board[8*y+x] == WHITE:
self.create_oval(x0+3, y0+3, x0+self.cs-3,
y0+self.cs-3, fill="#FFFFFF", width=1)
if self.cpp_id: self.delete(self.cpp_id)
x, y = self.cpp
x = self.cs * x + self.cs / 2
y = self.cs * y + self.cs / 2
self.cpp_id = self.create_oval(x-5, y-5, x+5, y+5, fill="red")
def loop(self):
if self.reversi.judge_remain(NONE) or self.reversi.judge_end():
self.game_set()
if (self.turn == self.com_turn and
(time.time() - self.player_time) >= 0.3):
self.turn = self.ply_turn
if not self.reversi.get_nummoves(self.com_turn):
self.msg.update(text="COMはパスしました")
else:
x, y = self.ai.put()
self.reversi.put_stone(x, y, self.com_turn)
stone = "黒" if self.ply_turn==WHITE else "白"
self.msg.update(text="プレイヤーの番です",
pos="COM (%d,%d) %s"%(x, y, stone))
self.cpp = (x, y)
self.draw_stone()
elif self.turn == self.ply_turn:
if not self.reversi.get_nummoves(self.ply_turn):
self.turn = self.com_turn
self.after(50, self.loop)
def clicked(self, event):
self.msg.update(text="")
if self.turn != self.ply_turn: return
x = event.x / self.cs
y = event.y / self.cs
result = self.reversi.put_stone(x, y, self.ply_turn)
if result:
self.draw_stone()
self.turn = self.com_turn
if self.reversi.judge_remain(self.turn): self.game_set()
self.player_time = time.time()
stone = "黒" if self.ply_turn == BLACK else "白"
self.msg.update(pos="PLAYER (%d,%d) %s" %(x, y, stone))
else:
self.msg.update(text="そこには置けません")
def end_game(self):
bn = wn = 0
ply = com = 0
for i in range(64):
x, y = get_pos(i)
if board[8*y+x] == BLACK:
bn += 1
elif board[8*y+x] == WHITE:
wn += 1
if board[8*y+x] == self.ply_turn:
ply += 1
elif board[8*y+x] == self.com_turn:
com += 1
if ply > com:
text = "プレイヤーの勝ち"
elif ply < com:
text = "COMの勝ち"
else:
text = "引き分け"
text = "黒:%d 白:%d " % (bn,wn) + "\n" + text
dialog = D.Dialog(title="勝敗", text=text, bitmap="question",
default=0, strings=["OK"])
self.msg.clear()
self.turn = BLACK
class Message:
def __init__(self, master):
self.msg = Tk.Label(master, relief=Tk.RIDGE, bd=2)
self.msg.pack(fill=Tk.X)
self.log = ST.ScrolledText(master, state=Tk.DISABLED,
font=("Monospace", "12"),
height=5, width=1)
self.log.pack(side=Tk.BOTTOM, fill=Tk.X)
def update(self, text=None, pos=None):
if text is not None:
self.msg.config(text=text)
if pos is not None:
self.log.config(state=Tk.NORMAL)
self.log.insert(Tk.END, pos+"\n")
self.log.config(state=Tk.DISABLED)
self.log.see(Tk.END)
def clear(self):
self.msg.config(text="")
self.log.config(state=Tk.NORMAL)
self.log.delete("1.0", Tk.END)
self.log.config(state=Tk.DISABLED)
class Application(Tk.Frame):
def __init__(self, master=None):
Tk.Frame.__init__(self, master)
frame = Tk.Frame()
message = Message(frame)
self.board = Board(frame, message)
self.board.pack(side=Tk.TOP)
frame.pack()
self.set_menubar()
def set_menubar(self):
menubar = Tk.Menu(self, tearoff=0)
filemenu = Tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label=u"ゲーム(G)", menu=filemenu, underline=5)
filemenu.add_command(label=u"最初から", command=self.board.init)
self.master.config(menu=menubar)
if __name__ == "__main__":
frame = Application()
frame.pack()
frame.mainloop()
IyEvdXNyL2Jpbi9weXRob24KI2NvZGluZzogdXRmLTgKCmltcG9ydCBUa2ludGVyIGFzIFRrCmltcG9ydCBEaWFsb2cgYXMgRAppbXBvcnQgU2Nyb2xsZWRUZXh0IGFzIFNUCmltcG9ydCByYW5kb20sIHRpbWUKaW1wb3J0IHN5cwoKQkxBQ0ssIFdISVRFLCBOT05FID0gcmFuZ2UoMykKCmRlZiBnZXRfcG9zKGkpOgogICAgcmV0dXJuIGkgJSA4LCBpIC8gOAoKY2xhc3MgUmV2ZXJzaToKICAgIGRlZiBfX2luaXRfXyhzZWxmKToKICAgICAgICBzZWxmLnhsaXN0ID0gKC0xLCAwLCAxLCAtMSwgMSwgLTEsIDAsIDEpCiAgICAgICAgc2VsZi55bGlzdCA9ICgtMSwgLTEsIC0xLCAwLCAwLCAxLCAxLCAxKQogICAgICAgIHNlbGYuaW5pdF9nYW1lKCkKCiAgICBkZWYgaW5pdF9nYW1lKHNlbGYpOgogICAgICAgIGdsb2JhbCBib2FyZAogICAgICAgIGJvYXJkID0gW05PTkUgZm9yIGkgaW4gcmFuZ2UoNjQpXQogICAgICAgIGJvYXJkWzgqMyszXSA9IGJvYXJkWzgqNCs0XSA9IFdISVRFCiAgICAgICAgYm9hcmRbOCo0KzNdID0gYm9hcmRbOCozKzRdID0gQkxBQ0sKICAgICAgICBnbG9iYWwgQ09VTlQKICAgICAgICBDT1VOVCA9IDEKCiAgICBkZWYgcHV0X3N0b25lKHNlbGYsIHgsIHksIHR1cm4sIGNvdW50PVRydWUpOgogICAgICAgIGdsb2JhbCBDT1VOVAogICAgICAgIGlmIGJvYXJkWzgqeSt4XSAhPSBOT05FOgogICAgICAgICAgICByZXR1cm4gRmFsc2UKICAgICAgICBlbGlmIHNlbGYuY2hlY2tfcHV0KHgsIHksIHR1cm4pOgogICAgICAgICAgICBpZiBjb3VudDogQ09VTlQgKz0gMQogICAgICAgICAgICBzZWxmLnB1dF9zdG9uZXMoeCwgeSwgdHVybikKICAgICAgICAgICAgcmV0dXJuIFRydWUKICAgICAgICBlbHNlOgogICAgICAgICAgICByZXR1cm4gRmFsc2UKCiAgICBkZWYgY2hlY2tfcHV0KHNlbGYsIHgsIHksIHR1cm4pOgogICAgICAgICIiIiAoeCx5KeOBruS9jee9ruOBq+efs+OCkue9ruOBkeOCi+OBi+WIpOWumuOBmeOCiyAiIiIKICAgICAgICByZXZudW0gPSAwCiAgICAgICAgZm9yIGkgaW4gcmFuZ2UoOCk6CiAgICAgICAgICAgIGF4ID0gc2VsZi54bGlzdFtpXSArIHgKICAgICAgICAgICAgYXkgPSBzZWxmLnlsaXN0W2ldICsgeQogICAgICAgICAgICBwcmV2bnVtID0gMAogICAgICAgICAgICB3aGlsZSBUcnVlOgogICAgICAgICAgICAgICAgaWYgbm90KDAgPD0gYXggPD0gNyBhbmQgMCA8PSBheSA8PSA3KToKICAgICAgICAgICAgICAgICAgICBicmVhawogICAgICAgICAgICAgICAgaWYgYm9hcmRbOCpheStheF0gPT0gKG5vdCB0dXJuKToKICAgICAgICAgICAgICAgICAgICBwcmV2bnVtICs9IDEKICAgICAgICAgICAgICAgIGVsaWYgYm9hcmRbOCpheStheF0gPT0gdHVybiBhbmQgcHJldm51bToKICAgICAgICAgICAgICAgICAgICByZXZudW0gKz0gcHJldm51bQogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgICAgICBheCArPSBzZWxmLnhsaXN0W2ldCiAgICAgICAgICAgICAgICBheSArPSBzZWxmLnlsaXN0W2ldCiAgICAgICAgcmV0dXJuIHJldm51bQoKICAgIGRlZiBwdXRfc3RvbmVzKHNlbGYsIHgsIHksIHR1cm4pOgogICAgICAgICIiIiAoeCwgeSnjgavnn7PjgpLnva7jgY8gIiIiCiAgICAgICAgYm9hcmRbOCp5K3hdID0gdHVybgogICAgICAgIGZvciBpIGluIHJhbmdlKDgpOgogICAgICAgICAgICBheCA9IHNlbGYueGxpc3RbaV0gKyB4CiAgICAgICAgICAgIGF5ID0gc2VsZi55bGlzdFtpXSArIHkKICAgICAgICAgICAgY2hlY2tfZmxhZyA9IHB1dF9mbGFnID0gRmFsc2UKCiAgICAgICAgICAgIHB4ID0gYXg7IHB5ID0gYXkKICAgICAgICAgICAgd2hpbGUgKDAgPD0gcHggPD0gNyBhbmQgMCA8PSBweSA8PSA3KToKICAgICAgICAgICAgICAgIGlmIGJvYXJkWzgqcHkrcHhdID09IChub3QgdHVybik6CiAgICAgICAgICAgICAgICAgICAgY2hlY2tfZmxhZyA9IFRydWUKICAgICAgICAgICAgICAgIGVsaWYgYm9hcmRbOCpweStweF0gPT0gdHVybiBhbmQgY2hlY2tfZmxhZzoKICAgICAgICAgICAgICAgICAgICBwdXRfZmxhZyA9IFRydWUKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgICAgIHB4ICs9IHNlbGYueGxpc3RbaV0KICAgICAgICAgICAgICAgIHB5ICs9IHNlbGYueWxpc3RbaV0KCiAgICAgICAgICAgIHdoaWxlIHB1dF9mbGFnOgogICAgICAgICAgICAgICAgaWYgYm9hcmRbOCpheStheF0gPT0gKG5vdCB0dXJuKToKICAgICAgICAgICAgICAgICAgICBib2FyZFs4KmF5K2F4XSA9IHR1cm4KICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgICAgIGF4ICs9IHNlbGYueGxpc3RbaV0KICAgICAgICAgICAgICAgIGF5ICs9IHNlbGYueWxpc3RbaV0KCiAgICBkZWYgZ2V0X251bW1vdmVzKHNlbGYsIHR1cm4pOgogICAgICAgICIiIiDjgZ3jga7jgr/jg7zjg7Pjga7miYvmlbDjgpLjg6rjgrnjg4jjgavjgZnjgosgIiIiCiAgICAgICAgbnVtbW92ZXMgPSBbXQogICAgICAgIGZvciBpIGluIHJhbmdlKDY0KToKICAgICAgICAgICAgeCwgeSA9IGdldF9wb3MoaSkKICAgICAgICAgICAgaWYgYm9hcmRbOCp5K3hdID09IE5PTkUgYW5kIHNlbGYuY2hlY2tfcHV0KHgsIHksIHR1cm4pOgogICAgICAgICAgICAgICAgbnVtbW92ZXMuYXBwZW5kKCh4LCB5KSkKICAgICAgICByZXR1cm4gbnVtbW92ZXMKCiAgICBkZWYganVkZ2VfcmVtYWluKHNlbGYsIHN0YXRlKToKICAgICAgICAiIiIg55m944CB6buS44GM5YWo5ruF44CB572u44GP5omA44GM44Gq44GP44Gq44Gj44Gf44GL5Yik5a6a44GZ44KLICIiIgogICAgICAgIHJldHVybiBzdGF0ZSBub3QgaW4gYm9hcmQKCiAgICBkZWYganVkZ2VfZW5kKHNlbGYpOgogICAgICAgICIiIiDjganjgaHjgonjgoLnva7jgZHjgarjgY/jgarjgaPjgabntYLlsYDjgavjgarjgovjgYvliKTlrprjgZnjgosgIiIiCiAgICAgICAgZm9yIHN0b25lIGluIChCTEFDSywgV0hJVEUpOgogICAgICAgICAgICBpZiBzZWxmLmdldF9udW1tb3ZlcyhzdG9uZSk6CiAgICAgICAgICAgICAgICByZXR1cm4gRmFsc2UKICAgICAgICByZXR1cm4gVHJ1ZQoKY2xhc3MgQUkoUmV2ZXJzaSk6CiAgICBkZWYgX19pbml0X18oc2VsZiwgc3RvbmUpOgogICAgICAgIFJldmVyc2kuX19pbml0X18oc2VsZikKICAgICAgICBzZWxmLnN0b25lID0gc3RvbmUKICAgICAgICAiIiIKICAgICAgICB3ZWlnaHRzID0gKDEyMCwgLTIwLCAyMCwgNSwgNSwgMjAsIC0yMCwgMTIwLCAtMjAsIC00MCwgLTUsIC01LAogICAgICAgICAgICAgICAgICAgLTUsIC01LCAtNDAsIC0yMCwgMjAsIC01LCAxNSwgMywgMywgMTUsIC01LCAyMCwgNSwKICAgICAgICAgICAgICAgICAgIC01LCAzLCAzLCAzLCAzLCAtNSwgMjApCiAgICAgICAgICAgICAgICAgICAiIiIKICAgICAgICB3ZWlnaHRzID0gKDMwLC0xMiwwLC0xLC0xLDAsLTEyLDMwLC0xMiwtMTUsLTMsLTMsLTMsLTMsLTE1LC0xMiwKICAgICAgICAgICAgICAgICAgIDAsLTMsMCwtMSwtMSwwLC0zLDAsLTEsLTMsLTEsLTEsLTEsLTEsLTMsLTEpCgogICAgICAgIHNlbGYud2JvYXJkID0gWzAgZm9yIGkgaW4gcmFuZ2UoNjQpXQogICAgICAgIGZvciBpIGluIHJhbmdlKGxlbih3ZWlnaHRzKSk6CiAgICAgICAgICAgIHNlbGYud2JvYXJkW2ldID0gd2VpZ2h0c1tpXQogICAgICAgICAgICBzZWxmLndib2FyZFs2My1pXSA9IHdlaWdodHNbaV0KCiAgICBkZWYgcHV0KHNlbGYpOgogICAgICAgIHNlbGYubnVtbW92ZXMgPSBzZWxmLmdldF9udW1tb3ZlcyhzZWxmLnN0b25lKQogICAgICAgIHgsIHkgPSBzZWxmLnRoaW5raW5nKCkKICAgICAgICByZXR1cm4geCwgeQoKICAgIGRlZiBjYWxjX3dlaWdodHMoc2VsZik6CiAgICAgICAgc2NvcmUgPSAwCiAgICAgICAgZm9yIGkgaW4gcmFuZ2UoNjQpOgogICAgICAgICAgICBpZiBib2FyZFtpXSA9PSBzZWxmLnN0b25lOgogICAgICAgICAgICAgICAgc2NvcmUgKz0gc2VsZi53Ym9hcmRbaV0KICAgICAgICAgICAgZWxpZiBib2FyZFtpXSA9PSAobm90IHNlbGYuc3RvbmUpOgogICAgICAgICAgICAgICAgc2NvcmUgLT0gc2VsZi53Ym9hcmRbaV0KICAgICAgICByZXR1cm4gc2NvcmUKCiAgICBkZWYganVkZ2Vfc2NvcmUoc2VsZik6CiAgICAgICAgZ2xvYmFsIGJvYXJkCiAgICAgICAgc2NvcmVfbGlzdCA9IFtdCiAgICAgICAgZm9yIHgsIHkgaW4gc2VsZi5udW1tb3ZlczoKICAgICAgICAgICAgYmFja3VwID0gYm9hcmRbOl0KICAgICAgICAgICAgaWYgc2VsZi5wdXRfc3RvbmUoeCwgeSwgc2VsZi5zdG9uZSwgY291bnQ9RmFsc2UpOgogICAgICAgICAgICAgICAgc2NvcmVfbGlzdC5hcHBlbmQoKHNlbGYuY2FsY193ZWlnaHRzKCksIHgsIHkpKQogICAgICAgICAgICAgICAgYm9hcmQgPSBiYWNrdXBbOl0KICAgICAgICB4LCB5ID0gbWF4KHNjb3JlX2xpc3QpWzE6XQogICAgICAgIHJldHVybiB4LCB5CgogICAgZGVmIHRoaW5raW5nKHNlbGYpOgogICAgICAgICMg6byg5a6a55+z44Gr44Gq44KJ44Gq44GE44KI44GG44Gr44GZ44KLCiAgICAgICAgaWYgc2VsZi5zdG9uZSA9PSBXSElURSBhbmQgQ09VTlQgPT0gMjoKICAgICAgICAgICAgbnVtbW92ZXMgPSBbXQogICAgICAgICAgICBmb3IgeCwgeSBpbiBzZWxmLm51bW1vdmVzOgogICAgICAgICAgICAgICAgY291bnQgPSAwCiAgICAgICAgICAgICAgICBmb3IgYXgsIGF5IGluIFsoLTEsIDApLCAoMSwgMCksICgwLCAtMSksICgwLCAxKV06CiAgICAgICAgICAgICAgICAgICAgaWYgYm9hcmRbKHkrYXkpKjgrKHgrYXgpXSA9PSAobm90IHNlbGYuc3RvbmUpOgogICAgICAgICAgICAgICAgICAgICAgICBjb3VudCArPSAxCiAgICAgICAgICAgICAgICBpZiBjb3VudCAhPSAyOgogICAgICAgICAgICAgICAgICAgIG51bW1vdmVzLmFwcGVuZCgoeCwgeSkpCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICByYW5kb20uY2hvaWNlKG51bW1vdmVzKQoKICAgICAgICByZXR1cm4gc2VsZi5qdWRnZV9zY29yZSgpCgogICAgICAgIGlmIENPVU5UIDw9IDE1OiAj5bqP55ukCiAgICAgICAgICAgIHBhc3MKICAgICAgICBlbGlmIENPVU5UIDw9IDQwOiAj5Lit55ukCiAgICAgICAgICAgIHBhc3MKICAgICAgICBlbHNlOiAj57WC55ukCiAgICAgICAgICAgIHBhc3MKCmNsYXNzIEJvYXJkKFRrLkNhbnZhcyk6CiAgICBjcyA9IDUwCiAgICBkZWYgX19pbml0X18oc2VsZiwgbWFzdGVyLCBtc2cpOgogICAgICAgIFRrLkNhbnZhcy5fX2luaXRfXyhzZWxmLCBtYXN0ZXIsIGJnPSJ3aGl0ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoPXNlbGYuY3MqOCwgaGVpZ2h0PXNlbGYuY3MqOCkKICAgICAgICBzZWxmLm1zZyA9IG1zZwogICAgICAgIHNlbGYuaW5pdCgpCiAgICAgICAgc2VsZi5iaW5kX2FsbCgiPEJ1dHRvbi0xPiIsIHNlbGYuY2xpY2tlZCkKICAgICAgICBzZWxmLmFmdGVyKDAsIHNlbGYubG9vcCkKCiAgICBkZWYgaW5pdChzZWxmKToKICAgICAgICBzZWxmLmNwcF9pZCA9IE5vbmUKICAgICAgICBzZWxmLmNwcCA9ICgtMSwgLTEpCiAgICAgICAgc2VsZi5wbHlfdHVybiA9IHJhbmRvbS5yYW5kcmFuZ2UoMikKICAgICAgICBpZiBsZW4oc3lzLmFyZ3YpID49IDI6CiAgICAgICAgICAgIHNlbGYucGx5X3R1cm4gPSBpbnQoc3lzLmFyZ3ZbMV0pCiAgICAgICAgc2VsZi5jb21fdHVybiA9IG5vdCBzZWxmLnBseV90dXJuCiAgICAgICAgc2VsZi5yZXZlcnNpID0gUmV2ZXJzaSgpCiAgICAgICAgc2VsZi5haSA9IEFJKHNlbGYuY29tX3R1cm4pCiAgICAgICAgc2VsZi50dXJuID0gQkxBQ0sKICAgICAgICBzZWxmLmRyYXdfYm9hcmQoKQogICAgICAgIHNlbGYuZHJhd19zdG9uZSgpCiAgICAgICAgc2VsZi5tc2cudXBkYXRlKCLjg5fjg6zjgqTjg6Tjg7zjga7nlarjgafjgZkiIGlmIHNlbGYucGx5X3R1cm4gPT0gQkxBQ0sgZWxzZSAiIikKICAgICAgICBzZWxmLnBsYXllcl90aW1lID0gdGltZS50aW1lKCkKCiAgICBkZWYgZ2FtZV9zZXQoc2VsZik6CiAgICAgICAgc2VsZi5lbmRfZ2FtZSgpCiAgICAgICAgZm9yIGkgaW4gc2VsZi5maW5kX2FsbCgpOiBzZWxmLmRlbGV0ZShpKQogICAgICAgIHNlbGYuaW5pdCgpCgogICAgZGVmIGRyYXdfYm9hcmQoc2VsZik6CiAgICAgICAgc2VsZi5jcmVhdGVfcmVjdGFuZ2xlKDAsIDAsIHNlbGYuY3MqOCwgc2VsZi5jcyo4LCBmaWxsPSIjMDA4QjAwIikKICAgICAgICBmb3IgcG9zIGluIHJhbmdlKDEsOCk6CiAgICAgICAgICAgIHBvcyAqPSBzZWxmLmNzCiAgICAgICAgICAgIHNlbGYuY3JlYXRlX2xpbmUocG9zLCAwLCBwb3MsIHNlbGYuY3MqOCkKICAgICAgICAgICAgc2VsZi5jcmVhdGVfbGluZSgwLCBwb3MsIHNlbGYuY3MqOCwgcG9zKQogICAgICAgIHdpZCA9IDMKICAgICAgICBmb3IgeCwgeSBpbiAoMiwgMiksICgyLCA2KSwgKDYsIDIpLCAoNiwgNik6CiAgICAgICAgICAgIHNlbGYuY3JlYXRlX292YWwoc2VsZi5jcyp4LXdpZCwgc2VsZi5jcyp5LXdpZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxmLmNzKngrd2lkLCBzZWxmLmNzKnkrd2lkLCBmaWxsPSJibGFjayIpCgogICAgZGVmIGRyYXdfc3RvbmUoc2VsZik6CiAgICAgICAgZm9yIHkgaW4gcmFuZ2UoOCk6CiAgICAgICAgICAgIGZvciB4IGluIHJhbmdlKDgpOgogICAgICAgICAgICAgICAgeDAgPSBzZWxmLmNzICogeAogICAgICAgICAgICAgICAgeTAgPSBzZWxmLmNzICogeQogICAgICAgICAgICAgICAgaWYgYm9hcmRbOCp5K3hdID09IEJMQUNLOgogICAgICAgICAgICAgICAgICAgIHNlbGYuY3JlYXRlX292YWwoeDArMywgeTArMywgeDArc2VsZi5jcy0zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeTArc2VsZi5jcy0zLCBmaWxsPSIjMDAwMDAwIiwgd2lkdGg9MSkKICAgICAgICAgICAgICAgIGVsaWYgYm9hcmRbOCp5K3hdID09IFdISVRFOgogICAgICAgICAgICAgICAgICAgIHNlbGYuY3JlYXRlX292YWwoeDArMywgeTArMywgeDArc2VsZi5jcy0zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeTArc2VsZi5jcy0zLCBmaWxsPSIjRkZGRkZGIiwgd2lkdGg9MSkKCiAgICAgICAgaWYgc2VsZi5jcHBfaWQ6IHNlbGYuZGVsZXRlKHNlbGYuY3BwX2lkKQogICAgICAgIHgsIHkgPSBzZWxmLmNwcAogICAgICAgIHggPSBzZWxmLmNzICogeCArIHNlbGYuY3MgLyAyCiAgICAgICAgeSA9IHNlbGYuY3MgKiB5ICsgc2VsZi5jcyAvIDIKICAgICAgICBzZWxmLmNwcF9pZCA9IHNlbGYuY3JlYXRlX292YWwoeC01LCB5LTUsIHgrNSwgeSs1LCBmaWxsPSJyZWQiKQoKICAgIGRlZiBsb29wKHNlbGYpOgogICAgICAgIGlmIHNlbGYucmV2ZXJzaS5qdWRnZV9yZW1haW4oTk9ORSkgb3Igc2VsZi5yZXZlcnNpLmp1ZGdlX2VuZCgpOgogICAgICAgICAgICBzZWxmLmdhbWVfc2V0KCkKCiAgICAgICAgaWYgKHNlbGYudHVybiA9PSBzZWxmLmNvbV90dXJuIGFuZAogICAgICAgICAgICAodGltZS50aW1lKCkgLSBzZWxmLnBsYXllcl90aW1lKSA+PSAwLjMpOgogICAgICAgICAgICBzZWxmLnR1cm4gPSBzZWxmLnBseV90dXJuCgogICAgICAgICAgICBpZiBub3Qgc2VsZi5yZXZlcnNpLmdldF9udW1tb3ZlcyhzZWxmLmNvbV90dXJuKToKICAgICAgICAgICAgICAgIHNlbGYubXNnLnVwZGF0ZSh0ZXh0PSJDT03jga/jg5HjgrnjgZfjgb7jgZfjgZ8iKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgeCwgeSA9IHNlbGYuYWkucHV0KCkKICAgICAgICAgICAgICAgIHNlbGYucmV2ZXJzaS5wdXRfc3RvbmUoeCwgeSwgc2VsZi5jb21fdHVybikKICAgICAgICAgICAgICAgIHN0b25lID0gIum7kiIgaWYgc2VsZi5wbHlfdHVybj09V0hJVEUgZWxzZSAi55m9IgogICAgICAgICAgICAgICAgc2VsZi5tc2cudXBkYXRlKHRleHQ9IuODl+ODrOOCpOODpOODvOOBrueVquOBp+OBmSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zPSJDT00gICAgKCVkLCVkKSAlcyIlKHgsIHksIHN0b25lKSkKICAgICAgICAgICAgICAgIHNlbGYuY3BwID0gKHgsIHkpCiAgICAgICAgICAgIHNlbGYuZHJhd19zdG9uZSgpCiAgICAgICAgZWxpZiBzZWxmLnR1cm4gPT0gc2VsZi5wbHlfdHVybjoKICAgICAgICAgICAgaWYgbm90IHNlbGYucmV2ZXJzaS5nZXRfbnVtbW92ZXMoc2VsZi5wbHlfdHVybik6CiAgICAgICAgICAgICAgICBzZWxmLnR1cm4gPSBzZWxmLmNvbV90dXJuCiAgICAgICAgc2VsZi5hZnRlcig1MCwgc2VsZi5sb29wKQoKICAgIGRlZiBjbGlja2VkKHNlbGYsIGV2ZW50KToKICAgICAgICBzZWxmLm1zZy51cGRhdGUodGV4dD0iIikKICAgICAgICBpZiBzZWxmLnR1cm4gIT0gc2VsZi5wbHlfdHVybjogcmV0dXJuCgogICAgICAgIHggPSBldmVudC54IC8gc2VsZi5jcwogICAgICAgIHkgPSBldmVudC55IC8gc2VsZi5jcwoKICAgICAgICByZXN1bHQgPSBzZWxmLnJldmVyc2kucHV0X3N0b25lKHgsIHksIHNlbGYucGx5X3R1cm4pCiAgICAgICAgaWYgcmVzdWx0OgogICAgICAgICAgICBzZWxmLmRyYXdfc3RvbmUoKQogICAgICAgICAgICBzZWxmLnR1cm4gPSBzZWxmLmNvbV90dXJuCiAgICAgICAgICAgIGlmIHNlbGYucmV2ZXJzaS5qdWRnZV9yZW1haW4oc2VsZi50dXJuKTogc2VsZi5nYW1lX3NldCgpCiAgICAgICAgICAgIHNlbGYucGxheWVyX3RpbWUgPSB0aW1lLnRpbWUoKQogICAgICAgICAgICBzdG9uZSA9ICLpu5IiIGlmIHNlbGYucGx5X3R1cm4gPT0gQkxBQ0sgZWxzZSAi55m9IgogICAgICAgICAgICBzZWxmLm1zZy51cGRhdGUocG9zPSJQTEFZRVIgKCVkLCVkKSAlcyIgJSh4LCB5LCBzdG9uZSkpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgc2VsZi5tc2cudXBkYXRlKHRleHQ9IuOBneOBk+OBq+OBr+e9ruOBkeOBvuOBm+OCkyIpCgogICAgZGVmIGVuZF9nYW1lKHNlbGYpOgogICAgICAgIGJuID0gd24gPSAwCiAgICAgICAgcGx5ID0gY29tID0gMAogICAgICAgIGZvciBpIGluIHJhbmdlKDY0KToKICAgICAgICAgICAgeCwgeSA9IGdldF9wb3MoaSkKICAgICAgICAgICAgaWYgYm9hcmRbOCp5K3hdID09IEJMQUNLOgogICAgICAgICAgICAgICAgYm4gKz0gMQogICAgICAgICAgICBlbGlmIGJvYXJkWzgqeSt4XSA9PSBXSElURToKICAgICAgICAgICAgICAgIHduICs9IDEKICAgICAgICAgICAgaWYgYm9hcmRbOCp5K3hdID09IHNlbGYucGx5X3R1cm46CiAgICAgICAgICAgICAgICBwbHkgKz0gMQogICAgICAgICAgICBlbGlmIGJvYXJkWzgqeSt4XSA9PSBzZWxmLmNvbV90dXJuOgogICAgICAgICAgICAgICAgY29tICs9IDEKCiAgICAgICAgaWYgcGx5ID4gY29tOgogICAgICAgICAgICB0ZXh0ID0gIuODl+ODrOOCpOODpOODvOOBruWLneOBoSIKICAgICAgICBlbGlmIHBseSA8IGNvbToKICAgICAgICAgICAgdGV4dCA9ICJDT03jga7li53jgaEiCiAgICAgICAgZWxzZToKICAgICAgICAgICAgdGV4dCA9ICLlvJXjgY3liIbjgZEiCiAgICAgICAgdGV4dCA9ICLpu5I6JWQg55m9OiVkICIgJSAoYm4sd24pICsgIlxuIiArIHRleHQKICAgICAgICBkaWFsb2cgPSBELkRpYWxvZyh0aXRsZT0i5Yud5pWXIiwgdGV4dD10ZXh0LCBiaXRtYXA9InF1ZXN0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBkZWZhdWx0PTAsIHN0cmluZ3M9WyJPSyJdKQogICAgICAgIHNlbGYubXNnLmNsZWFyKCkKICAgICAgICBzZWxmLnR1cm4gPSBCTEFDSwoKY2xhc3MgTWVzc2FnZToKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBtYXN0ZXIpOgogICAgICAgIHNlbGYubXNnID0gVGsuTGFiZWwobWFzdGVyLCByZWxpZWY9VGsuUklER0UsIGJkPTIpCiAgICAgICAgc2VsZi5tc2cucGFjayhmaWxsPVRrLlgpCiAgICAgICAgc2VsZi5sb2cgPSBTVC5TY3JvbGxlZFRleHQobWFzdGVyLCBzdGF0ZT1Uay5ESVNBQkxFRCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb250PSgiTW9ub3NwYWNlIiwgIjEyIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVpZ2h0PTUsIHdpZHRoPTEpCiAgICAgICAgc2VsZi5sb2cucGFjayhzaWRlPVRrLkJPVFRPTSwgZmlsbD1Uay5YKQoKICAgIGRlZiB1cGRhdGUoc2VsZiwgdGV4dD1Ob25lLCBwb3M9Tm9uZSk6CiAgICAgICAgaWYgdGV4dCBpcyBub3QgTm9uZToKICAgICAgICAgICAgc2VsZi5tc2cuY29uZmlnKHRleHQ9dGV4dCkKICAgICAgICBpZiBwb3MgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIHNlbGYubG9nLmNvbmZpZyhzdGF0ZT1Uay5OT1JNQUwpCiAgICAgICAgICAgIHNlbGYubG9nLmluc2VydChUay5FTkQsIHBvcysiXG4iKQogICAgICAgICAgICBzZWxmLmxvZy5jb25maWcoc3RhdGU9VGsuRElTQUJMRUQpCiAgICAgICAgICAgIHNlbGYubG9nLnNlZShUay5FTkQpCgogICAgZGVmIGNsZWFyKHNlbGYpOgogICAgICAgIHNlbGYubXNnLmNvbmZpZyh0ZXh0PSIiKQogICAgICAgIHNlbGYubG9nLmNvbmZpZyhzdGF0ZT1Uay5OT1JNQUwpCiAgICAgICAgc2VsZi5sb2cuZGVsZXRlKCIxLjAiLCBUay5FTkQpCiAgICAgICAgc2VsZi5sb2cuY29uZmlnKHN0YXRlPVRrLkRJU0FCTEVEKQoKY2xhc3MgQXBwbGljYXRpb24oVGsuRnJhbWUpOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIG1hc3Rlcj1Ob25lKToKICAgICAgICBUay5GcmFtZS5fX2luaXRfXyhzZWxmLCBtYXN0ZXIpCiAgICAgICAgZnJhbWUgPSBUay5GcmFtZSgpCiAgICAgICAgbWVzc2FnZSA9IE1lc3NhZ2UoZnJhbWUpCiAgICAgICAgc2VsZi5ib2FyZCA9IEJvYXJkKGZyYW1lLCBtZXNzYWdlKQogICAgICAgIHNlbGYuYm9hcmQucGFjayhzaWRlPVRrLlRPUCkKICAgICAgICBmcmFtZS5wYWNrKCkKICAgICAgICBzZWxmLnNldF9tZW51YmFyKCkKCiAgICBkZWYgc2V0X21lbnViYXIoc2VsZik6CiAgICAgICAgbWVudWJhciA9IFRrLk1lbnUoc2VsZiwgdGVhcm9mZj0wKQogICAgICAgIGZpbGVtZW51ID0gVGsuTWVudShtZW51YmFyLCB0ZWFyb2ZmPTApCiAgICAgICAgbWVudWJhci5hZGRfY2FzY2FkZShsYWJlbD11IuOCsuODvOODoChHKSIsIG1lbnU9ZmlsZW1lbnUsICB1bmRlcmxpbmU9NSkKICAgICAgICBmaWxlbWVudS5hZGRfY29tbWFuZChsYWJlbD11IuacgOWIneOBi+OCiSIsIGNvbW1hbmQ9c2VsZi5ib2FyZC5pbml0KQogICAgICAgIHNlbGYubWFzdGVyLmNvbmZpZyhtZW51PW1lbnViYXIpCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgZnJhbWUgPSBBcHBsaWNhdGlvbigpCiAgICBmcmFtZS5wYWNrKCkKICAgIGZyYW1lLm1haW5sb29wKCkK