# -*- coding: utf-8 -*- from __future__ import unicode_literals from traceback import format_exc class GRPException(Exception): def __init__(self,msg): if type(msg)==type(u"abc"): self.utf_msg=msg self.str_msg=msg.encode("utf-8",errors='replace') else: self.str_msg=msg self.utf_msg=msg.decode("utf-8",errors='replace') log("===") log(format_exc()) log("===") Exception.__init__(self,self.str_msg) def __unicode__(self): return self.utf_msg import threading import codecs loglock=threading.Lock() def log(*args): global loglock loglock.acquire() encoding=sys.stdout.encoding for arg in args: try: if type(arg)==type(str('abc')): arg=arg.decode('utf-8',errors='replace') elif type(arg)!=type(u'abc'): try: arg=str(arg) except: arg=unicode(arg,errors='replace') arg=arg.decode('utf-8',errors='replace') arg=arg.encode(encoding, errors='replace') print arg, except: print "?"*len(arg), print loglock.release() def linelog(*args): global loglock loglock.acquire() encoding=sys.stdout.encoding for arg in args: try: if type(arg)==type(str('abc')): arg=arg.decode('utf-8',errors='replace') elif type(arg)!=type(u'abc'): try: arg=str(arg) except: arg=unicode(arg,errors='replace') arg=arg.decode('utf-8',errors='replace') arg=arg.encode(encoding, errors='replace') print arg, except: print "?"*len(arg), loglock.release() import tkMessageBox def show_error(txt,parent=None): if type(txt)==type(str('abc')): txt=txt.decode('utf-8',errors='replace') try: tkMessageBox.showerror(_("Error"),txt,parent=parent) log("ERROR: "+txt) except: log("ERROR: "+txt) def show_info(txt,parent=None): if type(txt)==type(str('abc')): txt=txt.decode('utf-8',errors='replace') try: tkMessageBox.showinfo(_("Information"),txt,parent=parent) log("INFO: "+txt) except: log("INFO: "+txt) def get_moves_number(move_zero): k=0 move=move_zero while move: move=move[0] k+=1 return k def go_to_move(move_zero,move_number=0): if move_number==0: return move_zero move=move_zero k=0 while k!=move_number: if not move: log("The end of the sgf tree was reached before getting to move_number",move_number) log("Could only reach move_number",k) return False move=move[0] k+=1 return move def gtp2ij(move): try: letters="ABCDEFGHJKLMNOPQRSTUVWXYZ" return int(move[1:])-1,letters.index(move[0]) except: raise GRPException("Cannot convert GTP coordinates "+str(move)+" to grid coordinates!") def ij2gtp(m): # (17,0) => a18 try: if m==None: return "pass" i,j=m letters="ABCDEFGHJKLMNOPQRSTUVWXYZ" return letters[j]+str(i+1) except: raise GRPException("Cannot convert grid coordinates "+str(m)+" to GTP coordinates!") def sgf2ij(m): # cj => 8,2 a, b=m letters="abcdefghjklmnopqrstuvwxyz" i=letters.index(b) j=letters.index(a) return i, j def ij2sgf(m): # (17,0) => ??? try: if m==None: return "pass" i,j=m letters=['a','b','c','d','e','f','g','h','j','k','l','m','n','o','p','q','r','s','t'] return letters[j]+letters[i] except: raise GRPException("Cannot convert grid coordinates "+str(m)+" to SGF coordinates!") from gomill import sgf, sgf_moves from Tkinter import * #from Tix import Tk, NoteBook from Tkconstants import * import sys import os import urllib2 class DownloadFromURL(Toplevel): def __init__(self,parent,bots=None): Toplevel.__init__(self,parent) self.bots=bots self.parent=parent self.title('GoReviewPartner') self.config(padx=10,pady=10) Label(self,text=_("Paste the URL to the SGF file (http or https):")).grid(row=1,column=1,sticky=W) self.url_entry=Entry(self) self.url_entry.grid(row=2,column=1,sticky=W) self.url_entry.focus() Button(self,text=_("Get"),command=self.get).grid(row=3,column=1,sticky=E) self.protocol("WM_DELETE_WINDOW", self.close) def get(self): user_agent = 'GoReviewPartner (https://github.com/pnprog/goreviewpartner/)' headers = { 'User-Agent' : user_agent } url=self.url_entry.get() if not url: return if url[:4]!="http": url="http://"+url log("Downloading",url) r=urllib2.Request(url,headers=headers) try: h=urllib2.urlopen(r) except: show_error(_("Could not open the URL"),parent=self) return filename="" sgf=h.read() if sgf[:7]!="(;FF[4]": log("not a sgf file") show_error(_("Not a valid SGF file!"),parent=self) log(sgf[:7]) return try: filename=h.info()['Content-Disposition'] if 'filename="' in filename: filename=filename.split('filename="')[0][:-1] if "''" in filename: filename=filename.split("''")[1] except: log("no Content-Disposition in header") black='black' white='white' date="" if 'PB[' in sgf: black=sgf.split('PB[')[1].split(']')[0] if 'PW[' in sgf: white=sgf.split('PW[')[1].split(']')[0] if 'DT[' in sgf: date=sgf.split('DT[')[1].split(']')[0] filename="" if date: filename=date+'_' filename+=black+'_VS_'+white+'.sgf' log(filename) game = convert_sgf_to_utf(sgf) write_rsgf(filename,game) popup=RangeSelector(self.parent,filename,self.bots) self.parent.add_popup(popup) self.close() def close(self): log("Closing DownloadFromURL()") self.parent.remove_popup(self) self.destroy() filelock=threading.Lock() c=0 def write_rsgf(filename,sgf_content): filelock.acquire() global c try: #log("Saving RSGF file",filename) if type(sgf_content)==type("abc"): content=sgf_content else: content=sgf_content.serialise() filename2=filename if type(filename2)==type(u"abc"): if sys.getfilesystemencoding()!="mbcs": filename2=filename2.encode(sys.getfilesystemencoding()) try: new_file=open(filename2,'w') new_file.write(content) except: new_file=codecs.open(filename2,"w","utf-8") new_file.write(content) new_file.close() filelock.release() except IOError, e: filelock.release() log("Could not save the RSGF file",filename) log("=>", e.errno,e.strerror) raise GRPException(_("Could not save the RSGF file: ")+filename+"\n"+e.strerror) except Exception,e: filelock.release() log("Could not save the RSGF file",filename) log("=>",e) raise GRPException(_("Could not save the RSGF file: ")+filename+"\n"+unicode(e)) def write_sgf(filename,sgf_content): filelock.acquire() try: log("Saving SGF file",filename) if type(sgf_content)==type("abc"): content=sgf_content else: content=sgf_content.serialise() filename2=filename if type(filename2)==type(u"abc"): if sys.getfilesystemencoding()!="mbcs": filename2=filename2.encode(sys.getfilesystemencoding()) try: new_file=open(filename2,'w') new_file.write(content) except: new_file=codecs.open(filename2,"w","utf-8") new_file.write(content) new_file.close() filelock.release() except IOError, e: filelock.release() log("Could not save the SGF file",filename) log("=>", e.errno,e.strerror) raise GRPException(_("Could not save the RSGF file: ")+filename+"\n"+e.strerror) except Exception,e: filelock.release() log("Could not save the RSGF file",filename) log("=>",e) raise GRPException(_("Could not save the SGF file: ")+filename+"\n"+unicode(e)) def convert_sgf_to_utf(content): game = sgf.Sgf_game.from_string(content) gameroot=game.get_root() sgf_moves.indicate_first_player(game) #adding the PL property on the root if node_has(gameroot,"CA"): ca=node_get(gameroot,"CA") if ca=="UTF-8": #the sgf is already in UTF, so we accept it directly return game else: log("Encoding is",ca) log("Converting from",ca,"to UTF-8") encoding=(codecs.lookup(ca).name.replace("_", "-").upper().replace("ISO8859", "ISO-8859")) #from gomill code content=game.serialise() content=content.decode(encoding,errors='ignore') #transforming content into a unicode object content=content.replace("CA["+ca+"]","CA[UTF-8]") game = sgf.Sgf_game.from_string(content.encode("utf-8")) #sgf.Sgf_game.from_string requires str object, not unicode return game else: log("the sgf has no declared encoding, we will enforce UTF-8 encoding") content=game.serialise() content=content.decode("utf",errors="replace").encode("utf") game = sgf.Sgf_game.from_string(content,override_encoding="UTF-8") return game def open_sgf(filename): filelock.acquire() try: #log("Opening SGF file",filename) filename2=filename if type(filename2)==type(u"abc"): if sys.getfilesystemencoding()!="mbcs": filename2=filename2.encode(sys.getfilesystemencoding()) txt = open(filename2,'r') content=clean_sgf(txt.read()) txt.close() filelock.release() game = convert_sgf_to_utf(content) return game except IOError, e: filelock.release() log("Could not open the SGF file",filename) log("=>", e.errno,e.strerror) raise GRPException(_("Could not open the RSGF file: ")+filename+"\n"+e.strerror) except Exception,e: log("Could not open the SGF file",filename) log("=>",e) try: filelock.release() except: pass raise GRPException(_("Could not open the SGF file: ")+filename+"\n"+unicode(e)) def clean_sgf(txt): #txt is still of type str here.... #https://github.com/pnprog/goreviewpartner/issues/56 txt=txt.replace(str(";B[ ])"),str(";B[])")).replace(str(";W[ ])"),str(";W[])")) #https://github.com/pnprog/goreviewpartner/issues/71 txt=txt.replace(str("KM[]"),str("")) txt=txt.replace(str("B[**];"),str("B[];")).replace(str("W[**];"),str("W[];")) return txt def get_all_sgf_leaves(root,deep=0): if len(root)==0: #this is a leave return [(root,deep)] leaves=[] deep+=1 for leaf in root: leaves.extend(get_all_sgf_leaves(leaf,deep)) return leaves def keep_only_one_leaf(leaf): while 1: try: parent=leaf.parent for other_leaf in parent: if other_leaf!=leaf: log("deleting...") other_leaf.delete() leaf=parent except: #reached root return def check_selection(selection,nb_moves): move_selection=[] selection=selection.replace(" ","") for sub_selection in selection.split(","): if sub_selection: try: if "-" in sub_selection: a,b=sub_selection.split('-') a=int(a) b=int(b) else: a=int(sub_selection) b=a if a<=b and a>0 and b<=nb_moves: move_selection.extend(range(a,b+1)) except Exception, e: print e return False move_selection=list(set(move_selection)) move_selection=sorted(move_selection) return move_selection def check_selection_for_color(move_zero,move_selection,color): if color=="black": new_move_selection=[] for m in move_selection: player_color=guess_color_to_play(move_zero,m) if player_color.lower()=='b': new_move_selection.append(m) return new_move_selection elif color=="white": new_move_selection=[] for m in move_selection: player_color=guess_color_to_play(move_zero,m) if player_color.lower()=='w': new_move_selection.append(m) return new_move_selection else: return move_selection class RangeSelector(Toplevel): def __init__(self,parent,filename,bots=None): Toplevel.__init__(self,parent) self.parent=parent self.filename=filename self.config(padx=10,pady=10) root = self root.parent.title('GoReviewPartner') self.protocol("WM_DELETE_WINDOW", self.close) self.bots=bots self.g=open_sgf(self.filename) self.move_zero=self.g.get_root() nb_moves=get_moves_number(self.move_zero) self.nb_moves=nb_moves row=0 Label(self,text="").grid(row=row,column=1) row+=1 Label(self,text=_("Bot to use for analysis:")).grid(row=row,column=1,sticky=N+W) #value={"slow":" (%s)"%_("Slow profile"),"fast":" (%s)"%_("Fast profile")} bot_names=[bot['name']+" - "+bot['profile'] for bot in bots] self.bot_selection=StringVar() if not bot_names: Label(self,text=_("There is no bot configured in Settings")).grid(row=row,column=2,sticky=W) else: botOptionMenu = apply(OptionMenu,(self, self.bot_selection)+tuple(bot_names)) botOptionMenu.config(width=20) botOptionMenu.grid(row=row,column=2,sticky=W) self.bot_selection.set(bot_names[0]) analyser=grp_config.get("Analysis","analyser") if analyser in bot_names: self.bot_selection.set(analyser) row+=1 Label(self,text="").grid(row=row,column=1) row+=1 variation_label_widget=Label(self,text=_("Select variation to be analysed")) self.leaves=get_all_sgf_leaves(self.move_zero) self.variation_selection=StringVar() self.variation_selection.trace("w", self.variation_changed) options=[] v=1 for unused,deep in self.leaves: options.append(_("Variation %i (%i moves)")%(v,deep)) v+=1 self.variation_selection.set(options[0]) variation_menu_widget=apply(OptionMenu,(self,self.variation_selection)+tuple(options)) existing_variations = StringVar() existing_variations.set("remove_everything") if node_has(self.move_zero,"RSGF"): existing_variations.set("keep") row+=10 Label(self,text=_("This analysis will be performed on an already analysed SGF file.")).grid(row=row,column=1,columnspan=2,sticky=W) row+=1 Label(self,text=_("What to do with the existing variations?")).grid(row=row,column=1,columnspan=2,sticky=W) row+=1 d1=Radiobutton(self,text=_("Keep existing variations"),variable=existing_variations, value="keep") d1.grid(row=row,column=1,sticky=W) row+=1 d2=Radiobutton(self,text=_("Replace existing variations"),variable=existing_variations, value="replace") d2.grid(row=row,column=1,sticky=W) else: variation_label_widget.grid(row=row,column=1,sticky=W) variation_menu_widget.grid(row=row,column=2,sticky=W) self.rsgf_filename=".".join(self.filename.split(".")[:-1])+".rsgf" row+=1 Label(self,text="").grid(row=row,column=1) row+=1 Label(self,text=_("Select moves to be analysed")).grid(row=row,column=1,sticky=W) row+=1 s = StringVar() s.set("all") self.r1=Radiobutton(self,text=_("Analyse all %i moves")%nb_moves,variable=s, value="all") self.r1.grid(row=row,column=1,sticky=W) self.after(0,self.r1.select) row+=1 r2=Radiobutton(self,text=_("Analyse only those moves:"),variable=s, value="only") r2.grid(row=row,column=1,sticky=W) only_entry=Entry(self) only_entry.bind("<Button-1>", lambda e: r2.select()) only_entry.grid(row=row,column=2,sticky=W) only_entry.delete(0, END) if nb_moves>0: only_entry.insert(0, "1-"+str(nb_moves)) row+=3 Label(self,text="").grid(row=row,column=1) row+=1 Label(self,text=_("Select colors to be analysed")).grid(row=row,column=1,sticky=W) c = StringVar() c.set("both") row+=1 c0=Radiobutton(self,text=_("Black & white"),variable=c, value="both") c0.grid(row=row,column=1,sticky=W) self.after(0,c0.select) if node_has(self.move_zero,'PB'): black_player=node_get(self.move_zero,'PB') if black_player.lower().strip() in ['black','']: black_player='' else: black_player=' ('+black_player+')' else: black_player='' if node_has(self.move_zero,'PW'): white_player=node_get(self.move_zero,'PW') if white_player.lower().strip() in ['white','']: white_player='' else: white_player=' ('+white_player+')' else: white_player='' row+=1 c1=Radiobutton(self,text=_("Black only")+black_player,variable=c, value="black") c1.grid(row=row,column=1,sticky=W) row+=1 c2=Radiobutton(self,text=_("White only")+white_player,variable=c, value="white") c2.grid(row=row,column=1,sticky=W) row+=10 Label(self,text="").grid(row=row,column=1) row+=1 Label(self,text=_("Confirm the value of komi")).grid(row=row,column=1,sticky=W) komi_entry=Entry(self) komi_entry.grid(row=row,column=2,sticky=W) komi_entry.delete(0, END) try: komi=self.g.get_komi() komi_entry.insert(0, str(komi)) except Exception, e: log("Error while reading komi value, please check:\n"+unicode(e)) show_error(_("Error while reading komi value, please check:")+"\n"+unicode(e),parent=self) komi_entry.insert(0, "0") row+=10 Label(self,text="").grid(row=row,column=1) row+=1 Label(self,text=_("Stop the analysis if the bot resigns")).grid(row=row,column=1,sticky=W) StopAtFirstResign = BooleanVar(value=grp_config.getboolean('Analysis', 'StopAtFirstResign')) StopAtFirstResignCheckbutton=Checkbutton(self, text="", variable=StopAtFirstResign,onvalue=True,offvalue=False) StopAtFirstResignCheckbutton.grid(row=row,column=2,sticky=W) StopAtFirstResignCheckbutton.var=StopAtFirstResign self.StopAtFirstResign=StopAtFirstResign row+=10 Label(self,text="").grid(row=row,column=1) row+=1 start_button=Button(self,text=_("Start"),command=self.start) start_button.grid(row=row,column=2,sticky=E) if not bot_names: start_button.config(state="disabled") self.mode=s self.color=c self.existing_variations=existing_variations self.nb_moves=nb_moves self.only_entry=only_entry self.komi_entry=komi_entry self.focus() self.parent.focus() def variation_changed(self,*unused): log("variation changed!",self.variation_selection.get()) try: self.after(0,self.r1.select) variation=int(self.variation_selection.get().split(" ")[1])-1 deep=self.leaves[variation][1] self.only_entry.delete(0, END) if deep>0: self.only_entry.insert(0, "1-"+str(deep)) self.r1.config(text=_("Analyse all %i moves")%deep) self.nb_moves=deep except: pass def close(self): self.destroy() self.parent.remove_popup(self) def start(self): #if self.nb_moves==0: # show_error(_("This variation is empty (0 move), the analysis cannot be performed!"),parent=self) # return try: komi=float(self.komi_entry.get()) except: show_error(_("Incorrect value for komi (%s), please double check.")%self.komi_entry.get(),parent=self) return if self.bots!=None: bot=self.bot_selection.get() log("bot selection:",bot) bot={bot['name']+" - "+bot['profile']:bot for bot in self.bots}[bot] RunAnalysis=bot['runanalysis'] if self.mode.get()=="all": intervals="all moves" move_selection=range(1,self.nb_moves+1) else: selection = self.only_entry.get() intervals="moves "+selection move_selection=check_selection(selection,self.nb_moves) if move_selection==False: show_error(_("Could not make sense of the moves range.")+"\n"+_("Please indicate one or more move intervals (e.g. \"10-20, 40,50-51,63,67\")"),parent=self) return if self.color.get()=="black": intervals+=" (black only)" log("black only") elif self.color.get()=="white": intervals+=" (white only)" log("white only") else: intervals+=" (both colors)" move_selection=check_selection_for_color(self.move_zero,move_selection,self.color.get()) log("========= move selection") log(move_selection) log("========= variation") variation=int(self.variation_selection.get().split(" ")[1])-1 log(variation) grp_config.set("Analysis","analyser",self.bot_selection.get()) grp_config.set("Analysis","StopAtFirstResign",self.StopAtFirstResign.get()) popup=RunAnalysis(self.parent,(self.filename,self.rsgf_filename),move_selection,intervals,variation,komi,bot,self.existing_variations.get()) self.parent.add_popup(popup) self.close() import Queue import time import ttk def guess_color_to_play(move_zero,move_number): one_move=go_to_move(move_zero,move_number) if one_move==False: previous_move_color=guess_color_to_play(move_zero,move_number-1) if previous_move_color.lower()=='b': return "w" else: return "b" player_color,unused=one_move.get_move() if player_color != None: return player_color if one_move is move_zero: if node_has(move_zero,"PL"): if node_get(move_zero,"PL").lower()=="b": return "w" if node_get(move_zero,"PL").lower()=="w": return "b" else: return "w" previous_move_color=guess_color_to_play(move_zero,move_number-1) if previous_move_color.lower()=='b': return "w" else: return "b" class LiveAnalysisBase(): def __init__(self,g,rsgf_filename,profile): self.g=g self.rsgf_filename=rsgf_filename self.profile=profile self.bot=self.initialize_bot() self.update_queue=Queue.PriorityQueue() self.label_queue=Queue.Queue() self.best_moves_queue=Queue.Queue() self.move_zero=self.g.get_root() self.no_variation_if_same_move=True size=self.g.get_size() log("size of the tree:", size) self.size=size self.no_variation_if_same_move=grp_config.getboolean('Analysis', 'NoVariationIfSameMove') self.maxvariations=grp_config.getint("Analysis", "maxvariations") self.stop_at_first_resign=False self.cpu_lock=threading.Lock() def start(self): threading.Thread(target=self.run_live_analysis).start() def play(self,gtp_color,gtp_move): if gtp_color=='w': self.bot.place_white(gtp_move) else: self.bot.place_black(gtp_move) def undo(self): self.bot.undo() def run_live_analysis(self): self.current_move=1 wait=0 while 1: while wait>0: time.sleep(0.1) wait-=0.1 try: priority,msg=self.update_queue.get(False) if priority<1: log("Analyser received a high priority message") wait=0 self.update_queue.put((priority,msg)) except: continue if not self.cpu_lock.acquire(False): time.sleep(2) #let's wait just enough time in case human player already has a move to play continue try: priority,msg=self.update_queue.get(False) except: self.cpu_lock.release() time.sleep(.5) continue if msg==None: log("Leaving the analysis") self.cpu_lock.release() return if msg=="wait": log("Analyser iddle for five seconds") self.cpu_lock.release() wait=5 continue if type(msg)==type("undo xxx"): move_to_undo=int(msg.split()[1]) log("received undo msg for move",move_to_undo,"and beyong") log("GTP bot is currently at move",len(self.bot.history)) while len(self.bot.history)>=move_to_undo: log("Undoing move",len(self.bot.history),"through GTP") self.undo() self.current_move-=1 log("Deleting the SGF branch") parent=go_to_move(self.move_zero,move_to_undo-1) new_branch=parent[0] old_branch=parent[1] for p in ["ES","CBM","BWWR","VNWR", "MCWR","UBS","LBS","C"]: if node_has(old_branch,p): node_set(new_branch,p,node_get(old_branch,p)) old_branch.delete() write_rsgf(self.rsgf_filename,self.g) self.cpu_lock.release() self.update_queue.put((0,"wait")) self.best_moves_queue.put((priority,msg))#sending echo continue log("Analyser received msg to analyse move",msg) while msg>self.current_move: log("Analyser currently at move",self.current_move) log("So asking "+self.bot.bot_name+" to play the game move",self.current_move) one_move=go_to_move(self.move_zero,self.current_move) player_color,player_move=one_move.get_move() log("game move",self.current_move,"is",player_color,"at",player_move) if player_color in ('w',"W"): log("white at",ij2gtp(player_move)) self.play("w",ij2gtp(player_move)) else: log("black at",ij2gtp(player_move)) self.play("b",ij2gtp(player_move)) self.current_move+=1 log("Analyser is currently at move",self.current_move) self.label_queue.put(self.current_move) log("starting analysis of move",self.current_move) answer=self.run_analysis(self.current_move) log("Analyser best move: move %i at %s"%(self.current_move,answer)) self.best_moves_queue.put([self.current_move,answer]) try: game_move=go_to_move(self.move_zero,self.current_move).get_move()[1] log("Game move:",game_move) if game_move: if self.no_variation_if_same_move: if ij2gtp(game_move)==answer: log("Bot move and game move are the same ("+answer+"), removing variations for this move") parent=go_to_move(self.move_zero,self.current_move-1) for child in parent[1:]: child.delete() except: #what could possibly go wrong with this? pass if self.update_queue.empty(): self.label_queue.put("") write_rsgf(self.rsgf_filename,self.g) self.cpu_lock.release() #self.current_move+=1 time.sleep(.1) #enought time for Live analysis to grap the lock class RunAnalysisBase(Toplevel): def __init__(self,parent,filenames,move_range,intervals,variation,komi,profile,existing_variations="remove_everything"): if parent!="no-gui": Toplevel.__init__(self,parent) self.parent=parent self.filename=filenames[0] self.rsgf_filename=filenames[1] self.move_range=move_range self.update_queue=Queue.Queue(1) self.intervals=intervals self.variation=variation self.komi=komi self.profile=profile self.g=None self.move_zero=None self.current_move=None self.time_per_move=None self.existing_variations=existing_variations self.no_variation_if_same_move=grp_config.getboolean('Analysis', 'NoVariationIfSameMove') self.error=None try: self.g=open_sgf(self.filename) self.move_zero=self.g.get_root() self.max_move=get_moves_number(self.move_zero) if existing_variations=="remove_everything": leaves=get_all_sgf_leaves(self.g.get_root()) log("keeping only variation",self.variation) keep_only_one_leaf(leaves[self.variation][0]) else: log("analysis will be performed on first variation") if existing_variations=="keep": move=1 log("checking for moves already analysed") already_analysed=[] while move<=self.max_move: if move in self.move_range: node=go_to_move(self.move_zero,move) if len(node.parent)>1: already_analysed.append(move) move+=1 log("The following moves are already analysed and will be skipped") log(already_analysed) for move in already_analysed: self.move_range.remove(move) if not self.move_range: self.move_range=["empty"] size=self.g.get_size() log("size of the tree:", size) self.size=size log("Setting new komi") node_set(self.g.get_root(),"KM",self.komi) except Exception,e: self.error=unicode(e) self.abort() return try: self.bot=self.initialize_bot() except Exception,e: self.error=_("Error while initializing the GTP bot:")+"\n"+unicode(e) self.abort() return if not self.bot: return self.total_done=0 if parent!="no-gui": try: self.initialize_UI() except Exception,e: self.error=_("Error while initializing the graphical interface:")+"\n"+unicode(e) self.abort() return self.root.after(500,self.follow_analysis) first_comment=_("Analysis by GoReviewPartner") first_comment+="\n"+_("Bot")+(": %s/%s"%(self.bot.bot_name,self.bot.bot_version)) first_comment+="\n"+_("Komi")+(": %0.1f"%self.komi) first_comment+="\n"+_("Intervals")+(": %s"%self.intervals) if grp_config.getboolean('Analysis', 'SaveCommandLine'): first_comment+="\n"+(_("Command line")+": %s"%self.bot.command_line) first_comment+="\n" node_set(self.move_zero,"RSGF",first_comment) node_set(self.move_zero,"BOT",self.bot.bot_name) node_set(self.move_zero,"BOTV",self.bot.bot_version) self.maxvariations=grp_config.getint("Analysis", "maxvariations") try: if grp_config.getboolean('Analysis', 'StopAtFirstResign'): log("Stop_At_First_Resign is ON") self.stop_at_first_resign=True else: self.stop_at_first_resign=False log("Stop_At_First_Resign is OFF") except: self.stop_at_first_resign=False log("Stop_At_First_Resign is OFF") #when the game last move is not pass or resign #then let's add a pass move and extand the analysis #only if the analysis is part of the last move if self.max_move in self.move_range: last_move=go_to_move(self.move_zero,self.max_move) if last_move.get_move()[1]: self.move_range.append(max(self.move_range)+1) self.g.extend_main_sequence() self.max_move+=1 self.completed=False if parent=="no-gui": self.run_all_analysis() else: threading.Thread(target=self.run_all_analysis).start() def initialize_bot(self): pass def run_analysis(self,current_move): log("Analysis of move",current_move) ################################################# ##### here is the place to perform analysis ##### ################################################# log("Analysis for this move is completed") def play(self,gtp_color,gtp_move): if gtp_color=='w': self.bot.place_white(gtp_move) else: self.bot.place_black(gtp_move) def run_all_analysis(self): self.current_move=1 while self.current_move<=self.max_move: answer="" if self.current_move in self.move_range: parent=go_to_move(self.move_zero,self.current_move-1) if len(parent)>1: log("Removing existing",len(parent)-1,"variations") for other_leaf in parent[1:]: other_leaf.delete() answer=self.run_analysis(self.current_move) self.total_done+=1 write_rsgf(self.rsgf_filename,self.g) log("For this position,",self.bot.bot_name,"would play:",answer) log("Analysis for this move is completed") elif self.move_range: log("Move",self.current_move,"not in the list of moves to be analysed, skipping") try: game_move=go_to_move(self.move_zero,self.current_move).get_move()[1] if game_move: if self.no_variation_if_same_move: if ij2gtp(game_move)==answer: log("Bot move and game move are the same ("+answer+"), removing variations for this move") parent=go_to_move(self.move_zero,self.current_move-1) for child in parent[1:]: child.delete() write_rsgf(self.rsgf_filename,self.g) except: #what could possibly go wrong with this? pass if (answer=="RESIGN") and (self.stop_at_first_resign==True): log("") log("The analysis will stop now") log("") self.move_range=[] #the bot has proposed to resign, and resign_at_first_stop is ON elif self.move_range: one_move=go_to_move(self.move_zero,self.current_move) player_color,player_move=one_move.get_move() if player_color in ('w',"W"): log("now asking "+self.bot.bot_name+" to play the game move: white at",ij2gtp(player_move)) self.play('w',ij2gtp(player_move)) else: log("now asking "+self.bot.bot_name+" to play the game move: black at",ij2gtp(player_move)) self.play('b',ij2gtp(player_move)) self.current_move+=1 if self.parent!="no-gui": self.update_queue.put(self.total_done) return def abort(self): try: self.lab1.config(text=_("Aborted")) self.lab2.config(text="") except: pass log("Leaving follow_anlysis()") show_error(_("Analysis aborted:")+"\n\n"+self.error,parent=self) def follow_analysis(self): if self.error: self.abort() return msg=None try: msg=self.update_queue.get(False) if self.total_done>0: self.time_per_move=1.0*(time.time()-self.t0)/self.total_done+1 #log(self.total_done,"move(s) analysed in",int(10*(time.time()-self.t0))/10.,"secondes =>",int(10*self.time_per_move)/10.,"s/m") #log("self.time_per_move=",(time.time()-self.t0),"/",self.total_done,"=",self.time_per_move) remaining_s=int((len(self.move_range)-self.total_done)*self.time_per_move) remaining_h=remaining_s/3600 remaining_s=remaining_s-3600*remaining_h remaining_m=remaining_s/60 remaining_s=remaining_s-60*remaining_m if self.time_per_move!=0: self.lab2.config(text=_("Remaining time: %ih, %im, %is")%(remaining_h,remaining_m,remaining_s)) self.lab1.config(text=_("Currently at move %i/%i")%(self.current_move,self.max_move)) self.pb.update_idletasks() if msg==1:#msg contains the value of self.total_done if not self.review_button: self.review_button=Button(self.right_frame,text=_("Start the review"),command=self.start_review) self.review_button.pack() except: pass if self.current_move<=self.max_move: if msg==None: self.parent.after(250,self.follow_analysis) else: self.pb.step() self.parent.after(10,self.follow_analysis) else: self.end_of_analysis() def end_of_analysis(self): self.lab1.config(text=_("Completed")) self.lab2.config(text="") self.pb["maximum"] = 100 self.pb["value"] = 100 def start_review(self): import dual_view app=self.parent popup=dual_view.DualView(app,self.rsgf_filename) self.parent.add_popup(popup) if (self.pb["maximum"] == 100) and (self.pb["value"] == 100): self.close() def terminate_bot(self): try: log("killing",self.bot.bot_name) self.bot.close() except Exception,e: log(e) def remove_app(self): log("RunAnalysis beeing closed") self.lab2.config(text=_("Now closing, please wait...")) self.update_idletasks() try: self.terminate_bot() except: pass self.destroy() def close(self): self.remove_app() self.destroy() self.parent.remove_popup(self) log("RunAnalysis closed") self.completed=True def initialize_UI(self): if not self.move_range: self.move_range=range(1,self.max_move+1) root = self root.title('GoReviewPartner') root.protocol("WM_DELETE_WINDOW", self.close) bg=root.cget("background") logo = Canvas(root,bg=bg,width=5,height=5) logo.pack(fill=BOTH,expand=1,side=LEFT) logo.bind("<Configure>",lambda e: draw_logo(logo,e,"vertical")) right_frame=Frame(root) right_frame.pack(side=LEFT,padx=5, pady=5) self.right_frame=right_frame Label(right_frame,text=_("Analysis of: %s")%os.path.basename(self.filename)).pack() self.lab1=Label(right_frame) self.lab1.pack() self.lab2=Label(right_frame) self.lab2.pack() self.lab1.config(text=_("Currently at move %i/%i")%(1,self.max_move)) self.pb = ttk.Progressbar(right_frame, orient="horizontal", length=250,maximum=self.max_move+1, mode="determinate") self.pb.pack() try: write_rsgf(self.rsgf_filename,self.g) except Exception,e: self.lab1.config(text=_("Aborted")) self.lab2.config(text="") raise e self.t0=time.time() self.root=root self.review_button=None class BotOpenMove(): def __init__(self,sgf_g,profile): self.name='Bot' self.bot=None self.okbot=False self.sgf_g=sgf_g self.profile=profile def start(self,silentfail=True): try: result=self.my_starting_procedure(self.sgf_g,profile=self.profile,silentfail=silentfail) if result: self.bot=result self.okbot=True else: self.okbot=False except Exception, e: log("Could not launch "+self.name) log(e) self.okbot=False return def undo(self): if self.okbot: self.bot.undo() def place(self,move,color): if self.okbot: if not self.bot.place(move,color): #self.config(state='disabled') return False return True def quick_evaluation(self,color): return self.bot.quick_evaluation(color) def click(self,color): log(self.name,"play") n0=time.time() if color==1: move=self.bot.play_black() else: move=self.bot.play_white() log("move=",move,"in",time.time()-n0,"s") return move def close(self): if self.okbot: log("killing",self.name) self.bot.close() def bot_starting_procedure(bot_name,bot_gtp_name,bot_gtp,sgf_g,profile,silentfail=False): log("Bot starting procedure started with profile =",profile["profile"]) log("\tbot name:",bot_name) log("\tbot gtp name",bot_gtp_name) command_entry=profile["command"] parameters_entry=profile["parameters"] size=sgf_g.get_size() try: log("Starting "+bot_name+"...") try: #bot_command_line=[grp_config.get(bot_name, command_entry)]+grp_config.get(bot_name, parameters_entry).split() bot_command_line=[command_entry]+parameters_entry.split() bot=bot_gtp(bot_command_line) except Exception,e: raise GRPException((_("Could not run %s using the command from config.ini file:")%bot_name)+"\n"+command_entry+" "+parameters_entry+"\n"+unicode(e)) log(bot_name+" started") log(bot_name+" identification through GTP...") try: answer=bot.name() except Exception, e: raise GRPException((_("%s did not reply as expected to the GTP name command:")%bot_name)+"\n"+unicode(e)) if bot_gtp_name!='GtpBot': if answer!=bot_gtp_name: raise GRPException((_("%s did not identify itself as expected:")%bot_name)+"\n'"+bot_gtp_name+"' != '"+answer+"'") else: bot_gtp_name=answer log(bot_name+" identified itself properly") log("Checking version through GTP...") try: bot_version=bot.version() except Exception, e: raise GRPException((_("%s did not reply as expected to the GTP version command:")%bot_name)+"\n"+unicode(e)) log("Version: "+bot_version) log("Setting goban size as "+str(size)+"x"+str(size)) try: ok=bot.boardsize(size) except: raise GRPException((_("Could not set the goboard size using GTP command. Check that %s is running in GTP mode.")%bot_name)) if not ok: raise GRPException(_("%s rejected this board size (%ix%i)")%(bot_name,size,size)) log("Clearing the board") bot.reset() log("Checking for existing stones or handicap stones on the board") gameroot=sgf_g.get_root() if node_has(gameroot,"HA"): nb_handicap=node_get(gameroot,"HA") log("The SGF indicates",nb_handicap,"stone(s)") else: nb_handicap=0 log("The SGF does not indicate handicap stone") #import pdb; pdb.set_trace() board, unused = sgf_moves.get_setup_and_moves(sgf_g) nb_occupied_points=len(board.list_occupied_points()) log("The SGF indicates",nb_occupied_points,"occupied point(s)") free_handicap_black_stones_positions=[] already_played_black_stones_position=[] already_played_white_stones_position=[] for color, move in board.list_occupied_points(): if move != None: row, col = move move=ij2gtp((row,col)) if color.lower()=='b': if nb_handicap>0: free_handicap_black_stones_positions.append(move) nb_handicap-=1 else: already_played_black_stones_position.append(move) else: already_played_white_stones_position.append(move) if len(free_handicap_black_stones_positions)>0: log("Setting handicap stones at"," ".join(free_handicap_black_stones_positions)) bot.set_free_handicap(free_handicap_black_stones_positions) for stone in already_played_black_stones_position: log("Adding a black stone at",stone) bot.place_black(stone) for stone in already_played_white_stones_position: log("Adding a white stone at",stone) bot.place_white(stone) log("Setting komi at",sgf_g.get_komi()) bot.komi(sgf_g.get_komi()) log(bot_name+" initialization completed") bot.bot_name=bot_gtp_name bot.bot_version=bot_version except Exception,e: if silentfail: log(e) else: show_error(unicode(e)) return False return bot def draw_logo(logo,event=None,stretch="horizontal"): for item in logo.find_all(): logo.delete(item) width=event.width height=event.height if stretch=="horizontal": logo.config(height=width) else: logo.config(width=height) border=0.1 w=width*(1-2*border) b=width*border for u in [1/4.,2/4.,3/4.]: for v in [1/4.,2/4.,3/4.]: x1=b+w*(u-1/8.) y1=b+w*(v-1/8.) x2=b+w*(u+1/8.) y2=b+w*(v+1/8.) logo.create_oval(x1, y1, x2, y2, fill="#ADC5E7", outline="") for k in [1/4.,2/4.,3/4.]: x1=b+k*w y1=b x2=x1 y2=b+w logo.create_line(x1, y1, x2, y2, width=w*7/318., fill="#21409A") logo.create_line(y1, x1, y2, x2, width=w*7/318., fill="#21409A") for u,v in [(2/4.,1/4.),(3/4.,2/4.),(1/4.,3/4.),(2/4.,3/4.),(3/4.,3/4.)]: x1=b+w*(u-1/8.) y1=b+w*(v-1/8.) x2=b+w*(u+1/8.) y2=b+w*(v+1/8.) logo.create_oval(x1, y1, x2, y2, fill="black", outline="") import __main__ try: usage="usage: python "+__main__.__file__+" [--range=<range>] [--color=<both|black|white>] [--komi=<komi>] [--variation=<variation>] [--profil=<\"profil\">] [--no-gui] <sgf file1> <sgf file2> <sgf file3>" except: log("Command line features are disabled") usage="" def parse_command_line(filename,argv): g=open_sgf(filename) move_zero=g.get_root() leaves=get_all_sgf_leaves(move_zero) found=False #argv=[(unicode(p,errors="replace"),unicode(v,errors="replace")) for p,v in argv] #ok, this is maybe overkill... for p,v in argv: if p=="--variation": try: variation=int(v) found=True except: show_error("Wrong variation parameter\n"+usage) sys.exit() if not found: variation=1 log("Variation:",variation) if variation<1: show_error("Wrong variation parameter, it must be a positive integer") sys.exit() if variation>len(leaves): show_error("Wrong variation parameter, this SGF file has only "+str(len(leaves))+" variation(s)") sys.exit() nb_moves=leaves[variation-1][1] log("Moves for this variation:",nb_moves) if nb_moves==0: show_error("This variation is empty (0 move), the analysis cannot be performed!") sys.exit() #nb_moves=get_moves_number(move_zero) found=False for p,v in argv: if p=="--range": if v=="": show_error("Wrong range parameter\n"+usage) sys.exit() elif v=="all": break else: intervals=v log("Range:",v) move_selection=check_selection(v.replace('"',''),nb_moves) if move_selection==False: show_error("Wrong range parameter\n"+usage) sys.exit() found=True break if not found: move_selection=range(1,nb_moves+1) intervals="all moves" log("Range: all") found=False for p,v in argv: if p=="--color": if v in ["black","white"]: log("Color:",v) move_selection=check_selection_for_color(move_zero,move_selection,v) intervals+=" ("+v+"only)" found=True break elif v=="both": break else: show_error("Wrong color parameter\n"+usage) sys.exit() if not found: intervals+=" (both colors)" log("Color: both") found=False for p,v in argv: if p=="--komi": try: komi=float(v) found=True except: show_error("Wrong komi parameter\n"+usage) sys.exit() if not found: try: komi=g.get_komi() except Exception, e: msg="Error while reading komi value, please check:\n"+unicode(e) msg+="\nPlease indicate komi using --komi parameter" log(msg) show_error(msg) sys.exit() log("Komi:",komi) found=False for p,v in argv: if p=="--profile": profile=v found=True if not found: profile=None log("Profile:",profile) nogui=False for p,v in argv: if p=="--no-gui": nogui=True break return move_selection,intervals,variation,komi,nogui,profile # from http://www.py2exe.org/index.cgi/WhereAmI def we_are_frozen(): """Returns whether we are frozen via py2exe. This will affect how we find out where we are located.""" return hasattr(sys, "frozen") def module_path(): """ This will get us the program's directory, even if we are frozen using py2exe""" if we_are_frozen(): log("Apparently running from the executable.") return os.path.dirname(unicode(sys.executable, sys.getfilesystemencoding( ))) return os.path.dirname(unicode(__file__, sys.getfilesystemencoding( ))) try: pathname=module_path() except: pathname=os.path.dirname(__file__) log('GRP path:', os.path.abspath(pathname)) config_file=os.path.join(os.path.abspath(pathname),"config.ini") log('Config file:', config_file) import ConfigParser log("Checking availability of config file") conf = ConfigParser.ConfigParser() try: conf.readfp(codecs.open(config_file,"r","utf-8")) except Exception, e: show_error("Could not open the config file of Go Review Partner"+"\n"+unicode(e)) #this cannot be translated sys.exit() class MyConfig(): def __init__(self,config_file): self.config = ConfigParser.ConfigParser() self.config.read(config_file) self.config_file=config_file self.default_values={} self.default_values["general"]={} self.default_values["general"]["language"]="" self.default_values["general"]["sgffolder"]="" self.default_values["general"]["rsgffolder"]="" self.default_values["general"]["pngfolder"]="" self.default_values["general"]["livefolder"]="" self.default_values["general"]["stonesound"]="" self.default_values["analysis"]={} self.default_values["analysis"]["maxvariations"]="26" self.default_values["analysis"]["savecommandline"]="False" self.default_values["analysis"]["stopatfirstresign"]="False" self.default_values["analysis"]["novariationifsamemove"]="False" self.default_values["analysis"]["analyser"]="" self.default_values["review"]={} self.default_values["review"]["fuzzystoneplacement"]="0.2" self.default_values["review"]["realgamesequencedeepness"]="5" self.default_values["review"]["leftgobanratio"]="0.4" self.default_values["review"]["rightgobanratio"]="0.4" self.default_values["review"]["rightpanelratio"]="0.4" self.default_values["review"]["opengobanratio"]="0.4" self.default_values["review"]["maxvariations"]="26" self.default_values["review"]["variationscoloring"]="blue_for_winning" self.default_values["review"]["variationslabel"]="letter" self.default_values["review"]["invertedmousewheel"]="False" self.default_values["review"]["lastgraph"]="" self.default_values["review"]["yellowbar"]="#F39C12" self.default_values["review"]["lastbot"]="" self.default_values["review"]["lastmap"]="" self.default_values["review"]["oneortwopanels"]="1" self.default_values["live"]={} self.default_values["live"]["livegobanratio"]="0.4" self.default_values["live"]["size"]="19" self.default_values["live"]["komi"]="7.5" self.default_values["live"]["handicap"]="0" self.default_values["live"]["nooverlap"]="False" self.default_values["live"]["analyser"]="" self.default_values["live"]["black"]="" self.default_values["live"]["white"]="" self.default_values["live"]["thinkbeforeplaying"]="0" def set(self, section, key, value): if type(value) in (type(1), type(0.5), type(True)): value=unicode(value) if type(section)!=type(u"abc"): print section, "Warning: A non utf section string sent to my config:",section if type(key)!=type(u"abc"): print key,"A non utf key string sent to my config:", key if type(value)!=type(u"abc"): print value,"A non utf value string sent to my config:",value section=unicode(section) key=unicode(key) value=unicode(value) self.config.set(section.encode("utf-8"),key.encode("utf-8"),value.encode("utf-8")) self.config.write(open(self.config_file,"w")) def get(self,section,key): try: value=self.config.get(section,key) value=value.decode("utf-8") except: log("Could not read",str(section)+"/"+str(key),"from the config file") log("Using default value") value=self.default_values[section.lower()][key.lower()] self.add_entry(section,key,value) return value def getint(self,section,key): try: value=self.config.getint(section,key) except: log("Could not read",str(section)+"/"+str(key),"from the config file") log("Using default value") value=self.default_values[section.lower()][key.lower()] self.add_entry(section,key,value) value=self.config.getint(section,key) return value def getfloat(self,section,key): try: value=self.config.getfloat(section,key) except: log("Could not read",str(section)+"/"+str(key),"from the config file") log("Using default value") value=self.default_values[section.lower()][key.lower()] self.add_entry(section,key,value) value=self.config.getfloat(section,key) return value def getboolean(self,section,key): try: value=self.config.getboolean(section,key) except: log("Could not read",str(section)+"/"+str(key),"from the config file") log("Using default value") value=self.default_values[section.lower()][key.lower()] self.add_entry(section,key,value) value=self.config.getboolean(section,key) return value def add_entry(self,section,key,value): #normally section/key/value should all be unicode here #but just to be sure: section=unicode(section) key=unicode(key) value=unicode(value) #then, let's turn every thing in str section=section.encode("utf-8") key=key.encode("utf-8") value=value.encode("utf-8") if not self.config.has_section(section): log("Adding section",section,"in config file") self.config.add_section(section) log("Setting",section,"/",key,"in the config file") self.config.set(section,key,value) self.config.write(open(self.config_file,"w")) def get_sections(self): return [section.decode("utf-8") for section in self.config.sections()] def get_options(self,section): return [option.decode("utf-8") for option in self.config.options(section)] def remove_section(self,section): result=self.config.remove_section(section) self.config.write(open(self.config_file,"w")) return result grp_config=MyConfig(config_file) log("Reading language setting from config file") lang=grp_config.get("General","Language") available_translations={"en": "English", "fr" : "Français", "de" : "Deutsch", "kr" : "한국어", "zh": "中文", "pl": "Polski", "ru": "Русский"} if not lang: log("No language setting in the config file") log("System language detection:") import locale try: lang=locale.getdefaultlocale()[0].split('_')[0] log("System language:",lang,"("+locale.getdefaultlocale()[0]+")") if lang in available_translations: log("There is a translation available for lang="+lang) else: log("No translation available for lang="+lang) log("Falling back on lang=en") lang="en" except Exception, e: log("Could not determine the system language") log(e) log("Falling back to english") lang="en" log("Saving the lang parameter in config.ini") grp_config.set("General","Language",lang) else: if lang in available_translations: log("lang="+lang) else: log("Unkonwn language setting in config.ini (lang="+lang+")") log("Falling back on lang=en") lang="en" log("Saving the lang parameter in config.ini") grp_config.set("General","Language",lang) translations={} def prepare_translations(): global translations if lang=='en': return data_file_url=os.path.join(os.path.abspath(pathname),"translations",lang+".po") log("Loading translation file:",data_file_url) data_file = codecs.open(data_file_url,"r","utf-8") translation_data=data_file.read() data_file.close() entry="" translation="" for line in translation_data.split('\n'): line=line.strip() key="msgid" if line[:len(key)+2]==key+' "': entry=line[len(key)+2:-1] translation="" key="msgstr" if line[:len(key)+2]==key+' "': translation=line[len(key)+2:-1] translation=translation.replace("\\\"","\"") if len(entry)>0 and len(translation)>0: translations[entry]=translation entry="" translation="" prepare_translations() def _(txt=None): global translations if not translations: return unicode(txt) if translations.has_key(txt): return translations[txt] return unicode(txt) def batch_analysis(app,batch): #there appears to be a Tk 8.6 regression bug that leads to random "Tcl_AsyncDelete: async handler deleted by the wrong thread Abandon (core dumped)" #this happens when app=Tk() is detroyed after one analysis, and a new one is created for the next analysis #this bug is sidestepped by recycling the same app=Tk() for all analysis #see also: http://learning-python.com/python-changes-2014-plus.html#s35E try: if len(batch)==0: app.remove_popup(app) return app.add_popup(app) one_analysis=batch[0] if len(one_analysis)==1: if one_analysis[0].completed==False: app.after(1000,lambda: batch_analysis(app,batch)) else: batch=batch[1:] app.after(1,lambda: batch_analysis(app,batch)) else: run,filename,move_selection,intervals,variation,komi,profil=one_analysis log("File to analyse:",filename[0]) log("Output file:",filename[1]) popup=run(app,filename,move_selection,intervals,variation,komi,profil) app.add_popup(popup) popup.end_of_analysis=popup.close batch[0]=[popup] app.after(1,lambda: batch_analysis(app,batch)) except Exception, e: log("Batch analysis failed") log(e) app.force_close() def opposite_rate(value): return str(100-float(value[:-1]))+"%" position_data_formating={} position_data_formating["CBM"]=_("For this position, %s would play: %s") position_data_formating["B"]=_("Black to play. In the game, black played %s") position_data_formating["W"]=_("White to play. In the game, white played %s") def format_data(sgf_property,formating,value="",bot="Bot"): txt=formating[sgf_property] #print "formating["+sgf_property+"]",txt try: if sgf_property in ("ES","CBM","BWWR"): txt=txt%(bot,value) except: pass try: if sgf_property in ("B","W","BWWR","PNV","MCWR","VNWR","PLYO","EVAL","RAVE","UBS","LBS"): txt=txt%(value) except: pass try: if sgf_property in ("BKMV",): txt=txt except: pass return txt variation_data_formating={} variation_data_formating["ES"]=_("Score estimation for this variation: %s") variation_data_formating["BWWR"]=_("black/white win probability for this variation: %s") variation_data_formating["BKMV"]=_("Book move") variation_data_formating["PNV"]=_("Policy network value for this variation: %s") variation_data_formating["MCWR"]=_("Monte Carlo win probability for this variation: %s") variation_data_formating["VNWR"]=_("Value network black/white win probability for this variation: %s") variation_data_formating["PLYO"]=_("Number of playouts used to estimate this variation: %s") variation_data_formating["EVAL"]=_("Evaluation for this variation: %s") variation_data_formating["RAVE"]=_("RAVE(x%% : y) for this variation: %s") def save_position_data(node,sgf_property,value): log("WARNING: save_position_data() still in used...") node_set(node,sgf_property,value) def save_variation_data(node,sgf_property,value): log("WARNING: save_variation_data() still in used...") node_set(node,sgf_property,value) class Application(Tk): def __init__(self): Tk.__init__(self) self.popups=[] self.title('GoReviewPartner') try: ico = Image("photo", file="icon.gif") self.tk.call('wm', 'iconphoto', str(self), '-default', ico) except: log("(Could not load the application icon)") self.withdraw() def force_close(self): os._exit(0) def remove_popup(self,popup): log("Removing popup") self.popups.remove(popup) log("Totally",len(self.popups),"popups left") if len(self.popups)==0: try: self.destroy() except: pass time.sleep(2) log("") log("GoReviewPartner is closing") log("Hope you enjoyed the experience!") log("") log("List of contributors") contributors_file_url=os.path.join(os.path.abspath(pathname),"AUTHORS") contributors_file = codecs.open(contributors_file_url,"r","utf-8") contributors=contributors_file.read() contributors_file.close() for line in contributors.split('\n'): if not line: continue if line[0]=="#": continue log("\t",line) log("") log("You are welcome to support GoReviewPartner (bug reports, code fixes, translations, ideas...). If you are interested, get in touch through Github, Reddit, or LifeIn19x19!") if we_are_frozen(): #running from py2exe time.sleep(2) self.force_close() def add_popup(self,popup): if popup not in self.popups: log("Adding new popup") self.popups.append(popup) log("Totally",len(self.popups),"popups") try: if "linux" not in sys.platform: raise Exception("Avoiding wx") import wx wxApp = wx.App(None) def open_all_file(parent,config,filetype): initialdir = grp_config.get(config[0],config[1]) dialog = wx.FileDialog(None,_('Select a file'), defaultDir=initialdir, wildcard=filetype, style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) filename = None if dialog.ShowModal() == wx.ID_OK: filename = dialog.GetPath() dialog.Destroy() if filename: initialdir=os.path.dirname(filename) grp_config.set(config[0],config[1],initialdir) return filename def open_sgf_file(parent=None): wildcard=_("SGF file")+" (*.sgf;*.SGF)|*.sgf;*.SGF;|"+_("Reviewed SGF file")+" (*.rsgf;*.RSGF)|*.rsgf;*.RSGF" return open_all_file(parent,config=("General","sgffolder"),filetype= wildcard) def open_rsgf_file(parent=None): wildcard=_("Reviewed SGF file")+" (*.rsgf;*.RSGF)|*.rsgf;*.RSGF" return open_all_file(parent,config=("General","rsgffolder"),filetype=wildcard) def save_all_file(filename, parent, config, filetype): initialdir = grp_config.get(config[0],config[1]) dialog = wx.FileDialog(None,_('Choose a filename'), defaultDir=initialdir,defaultFile=filename, wildcard=filetype[0]+" "+filetype[1], style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) filename = None if dialog.ShowModal() == wx.ID_OK: filename = dialog.GetPath() dialog.Destroy() if filename: initialdir=os.path.dirname(filename) grp_config.set(config[0],config[1],initialdir) return filename def save_png_file(filename, parent=None): return save_all_file(filename, parent, config=("General","pngfolder"), filetype=(_("PNG image"),"(*.png;*.PNG)|*.png;*.PNG")) def save_live_game(filename, parent=None): return save_all_file(filename, parent, config=("General","livefolder"), filetype=(_("SGF file"),"(*.sgf;*.SGF)|*.sgf;*.SGF")) except Exception, e: print "Could not import the WX GUI library, please double check it is installed:" log(e) log("=> No problem, falling back to tkFileDialog") def open_all_file(parent,config,filetype): import tkFileDialog initialdir = grp_config.get(config[0],config[1]) filename=tkFileDialog.askopenfilename(initialdir=initialdir, parent=parent,title=_("Select a file"),filetypes =filetype ) if filename: initialdir=os.path.dirname(filename) grp_config.set(config[0],config[1],initialdir) filename=unicode(filename) return filename def open_sgf_file(parent=None): return open_all_file(parent,config=("General","sgffolder"),filetype=[(_('SGF file'), '.sgf'),(_('Reviewed SGF file'), '.rsgf')]) def open_rsgf_file(parent=None): return open_all_file(parent,config=("General","rsgffolder"),filetype=[(_('Reviewed SGF file'), '.rsgf')]) def save_all_file(filename, parent,config,filetype): import tkFileDialog initialdir = grp_config.get(config[0],config[1]) filename=tkFileDialog.asksaveasfilename(initialdir=initialdir, parent=parent,title=_('Choose a filename'),filetypes = [(filetype[0], filetype[1])],initialfile=filename) if filename: initialdir=os.path.dirname(filename) grp_config.set(config[0],config[1],initialdir) return filename def save_png_file(filename, parent=None): return save_all_file(filename, parent, config=("General","pngfolder"), filetype=(_('PNG image'), '.png')) def save_live_game(filename, parent=None): return save_all_file(filename, parent, config=("General","livefolder"), filetype=(_('SGF file'), '.sgf')) import mss import mss.tools def canvas2png(goban,filename): top = goban.winfo_rooty() left = goban.winfo_rootx() width = goban.winfo_width() height = goban.winfo_height() try: dim=goban.dim space=goban.space current_tab_id=goban.parent.right_notebook.index("current") goban.parent.right_notebook.select(0) goban.parent.update_idletasks() goban.parent.right_notebook.select(current_tab_id) goban.parent.update_idletasks() top = goban.winfo_rooty() v_center=top+goban.anchor_y+space*(dim+3)/2 h_center=left+goban.anchor_x+space*(dim+3)/2 monitor = {'top': int(v_center-space*(dim+3)/2)+1, 'left': int(h_center-space*(dim+3)/2)+1, 'width': int(space*(dim+3))-2, 'height': int(space*(dim+3))-2} except: monitor = {'top': int(top), 'left': int(left), 'width': int(width), 'height': int(height)} goban.after(500,lambda: screenshot(monitor, filename)) def screenshot(monitor, filename): log("Screenshot!") log(monitor) sct_img = mss.mss().grab(monitor) mss.tools.to_png(sct_img.rgb, sct_img.size, output=filename) def get_variation_comments(one_variation): comments='' for sgf_property in ("BWWR","PNV","MCWR","VNWR","PLYO","EVAL","RAVE","ES","BKMV"): if node_has(one_variation,sgf_property): comments+=format_data(sgf_property,variation_data_formating,node_get(one_variation,sgf_property))+"\n" return comments def get_position_comments(current_move,gameroot): comments="" if current_move==1: if node_has(gameroot,"RSGF"): comments+=node_get(gameroot,"RSGF") if node_has(gameroot,"PB"): comments+=_("Black")+": "+node_get(gameroot,"PB")+"\n" if node_has(gameroot,"PW"): comments+=_("White")+": "+node_get(gameroot,"PW")+"\n" if comments: comments+="\n" comments+=_("Move %i")%current_move game_move_color,game_move=get_node(gameroot,current_move).get_move() if not game_move_color: game_move_color=guess_color_to_play(gameroot,current_move) if game_move_color.lower()=="w": comments+="\n"+(position_data_formating["W"])%ij2gtp(game_move) elif game_move_color.lower()=="b": comments+="\n"+(position_data_formating["B"])%ij2gtp(game_move) node=get_node(gameroot,current_move) if node_has(node,"CBM"): bot=node_get(gameroot,"BOT") comments+="\n"+(position_data_formating["CBM"])%(bot,node_get(node,"CBM")) try: if node_has(node[1],"BKMV"): if node_get(node[1],"BKMV")=="yes": comments+=" ("+variation_data_formating["BKMV"]+")" except: pass try: if node_has(node,"BWWR"): if node_has(node[0],"BWWR"): if node.get_move()[0].lower()=="b": comments+="\n\n"+_("Black win probability:") comments+="\n • "+(_("before %s")%ij2gtp(game_move))+": "+node_get(node,"BWWR").split("/")[0] comments+="\n • "+(_("after %s")%ij2gtp(game_move))+": "+node_get(node[0],"BWWR").split("/")[0] comments+=" (%+.2fpp)"%(float(node_get(node[0],"BWWR").split("%/")[0])-float(node_get(node,"BWWR").split("%/")[0])) else: comments+="\n\n"+_("White win probability:") comments+="\n • "+(_("before %s")%ij2gtp(game_move))+": "+node_get(node,"BWWR").split("/")[1] comments+="\n • "+(_("after %s")%ij2gtp(game_move))+": "+node_get(node[0],"BWWR").split("/")[1] comments+=" (%+.2fpp)"%(float(node_get(node[0],"BWWR").split("%/")[1][:-1])-float(node_get(node,"BWWR").split("%/")[1][:-1])) except: pass try: if node_has(node,"VNWR"): if node_has(node[0],"VNWR"): if node.get_move()[0].lower()=="b": comments+="\n\n"+_("Black Value Network win probability:") comments+="\n • "+(_("before %s")%ij2gtp(game_move))+": "+node_get(node,"VNWR").split("/")[0] comments+="\n • "+(_("after %s")%ij2gtp(game_move))+": "+node_get(node[0],"VNWR").split("/")[0] comments+=" (%+.2fpp)"%(float(node_get(node[0],"VNWR").split("%/")[0])-float(node_get(node,"VNWR").split("%/")[0])) else: comments+="\n\n"+_("White Value Network win probability:") comments+="\n • "+(_("before %s")%ij2gtp(game_move))+": "+node_get(node,"VNWR").split("/")[1] comments+="\n • "+(_("after %s")%ij2gtp(game_move))+": "+node_get(node[0],"VNWR").split("/")[1] comments+=" (%+.2fpp)"%(float(node_get(node[0],"VNWR").split("%/")[1][:-1])-float(node_get(node,"VNWR").split("%/")[1][:-1])) except: pass try: if node_has(node,"MCWR"): if node_has(node[0],"MCWR"): if node.get_move()[0].lower()=="b": comments+="\n\n"+_("Black Monte Carlo win probability:") comments+="\n • "+(_("before %s")%ij2gtp(game_move))+": "+node_get(node,"MCWR").split("/")[0] comments+="\n • "+(_("after %s")%ij2gtp(game_move))+": "+node_get(node[0],"MCWR").split("/")[0] comments+=" (%+.2fpp)"%(float(node_get(node[0],"MCWR").split("%/")[0])-float(node_get(node,"MCWR").split("%/")[0])) else: comments+="\n\n"+_("White Monte Carlo win probability:") comments+="\n • "+(_("before %s")%ij2gtp(game_move))+": "+node_get(node,"MCWR").split("/")[1] comments+="\n • "+(_("after %s")%ij2gtp(game_move))+": "+node_get(node[0],"MCWR").split("/")[1] comments+=" (%+.2fpp)"%(float(node_get(node[0],"MCWR").split("%/")[1][:-1])-float(node_get(node,"MCWR").split("%/")[1][:-1])) except: pass return comments def get_position_short_comments(current_move,gameroot): # One line comment comments="" node=get_node(gameroot,current_move) game_move_color,game_move=node.get_move() if not game_move_color: game_move_color=guess_color_to_play(gameroot,current_move) comments+="%i/%i: "%(current_move,get_node_number(gameroot)) if node_has(node,"BWWR"): comments+=node_get(node,"BWWR")+"\n" elif node_has(node,"VNWR"): comments+=node_get(node,"VNWR")+"\n" elif node_has(node,"MCWR"): comments+=node_get(node,"MCWR")+"\n" elif node_has(node,"ES"): comments+=node_get(node,"ES")+"\n" else: comments+="\n" comments+="\n" if game_move_color.lower()=="b": if node_has(gameroot,"PB"): player=node_get(gameroot,"PB") else: player=_("Black") else: if node_has(gameroot,"PW"): player=node_get(gameroot,"PW") else: player=_("White") comments+="%s: %s"%(player,ij2gtp(game_move)) if node_has(node,"CBM"): bot=node_get(gameroot,"BOT") comments+="\n%s: %s"%(bot,node_get(node,"CBM")) try: if node_has(node[1],"BKMV"): if node_get(node[1],"BKMV")=="yes": comments+=": "+_("Book move") except: pass else: comments+="\n" return comments def get_node_number(node): return get_moves_number(node) def get_node(root,number=0): if number==0:return root node=root k=0 while k!=number: if not node: return False node=node[0] k+=1 return node def node_set(node, property_name, value): if type(value)==type(u"abc"): value=value.encode("utf-8") if property_name.lower() in ("w","b"): node.set_move(property_name.encode("utf-8"),value) elif property_name.upper() in ("TBM","TWM", "IBM", "IWM"): new_list=[] for ij in value: new_list.append(ij2sgf(ij).encode("utf-8")) if type(property_name)==type(u"abc"): property_name=property_name.encode("utf-8") node.set_raw_list(property_name,new_list) else: if type(property_name)==type(u"abc"): property_name=property_name.encode("utf-8") node.set(property_name,value) def node_get(node, property_name): if type(property_name)==type(u"abc"): property_name=property_name.encode("utf-8") value=node.get(property_name) if type(value)==type(str("abc")): value=value.decode("utf-8") return value def node_has(node, property_name): if type(property_name)==type(u"abc"): property_name=property_name.encode("utf-8") return node.has_property(property_name) def get_available(): from leela_analysis import Leela from gnugo_analysis import GnuGo from ray_analysis import Ray from aq_analysis import AQ from leela_zero_analysis import LeelaZero from pachi_analysis import Pachi from phoenixgo_analysis import PhoenixGo bots=[] for bot in [Leela, AQ, Ray, GnuGo, LeelaZero, Pachi, PhoenixGo]: profiles=get_bot_profiles(bot["name"]) for profile in profiles: bot2=dict(bot) bots.append(bot2) for key, value in profile.items(): bot2[key]=value return bots def get_gtp_bots(): from gtp_bot import GtpBot bots=[] for bot in [GtpBot]: profiles=get_bot_profiles(bot["name"]) for profile in profiles: bot2=dict(bot) bots.append(bot2) for key, value in profile.items(): bot2[key]=value return bots def get_bot_profiles(bot="",withcommand=True): sections=grp_config.get_sections() if bot!="": bots=[bot] else: bots=["Leela","GnuGo","Ray","AQ","LeelaZero","Pachi","PhoenixGo"] profiles=[] for section in sections: for bot in bots: if bot+"-" in section: command=grp_config.get(section,"command") if (not command) and (withcommand==True): continue data={"bot":bot,"command":"","parameters":"","timepermove":"","variations":"4","deepness":"4"} for option in grp_config.get_options(section): value=grp_config.get(section,option) data[option]=value profiles.append(data) return profiles class BotProfiles(Frame): def __init__(self,parent,bot): Frame.__init__(self,parent) self.parent=parent self.bot=bot self.profiles=get_bot_profiles(bot,False) profiles_frame=self self.listbox = Listbox(profiles_frame) self.listbox.grid(column=10,row=10,rowspan=10) self.update_listbox() row=10 Label(profiles_frame,text=_("Profile")).grid(row=row,column=11,sticky=W) self.profile = StringVar() Entry(profiles_frame, textvariable=self.profile, width=30).grid(row=row,column=12) row+=1 Label(profiles_frame,text=_("Command")).grid(row=row,column=11,sticky=W) self.command = StringVar() Entry(profiles_frame, textvariable=self.command, width=30).grid(row=row,column=12) row+=1 Label(profiles_frame,text=_("Parameters")).grid(row=row,column=11,sticky=W) self.parameters = StringVar() Entry(profiles_frame, textvariable=self.parameters, width=30).grid(row=row,column=12) row+=10 buttons_frame=Frame(profiles_frame) buttons_frame.grid(row=row,column=10,sticky=W,columnspan=3) Button(buttons_frame, text=_("Add profile"),command=self.add_profile).grid(row=row,column=1,sticky=W) Button(buttons_frame, text=_("Modify profile"),command=self.modify_profile).grid(row=row,column=2,sticky=W) Button(buttons_frame, text=_("Delete profile"),command=self.delete_profile).grid(row=row,column=3,sticky=W) Button(buttons_frame, text=_("Test"),command=lambda: self.parent.parent.test(self.bot_gtp,self.command,self.parameters)).grid(row=row,column=4,sticky=W) self.listbox.bind("<Button-1>", lambda e: self.after(100,self.change_selection)) self.index=-1 def clear_selection(self): self.index=-1 self.profile.set("") self.command.set("") self.parameters.set("") def change_selection(self): try: index=int(self.listbox.curselection()[0]) self.index=index log("Profile",index,"selected") except: log("No selection") self.clear_selection() return data=self.profiles[index] self.profile.set(data["profile"]) self.command.set(data["command"]) self.parameters.set(data["parameters"]) def empty_profiles(self): profiles=self.profiles sections=grp_config.get_sections() for bot in [profile["bot"] for profile in profiles]: for section in sections: if bot+"-" in section: grp_config.remove_section(section) self.update_listbox() def create_profiles(self): profiles=self.profiles p=0 for profile in profiles: bot=profile["bot"] for key,value in profile.items(): if key!="bot": grp_config.add_entry(bot+"-"+str(p),key,value) p+=1 self.update_listbox() def add_profile(self): profiles=self.profiles if self.profile.get()=="": return data={"bot":self.bot} data["profile"]=self.profile.get() data["command"]=self.command.get() data["parameters"]=self.parameters.get() self.empty_profiles() profiles.append(data) self.create_profiles() self.clear_selection() def modify_profile(self): profiles=self.profiles if self.profile.get()=="": return if self.index<0: log("No selection") return index=self.index profiles[index]["profile"]=self.profile.get() profiles[index]["command"]=self.command.get() profiles[index]["parameters"]=self.parameters.get() self.empty_profiles() self.create_profiles() self.clear_selection() def delete_profile(self): profiles=self.profiles if self.index<0: log("No selection") return index=self.index self.empty_profiles() del profiles[index] self.create_profiles() self.clear_selection() def update_listbox(self): profiles=self.profiles self.listbox.delete(0, END) for item in [profile["bot"]+" - "+profile["profile"] for profile in profiles]: self.listbox.insert(END, item) from sys import argv import getopt def main(bot): if len(argv)==1: temp_root = Tk() filename = open_sgf_file(parent=temp_root) temp_root.destroy() log(filename) log("gamename:",filename[:-4]) if not filename: sys.exit() log("filename:",filename) top = Application() bots=[] profiles=get_bot_profiles(bot["name"]) for profile in profiles: bot2=dict(bot) for key, value in profile.items(): bot2[key]=value bots.append(bot2) if len(bots)>0: popup=RangeSelector(top,filename,bots=bots) top.add_popup(popup) top.mainloop() else: log("Not profiles available for "+bot["name"]+" in \"config.ini\"") else: existing_profiles=[p["profile"] for p in get_bot_profiles(bot["name"])] if not existing_profiles: log("Not profiles available for "+bot["name"]+" in config.ini") sys.exit() try: parameters=getopt.getopt(argv[1:], '', ['no-gui','range=', 'color=', 'komi=',"variation=", "profile="]) except Exception, e: show_error(unicode(e)+"\n"+usage) sys.exit() if not parameters[1]: show_error("SGF file missing\n"+usage) sys.exit() app=None batch=[] for filename in parameters[1]: move_selection,intervals,variation,komi,nogui,profile=parse_command_line(filename,parameters[0]) if not profile: log("No profile indicated, the profile \""+existing_profiles[0]+"\" will be used") profile=existing_profiles[0] if profile not in existing_profiles: log("Unknown profile \""+profile+"\" for",bot["name"]) log("The profile \""+profile+"\" is not defined in \"config.ini\"") log("The existing profiles are"," ".join(['"'+p+'"' for p in existing_profiles])) sys.exit() profile={p["profile"]:p for p in get_bot_profiles(bot["name"])}[profile] if isinstance(filename, str): filename = unicode(filename, 'utf-8') filename2=".".join(filename.split(".")[:-1])+".rsgf" if nogui: popup=bot["runanalysis"]("no-gui",[filename,filename2],move_selection,intervals,variation-1,komi,profile) popup.terminate_bot() else: if not app: app = Application() one_analysis=[bot["runanalysis"],[filename,filename2],move_selection,intervals,variation-1,komi,profile] batch.append(one_analysis) if not nogui: app.after(100,lambda: batch_analysis(app,batch)) app.mainloop() try: from playsound import playsound mp3=grp_config.get("General","StoneSound") if mp3: log("Reading",mp3) with open(mp3, mode='rb') as sound_file: #pre loading the sound file in memory fileContent = sound_file.read() def play_stone_sound(): if mp3: threading.Thread(target=playsound, args=(mp3,)).start() except Exception,e: log("Stone sound disabled:") log(e) play_stone_sound=lambda: None