diff --git a/Code/Analisis.py b/Code/Analisis.py index 685e4cf..ebe914d 100644 --- a/Code/Analisis.py +++ b/Code/Analisis.py @@ -595,7 +595,8 @@ def siElegido(self, posRM): def ponPosRMactual(self, posRM): self.posRMactual = posRM self.rm = self.listaRM[self.posRMactual][0] - self.partida = Partida.Partida(self.jg.posicionBase).leerPV(self.rm.pv) + self.partida = Partida.Partida(self.jg.posicionBase) + self.partida.leerPV(self.rm.pv) self.partida.siTerminada() self.posMovActual = 0 @@ -817,7 +818,8 @@ def analizaJugada(self, jgNueva): self.rm = self.xtutor.analizaVariante(jgNueva, secs * 1000, self.siBlancas) me.final() - self.partidaTutor = Partida.Partida(jgNueva.posicion).leerPV(self.rm.pv) + self.partidaTutor = Partida.Partida(jgNueva.posicion) + self.partidaTutor.leerPV(self.rm.pv) if len(self.partidaTutor): self.w.tableroT.ponPosicion(self.partidaTutor.jugada(0).posicion) diff --git a/Code/AnalisisIndexes.py b/Code/AnalisisIndexes.py index 5324a22..05590f8 100644 --- a/Code/AnalisisIndexes.py +++ b/Code/AnalisisIndexes.py @@ -1,4 +1,4 @@ -import LCEngine +import LCEngineV1 as LCEngine from Code import VarGen from Code import Partida diff --git a/Code/AperturasStd.py b/Code/AperturasStd.py index b54665e..9dd6056 100644 --- a/Code/AperturasStd.py +++ b/Code/AperturasStd.py @@ -1,3 +1,5 @@ +import LCEngineV1 as LCEngine + from operator import attrgetter from Code import TrListas @@ -34,6 +36,7 @@ def __str__(self): class ListaAperturasStd: def __init__(self): self.dic = None + self.dicFenM2 = None self.hijos = None self.lia1h8 = None @@ -49,6 +52,14 @@ def reset(self, configuracion, siBasic, siEntrenar): bl.trOrdena = ("A" if bl.siBasic else "B") + bl.trNombre.upper() self.hijos = self.ordena(self.hijos, 0) + dfen = {} + makePV = LCEngine.makePV + fen2fenM2 = LCEngine.fen2fenM2 + for pv, ap in self.dic.iteritems(): + fen = makePV(pv) + dfen[fen2fenM2(fen)] = ap + self.dicFenM2 = dfen + def ordena(self, hijos, n): if hijos: hijos = sorted(hijos, key=attrgetter("trOrdena")) @@ -132,6 +143,16 @@ def lee(self, ficheroPers, siEntrenar): if n <= 0: self.hijos.append(bloque) + def asignaTransposition(self, partida): + partida.transposition = None + if not (partida.apertura is None or partida.pendienteApertura): + for nj, jg in enumerate(partida.liJugadas): + if not jg.siApertura: + fenm2 = jg.posicion.fenM2() + if fenm2 in self.dicFenM2: + partida.transposition = self.dicFenM2[fenm2] + partida.jg_transposition = jg + def asignaApertura(self, partida): partida.apertura = None if not partida.siFenInicial(): diff --git a/Code/CajonDesastre.py b/Code/CajonDesastre.py index 5c21041..a83b37b 100644 --- a/Code/CajonDesastre.py +++ b/Code/CajonDesastre.py @@ -4,7 +4,7 @@ import shutil import sqlite3 -import LCEngine +import LCEngineV1 as LCEngine from Code import BaseConfig from Code import Util diff --git a/Code/Constantes.py b/Code/Constantes.py index ab6eab1..adc4f39 100644 --- a/Code/Constantes.py +++ b/Code/Constantes.py @@ -2,27 +2,34 @@ kSigueApertura, kSigueUsuario, kMalApertura = range(3) -kJugNueva, kJugEntPos, kJugPGN, kJugEntMaq, kJugGM, kJugRemoto, kJugSolo, kJug60, kJugElo, kJugMicElo, \ -kJugBooks, kJugAperturas, kJugBoxing, kJugEntTac, kJugMvM, kJugAlbum, kJugFics, kJugFide, kJugLichess, \ -kJugWorldMap, kJugRoute, kJugEntLight, kJugWashingCreate, kJugWashingTactics, kJugWashingReplay, kJugSingularMoves\ - = range(26) +( + kJugNueva, kJugEntPos, kJugPGN, kJugEntMaq, kJugGM, kJugRemoto, kJugSolo, kJug60, kJugElo, kJugMicElo, + kJugBooks, kJugAperturas, kJugOpeningLines, kJugBoxing, kJugEntTac, kJugMvM, kJugAlbum, kJugFics, kJugFide, + kJugLichess, kJugWorldMap, kJugRoute, kJugEntLight, kJugWashingCreate, kJugWashingTactics, kJugWashingReplay, + kJugSingularMoves +) = range(27) kFinNormal, kFinReinicio = range(2) -kGanamos, kGanaRival, kTablas, kTablasRepeticion, kTablas50, kTablasFaltaMaterial, kGanamosTiempo, kGanaRivalTiempo, kTablasAcuerdo, kDesconocido = range(10) +( + kGanamos, kGanaRival, kTablas, kTablasRepeticion, kTablas50, kTablasFaltaMaterial, kGanamosTiempo, + kGanaRivalTiempo, kTablasAcuerdo, kDesconocido +) = range(10) kMoverAdelante, kMoverAtras, kMoverInicio, kMoverFinal, kMoverLibre, kMoverReloj = range(6) -k_terminar, k_play, k_mainmenu, k_competicion, k_competir, k_entrenamiento, k_opciones, \ -k_informacion, k_grabar, k_grabarComo, k_file, k_recuperar, k_abandonar, k_reiniciar, k_atras, \ -k_aplazar, k_finpartida, k_ent_empezar, k_ent_otro, k_pgnFin, k_pgnPaste, \ -k_pgnFichero, k_pgnInformacion, k_pgnFicheroRepite, k_pgnNuestroFichero, k_jugadadia, k_pgnComandoExterno, \ -k_rendirse, k_tablas, k_libros, \ -k_peliculaTerminar, k_peliculaLento, k_peliculaPausa, k_peliculaSeguir, k_peliculaRapido, k_peliculaRepetir, \ -k_peliculaPGN, k_jugar, k_anterior, k_siguiente, k_trasteros, \ -k_ayuda, k_mateNivel, k_ayudaMover, \ -k_aceptar, k_cancelar, \ -k_configurar, k_utilidades, k_variantes, k_tools, k_elo, k_cambiar, k_libre, k_showtext, k_enviar = range(55) +( + k_terminar, k_play, k_mainmenu, k_competicion, k_competir, k_entrenamiento, k_opciones, + k_informacion, k_grabar, k_grabarComo, k_file, k_recuperar, k_abandonar, k_reiniciar, k_atras, + k_aplazar, k_finpartida, k_ent_empezar, k_ent_otro, k_pgnFin, k_pgnPaste, + k_pgnFichero, k_pgnInformacion, k_pgnFicheroRepite, k_pgnNuestroFichero, k_jugadadia, k_pgnComandoExterno, + k_rendirse, k_tablas, k_libros, + k_peliculaTerminar, k_peliculaLento, k_peliculaPausa, k_peliculaSeguir, k_peliculaRapido, k_peliculaRepetir, + k_peliculaPGN, k_jugar, k_anterior, k_siguiente, k_trasteros, + k_ayuda, k_mateNivel, k_ayudaMover, + k_aceptar, k_cancelar, + k_configurar, k_utilidades, k_variantes, k_tools, k_elo, k_cambiar, k_libre, k_showtext, k_enviar +) = range(55) kMP_1, kMP_2, kMP_3, kMP_4, kMP_5, kMP_6, kMP_7 = range(7) @@ -30,9 +37,11 @@ kTutorH, kTutorH2_1, kTutorH1_2, kTutorV = range(4) -kAjustarMejor, kAjustarSuperior, kAjustarSimilar, kAjustarInferior, kAjustarPeor, kAjustarPlayer, \ -kAjustarNivelAlto, kAjustarNivelMedio, kAjustarNivelBajo, kAjustarSuperiorM, kAjustarSuperiorMM, \ -kAjustarInferiorM, kAjustarInferiorMM = range(13) +( + kAjustarMejor, kAjustarSuperior, kAjustarSimilar, kAjustarInferior, kAjustarPeor, kAjustarPlayer, + kAjustarNivelAlto, kAjustarNivelMedio, kAjustarNivelBajo, kAjustarSuperiorM, kAjustarSuperiorMM, + kAjustarInferiorM, kAjustarInferiorMM +) = range(13) kControlTableroNo, kControlTableroGeneral, kControlTableroParticular = range(3) diff --git a/Code/ControlPosicion.py b/Code/ControlPosicion.py index 26524f3..42eb2aa 100644 --- a/Code/ControlPosicion.py +++ b/Code/ControlPosicion.py @@ -1,4 +1,4 @@ -import LCEngine +import LCEngineV1 as LCEngine from Code import TrListas @@ -434,3 +434,6 @@ def siPeonCoronando(self, desdeA1H8, hastaA1H8): return False return True + +def distancia(desde, hasta): + return ((ord(desde[0])-ord(hasta[0]))**2 + (ord(desde[1])-ord(hasta[1]))**2)**0.5 diff --git a/Code/DBgames.py b/Code/DBgames.py index f0c3acc..8ee44cc 100644 --- a/Code/DBgames.py +++ b/Code/DBgames.py @@ -4,7 +4,7 @@ import time import random -import LCEngine +import LCEngineV1 as LCEngine from Code import ControlPosicion from Code import Partida @@ -524,8 +524,9 @@ def getSummary(self, pvBase, dicAnalisis, siFigurinesPGN, allmoves=True): class DBgames: - def __init__(self, nomFichero): + def __init__(self, nomFichero, with_dbSTAT=True): self.nomFichero = Util.dirRelativo(nomFichero) + self.with_dbSTAT = with_dbSTAT self.liCamposBase = ["EVENT", "SITE", "DATE", "WHITE", "BLACK", "RESULT", "ECO", "WHITEELO", "BLACKELO", "PLIES"] self.liCamposWork = ["XPV", ] self.liCamposBLOB = ["PGN", ] @@ -556,7 +557,10 @@ def __init__(self, nomFichero): self.liOrden = [] - self.dbSTAT = TreeSTAT(self.nomFichero + "_s1") + if self.with_dbSTAT: + self.dbSTAT = TreeSTAT(self.nomFichero + "_s1") + else: + self.dbSTAT = None self.liRowids = [] @@ -759,10 +763,12 @@ def borrarLista(self, lista): for recno in lista: pv = self.damePV(recno) result = self.field(recno, "RESULT") - self.dbSTAT.append(pv, result, -1) + if self.with_dbSTAT: + self.dbSTAT.append(pv, result, -1) self._cursor.execute(cSQL,(self.liRowids[recno],)) del self.liRowids[recno] - self.dbSTAT.commit() + if self.with_dbSTAT: + self.dbSTAT.commit() self._conexion.commit() def getSummary(self, pvBase, dicAnalisis, siFigurinesPGN, allmoves=True): @@ -803,7 +809,8 @@ def leerPGNs(self, ficheros, dlTmp): t1 = time.time()-0.7 # para que empiece enseguida - self.dbSTAT.massive_append_set(True) + if self.with_dbSTAT: + self.dbSTAT.massive_append_set(True) def write_logs(fich, pgn): with open(fich, "ab") as ferr: @@ -880,7 +887,8 @@ def write_logs(fich, pgn): pgn = Util.var2blob(pgn) reg = (xpv, event, site, date, white, black, result, eco, whiteelo, blackelo, pgn, plies) - self.dbSTAT.append_fen(pv, result, liFens) + if self.with_dbSTAT: + self.dbSTAT.append_fen(pv, result, liFens) liRegs.append(reg) nRegs += 1 importados += 1 @@ -890,9 +898,10 @@ def write_logs(fich, pgn): liRegs = [] stRegs = set() conexion.commit() - self.dbSTAT.massive_append_set(False) - self.dbSTAT.commit() - self.dbSTAT.massive_append_set(True) + if self.with_dbSTAT: + self.dbSTAT.massive_append_set(False) + self.dbSTAT.commit() + self.dbSTAT.massive_append_set(True) if n == next_n: if time.time()-t1> 0.8: if not dlTmp.actualiza(erroneos+duplicados+importados, erroneos, duplicados, importados): @@ -906,15 +915,17 @@ def write_logs(fich, pgn): dlTmp.actualiza(erroneos+duplicados+importados, erroneos, duplicados, importados) dlTmp.ponSaving() - self.dbSTAT.massive_append_set(False) - self.dbSTAT.commit() + if self.with_dbSTAT: + self.dbSTAT.massive_append_set(False) + self.dbSTAT.commit() conexion.commit() dlTmp.ponContinuar() def appendDB(self, db, liRecnos, dlTmp): duplicados = importados = 0 - self.dbSTAT.massive_append_set(True) + if self.with_dbSTAT: + self.dbSTAT.massive_append_set(True) t1 = time.time() - 0.7 # para que empiece enseguida @@ -941,7 +952,8 @@ def appendDB(self, db, liRecnos, dlTmp): pv = xpv2pv(xpv) reg = (xpv, raw["EVENT"], raw["SITE"], raw["DATE"], raw["WHITE"], raw["BLACK"], raw["RESULT"], raw["ECO"], raw["WHITEELO"], raw["BLACKELO"], raw["PGN"], raw["PLIES"]) - self.dbSTAT.append(pv, raw["RESULT"]) + if self.with_dbSTAT: + self.dbSTAT.append(pv, raw["RESULT"]) liRegs.append(reg) nRegs += 1 importados += 1 @@ -949,9 +961,10 @@ def appendDB(self, db, liRecnos, dlTmp): cursor.executemany(sql, liRegs) liRegs = [] conexion.commit() - self.dbSTAT.massive_append_set(False) - self.dbSTAT.commit() - self.dbSTAT.massive_append_set(True) + if self.with_dbSTAT: + self.dbSTAT.massive_append_set(False) + self.dbSTAT.commit() + self.dbSTAT.massive_append_set(True) if pos == next_n: if time.time() - t1 > 0.8: @@ -967,8 +980,9 @@ def appendDB(self, db, liRecnos, dlTmp): dlTmp.actualiza(duplicados + importados, duplicados, importados) dlTmp.ponSaving() - self.dbSTAT.massive_append_set(False) - self.dbSTAT.commit() + if self.with_dbSTAT: + self.dbSTAT.massive_append_set(False) + self.dbSTAT.commit() conexion.commit() dlTmp.ponContinuar() @@ -1022,7 +1036,7 @@ def leePartidaRaw(self, raw): litags.extend(rtags) p.setTags(litags) - p.asignaApertura(VarGen.configuracion) + p.asignaApertura() return p def leePGNRecno(self, recno): @@ -1102,9 +1116,10 @@ def modifica(self, recno, partidaCompleta): self._conexion.commit() pvAnt = xpv2pv(reg_ant["XPV"]) resNue = dTags.get("RESULT", "*") - self.dbSTAT.append(pvAnt, resAnt, -1) - self.dbSTAT.append(pvNue, resNue, +1) - self.dbSTAT.commit() + if self.with_dbSTAT: + self.dbSTAT.append(pvAnt, resAnt, -1) + self.dbSTAT.append(pvNue, resNue, +1) + self.dbSTAT.commit() del self.cache[rowid] @@ -1136,8 +1151,9 @@ def inserta(self, partidaCompleta): sql = "insert into games (XPV,EVENT,SITE,DATE,WHITE,BLACK,RESULT,ECO,WHITEELO,BLACKELO,PLIES,PGN) values (?,?,?,?,?,?,?,?,?,?,?,?);" self._cursor.execute(sql, data) self._conexion.commit() - self.dbSTAT.append(pv, dTags.get("RESULT", "*"), +1) - self.dbSTAT.commit() + if self.with_dbSTAT: + self.dbSTAT.append(pv, dTags.get("RESULT", "*"), +1) + self.dbSTAT.commit() self.liRowids.append(self._cursor.lastrowid) diff --git a/Code/DBgamesFEN.py b/Code/DBgamesFEN.py index bdd021e..e6b29ff 100644 --- a/Code/DBgamesFEN.py +++ b/Code/DBgamesFEN.py @@ -4,7 +4,7 @@ import time import random -import LCEngine +import LCEngineV1 as LCEngine from Code import Partida from Code import Util diff --git a/Code/EngineThread.py b/Code/EngineThread.py index 16d012f..f8ef893 100644 --- a/Code/EngineThread.py +++ b/Code/EngineThread.py @@ -10,7 +10,6 @@ DEBUG_ENGINE = False - def xpr(line): if DEBUG_ENGINE: t = time.time() @@ -130,6 +129,10 @@ def start(self): startupinfo = None curdir = os.path.abspath(os.curdir) # problem with "." as curdir os.chdir(self.direxe) # to fix problems with non ascii folders + if VarGen.isLinux: + argv0 = self.args[0] + if "/" not in argv0: + self.args[0] = os.path.join(self.direxe, argv0) self.process = subprocess.Popen(self.args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, startupinfo=startupinfo, shell=False) os.chdir(curdir) diff --git a/Code/EnginesLinux32.py b/Code/EnginesLinux32.py index 3e91a81..c1c89b9 100644 --- a/Code/EnginesLinux32.py +++ b/Code/EnginesLinux32.py @@ -151,10 +151,10 @@ def mas(cm): cm.ordenUCI("Hash", "64") mas(cm) - cm = ConfigMotor("mcbrain", "Michael Byrne (based on stockfish)", "4.0", "https://github.com/MichaelB7/Stockfish/releases") - cm.path = "SF-McBrain-v40_x32_linux" + cm = ConfigMotor("mcbrain", "Michael Byrne", "9", "https://github.com/MichaelB7/Stockfish/releases") + cm.path = "McBrain-9_x32_linux" cm.elo = 3200 - cm.ordenUCI("Tactical", "3") + cm.ordenUCI("Contempt", "0") cm.ordenUCI("Hash", "64") cm.ponMultiPV(20, 500) mas(cm) diff --git a/Code/EnginesLinux64.py b/Code/EnginesLinux64.py index 924cd69..c4d6e83 100644 --- a/Code/EnginesLinux64.py +++ b/Code/EnginesLinux64.py @@ -48,8 +48,8 @@ def mas(cm): cm.ponMultiPV(20, 218) mas(cm) - cm = ConfigMotor("stockfish", "Tord Romstad, Marco Costalba, Joona Kiiski", "8", "http://stockfishchess.org/") - cm.path = "Linux/stockfish_8_x64" + cm = ConfigMotor("stockfish", "Tord Romstad, Marco Costalba, Joona Kiiski", "9", "http://stockfishchess.org/") + cm.path = "Linux/stockfish-9-64" cm.elo = 3300 cm.ordenUCI("Ponder", "false") cm.ordenUCI("Hash", "64") @@ -138,10 +138,10 @@ def mas(cm): cm.ordenUCI("Hash", "64") mas(cm) - cm = ConfigMotor("mcbrain", "Michael Byrne (based on stockfish)", "4.0", "https://github.com/MichaelB7/Stockfish/releases") - cm.path = "SSF-McBrain-v40_x64_linux" + cm = ConfigMotor("mcbrain", "Michael Byrne", "9", "https://github.com/MichaelB7/Stockfish/releases") + cm.path = "McBrain-9_x64_linux" cm.elo = 3200 - cm.ordenUCI("Tactical", "3") + cm.ordenUCI("Contempt", "0") cm.ordenUCI("Hash", "64") cm.ponMultiPV(20, 500) mas(cm) diff --git a/Code/EnginesWindows.py b/Code/EnginesWindows.py index 56b0a14..c8a0778 100644 --- a/Code/EnginesWindows.py +++ b/Code/EnginesWindows.py @@ -208,9 +208,9 @@ def mas(cm): cm.ponMultiPV(20, 256) mas(cm) - cm = ConfigMotor("stockfish", "Tord Romstad, Marco Costalba, Joona Kiiski", "8 32bits", "http://stockfishchess.org/") - cm.path = "Windows/stockfish_8_x32.exe" - cm.path_64 = "Windows/stockfish_8_x64_bmi2.exe", "8 64bits bmi2" + cm = ConfigMotor("stockfish", "Tord Romstad, Marco Costalba, Joona Kiiski", "9 32bits", "http://stockfishchess.org/") + cm.path = "Windows/stockfish_9_x32.exe" + cm.path_64 = "Windows/stockfish_9_x64_bmi2.exe", "9 64bits bmi2" cm.elo = 3300 cm.ordenUCI("Ponder", "false") cm.ordenUCI("Hash", "64") @@ -218,11 +218,11 @@ def mas(cm): cm.ponMultiPV(20, 500) mas(cm) - cm = ConfigMotor("mcbrain", "Michael Byrne (based on stockfish)", "4.0 32bit", "https://github.com/MichaelB7/Stockfish/releases") - cm.path = "SF-McBrain-v40_x32_old.exe" - cm.path_64 = "SF-McBrain-v40_x64_bmi2.exe", "4.0 64bit bmi2" + cm = ConfigMotor("mcbrain", "Michael Byrne", "9 32bit", "https://github.com/MichaelB7/Stockfish/releases") + cm.path = "McBrain-9_x32_old.exe" + cm.path_64 = "McBrain-9_x64_bmi2.exe", "9 64bit bmi2" cm.elo = 3200 - cm.ordenUCI("Tactical", "3") + cm.ordenUCI("Contempt", "0") cm.ordenUCI("Hash", "64") cm.ponMultiPV(20, 500) mas(cm) diff --git a/Code/Entrenamientos.py b/Code/Entrenamientos.py index 722862b..d20911b 100644 --- a/Code/Entrenamientos.py +++ b/Code/Entrenamientos.py @@ -261,14 +261,18 @@ def menuTacticas(submenu, tipo, carpetaBase, lista): menu1.separador() menu2 = menu1.submenu(_("Turn on the lights"), Iconos.TOL()) menu3 = menu2.submenu(_("Memory mode"), Iconos.TOL()) - xopcion(menu3, "tol_uned", _("UNED chess school"), Iconos.Uned()) + xopcion(menu3, "tol_uned_easy", "%s (%s)" % (_("UNED chess school"), _("Initial")), Iconos.Uned()) + menu3.separador() + xopcion(menu3, "tol_uned", "%s (%s)" % (_("UNED chess school"), _("Complete")), Iconos.Uned()) menu3.separador() xopcion(menu3, "tol_uwe_easy", "%s (%s)" % (_("Uwe Auerswald"), _("Initial")), Iconos.Uwe()) menu3.separador() xopcion(menu3, "tol_uwe", "%s (%s)" % (_("Uwe Auerswald"), _("Complete")), Iconos.Uwe()) menu2.separador() menu3 = menu2.submenu(_("Calculation mode"), Iconos.Calculo()) - xopcion(menu3, "tol_uned_calc", _("UNED chess school"), Iconos.Uned()) + xopcion(menu3, "tol_uned_easy_calc", "%s (%s)" % (_("UNED chess school"), _("Initial")), Iconos.Uned()) + menu3.separador() + xopcion(menu3, "tol_uned_calc", "%s (%s)" % (_("UNED chess school"), _("Complete")), Iconos.Uned()) menu3.separador() xopcion(menu3, "tol_uwe_easy_calc", "%s (%s)" % (_("Uwe Auerswald"), _("Initial")), Iconos.Uwe()) menu3.separador() @@ -290,8 +294,8 @@ def menuTacticas(submenu, tipo, carpetaBase, lista): txt = cat.nombre() nm = mem.nivel(x) - if nm > -1: - txt += " %s %d" % (_("Level"), nm + 1) + if nm >= 0: + txt += " %s %d" % (_("Level"), nm+1) xopcion(menu2, -100 - x, txt, cat.icono(), siDeshabilitado=not mem.siActiva(x)) @@ -667,7 +671,12 @@ def everest(self): PantallaEverest.everest(self.procesador) def turn_on_lights(self, name): - if name.startswith("uned"): + if name.startswith("uned_easy"): + title = "%s (%s)" % (_("UNED chess school"), _("Initial")) + folder = "Trainings/Tactics by UNED chess school" + icono = Iconos.Uned() + li_tam_blocks = (4, 6, 9, 12, 18, 36) + elif name.startswith("uned"): title = _("UNED chess school") folder = "Trainings/Tactics by UNED chess school" icono = Iconos.Uned() diff --git a/Code/Everest.py b/Code/Everest.py index f34f0ff..3bc652d 100644 --- a/Code/Everest.py +++ b/Code/Everest.py @@ -1,4 +1,4 @@ -import LCEngine +import LCEngineV1 as LCEngine from Code import Partida from Code.SQL import Base diff --git a/Code/GM.py b/Code/GM.py index e2ba76c..853265a 100644 --- a/Code/GM.py +++ b/Code/GM.py @@ -1,7 +1,7 @@ import operator import os -from LCEngine import xpv2pv, pv2xpv +from LCEngineV1 import xpv2pv, pv2xpv from Code import Jugada from Code import Util diff --git a/Code/Gestor.py b/Code/Gestor.py index edc5501..5009532 100644 --- a/Code/Gestor.py +++ b/Code/Gestor.py @@ -3,7 +3,7 @@ import random import time -import LCEngine +import LCEngineV1 as LCEngine from Code import Analisis from Code import AnalisisIndexes @@ -977,9 +977,13 @@ def borrar(self): li_del.append((_("Comments") + ":", False)) li_del.append(separador) li_del.append((_("Analysis") + ":", False)) + li_del.append(separador) + li_del.append((_("All") + ":", False)) resultado = FormLayout.fedit(li_del, title=_("Remove"), parent=self.pantalla, icon=Iconos.Delete()) if resultado: - variants, ratings, comments, analysis = resultado[1] + variants, ratings, comments, analysis, all = resultado[1] + if all: + variants, ratings, comments, analysis = True, True, True, True for jg in self.partida.liJugadas: if variants: jg.variantes = "" @@ -1500,6 +1504,7 @@ def showAnalisis(self): elos = self.partida.calc_elos(self.configuracion) alm = Histogram.genHistograms(self.partida, self.configuracion.centipawns) alm.indexesHTML, alm.indexesRAW, alm.eloW, alm.eloB, alm.eloT = AnalisisIndexes.genIndexes(self.partida, elos, alm) + alm.siBlancasAbajo = self.tablero.siBlancasAbajo um.final() PantallaAnalisis.showGraph(self.pantalla, self, alm, Analisis.muestraAnalisis) diff --git a/Code/Gestor60.py b/Code/Gestor60.py index 0b62c8c..92987b2 100644 --- a/Code/Gestor60.py +++ b/Code/Gestor60.py @@ -2,7 +2,7 @@ import random import time -import LCEngine +import LCEngineV1 as LCEngine from Code import ControlPosicion from Code import Gestor diff --git a/Code/GestorAlbum.py b/Code/GestorAlbum.py index b0e0af9..b3ed1c1 100644 --- a/Code/GestorAlbum.py +++ b/Code/GestorAlbum.py @@ -36,7 +36,7 @@ def inicio(self, album, cromo, aplazamiento=None): # -Aplazamiento 1/2-------------------------------------------------- if aplazamiento: self.partida.recuperaDeTexto(aplazamiento["JUGADAS"]) - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.xrival = Albums.GestorMotorAlbum(self, self.cromo) self.pantalla.ponToolBar((k_rendirse, k_aplazar, k_configurar, k_utilidades)) @@ -196,7 +196,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: diff --git a/Code/GestorAperturas.py b/Code/GestorAperturas.py index 45462c2..3958f8d 100644 --- a/Code/GestorAperturas.py +++ b/Code/GestorAperturas.py @@ -218,7 +218,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponFlechaSC(jg.desde, jg.hasta) self.beepExtendido(siNuestra) diff --git a/Code/GestorBooks.py b/Code/GestorBooks.py index fa4df2f..77edaa9 100644 --- a/Code/GestorBooks.py +++ b/Code/GestorBooks.py @@ -280,7 +280,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponFlechaSC(jg.desde, jg.hasta) self.beepExtendido(siNuestra) diff --git a/Code/GestorCompeticion.py b/Code/GestorCompeticion.py index 4651eb5..5c3a09c 100644 --- a/Code/GestorCompeticion.py +++ b/Code/GestorCompeticion.py @@ -185,7 +185,7 @@ def atras(self): self.ponAyudas(self.ayudas) self.partida.anulaUltimoMovimiento(self.siJugamosConBlancas) self.siApertura = False - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponteAlFinal() self.siAnalizadoTutor = False self.refresh() @@ -322,7 +322,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: diff --git a/Code/GestorElo.py b/Code/GestorElo.py index 74815e3..a0d4712 100644 --- a/Code/GestorElo.py +++ b/Code/GestorElo.py @@ -529,7 +529,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: @@ -692,7 +692,7 @@ def determinaColor(self, datosMotor): def atras(self): if self.partida.numJugadas() > 2: self.partida.anulaUltimoMovimiento(self.siJugamosConBlancas) - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponteAlFinal() self.refresh() self.siguienteJugada() diff --git a/Code/GestorEntMaq.py b/Code/GestorEntMaq.py index ade5792..2602bb7 100644 --- a/Code/GestorEntMaq.py +++ b/Code/GestorEntMaq.py @@ -1,4 +1,4 @@ -import LCEngine +import LCEngineV1 as LCEngine from PyQt4 import QtCore from Code import Analisis @@ -475,7 +475,7 @@ def atras(self): self.ponAyudasEM() self.partida.anulaUltimoMovimiento(self.siJugamosConBlancas) if not self.fen: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponteAlFinal() self.reOpenBook() self.refresh() @@ -883,7 +883,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: diff --git a/Code/GestorEverest.py b/Code/GestorEverest.py index bcee87f..989288b 100644 --- a/Code/GestorEverest.py +++ b/Code/GestorEverest.py @@ -246,6 +246,13 @@ def mueveHumano(self, desde, hasta, coronacion=""): um.final() rmUsu, posUsu = mrm.buscaRM(jgUsu.movimiento()) + if rmUsu is None: + um = QTUtil2.analizando(self.pantalla) + self.analizaFinal() + rmUsu = self.xanalyzer.valora(posicion, desde, hasta, coronacion) + mrm.agregaRM(rmUsu) + self.analizaInicio() + um.final() w = PantallaJuicio.WJuicio(self, self.xanalyzer, self.nombreObj, posicion, mrm, rmObj, rmUsu, analisis, siCompetitivo=False) @@ -286,7 +293,7 @@ def masJugada(self, siNuestra, analisis=None, comentario=None): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.movimientosPiezas(jg.liMovs, True) self.tablero.ponPosicion(jg.posicion) self.ponFlechaSC(jg.desde, jg.hasta) diff --git a/Code/GestorFideFics.py b/Code/GestorFideFics.py index 103a09b..ee7ef5a 100644 --- a/Code/GestorFideFics.py +++ b/Code/GestorFideFics.py @@ -3,7 +3,7 @@ import datetime import random -import LCEngine +import LCEngineV1 as LCEngine from Code import Apertura from Code import Gestor @@ -414,7 +414,7 @@ def masJugada(self, siNuestra, comentario=None, analisis=None): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.movimientosPiezas(jg.liMovs, True) self.tablero.ponPosicion(jg.posicion) self.ponFlechaSC(jg.desde, jg.hasta) @@ -506,7 +506,7 @@ def atras(self): if self.partida.numJugadas() > 2: self.analizaFinal() ndel = self.partida.anulaUltimoMovimiento(self.siJugamosConBlancas) - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.posJugadaObj -= ndel self.analisis = None self.ponteAlFinal() diff --git a/Code/GestorGM.py b/Code/GestorGM.py index c6beeab..b5f2493 100644 --- a/Code/GestorGM.py +++ b/Code/GestorGM.py @@ -308,10 +308,12 @@ def mueveHumano(self, desde, hasta, coronacion=None): rmUsu, nada = mrm.buscaRM(jgUsu.movimiento()) if rmUsu is None: + um = QTUtil2.analizando(self.pantalla) self.analizaFinal() rmUsu = self.xtutor.valora(posicion, desde, hasta, coronacion) mrm.agregaRM(rmUsu) self.analizaInicio() + um.final() rmGM, posGM = mrm.buscaRM(jgGM.movimiento()) if rmGM is None: @@ -390,7 +392,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponFlechaSC(jg.desde, jg.hasta) self.beepExtendido(siNuestra) diff --git a/Code/GestorMicElo.py b/Code/GestorMicElo.py index 49b74f1..cdf827a 100644 --- a/Code/GestorMicElo.py +++ b/Code/GestorMicElo.py @@ -134,7 +134,7 @@ def inicio(self, datosMotor, minutos, segundos, siCompetitivo, aplazamiento=None self.maxSegundos = aplazamiento["MAXSEGUNDOS"] self.segundosJugada = aplazamiento["SEGUNDOSJUGADA"] - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() else: self.datosMotor = datosMotor @@ -392,7 +392,7 @@ def masJugada(self, jg, siNuestra): self.partida.liJugadas.append(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: @@ -601,7 +601,7 @@ def relojStop(self, siUsuario): def atras(self): if self.partida.numJugadas() > 2: self.partida.anulaUltimoMovimiento(self.siJugamosConBlancas) - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponteAlFinal() self.refresh() self.siguienteJugada() diff --git a/Code/GestorOpeningLines.py b/Code/GestorOpeningLines.py new file mode 100644 index 0000000..127f0e6 --- /dev/null +++ b/Code/GestorOpeningLines.py @@ -0,0 +1,548 @@ +import time + +from Code import Gestor +from Code import Jugada +from Code import ControlPosicion +from Code import TrListas +from Code.QT import QTUtil2 +from Code import Util +from Code import OpeningLines +from Code import XMotorRespuesta +from Code import Partida +from Code.Constantes import * + + +class GestorOpeningLines(Gestor.Gestor): + def inicio(self, pathFichero, modo, num_linea): + self.tablero.saveVisual() + + self.pathFichero = pathFichero + dbop = OpeningLines.Opening(pathFichero) + self.tablero.dbVisual_setFichero(dbop.nomFichero) + self.reinicio(dbop, modo, num_linea) + + def reinicio(self, dbop, modo, num_linea): + self.dbop = dbop + self.tipoJuego = kJugOpeningLines + + self.modo = modo + self.num_linea = num_linea + + self.training = self.dbop.training() + self.liGames = self.training["LIGAMES_%s" % modo.upper()] + self.game = self.liGames[num_linea] + self.liPV = self.game["LIPV"] + self.numPV = len(self.liPV) + + self.calc_totalTiempo() + + self.dicFENm2 = self.training["DICFENM2"] + + + self.liMensBasic = [ + "%s: %d" % (_("Lines"), len(self.liGames)), + ] + + self.siAyuda = False + self.tablero.dbVisual_setShowAllways(False) + + self.partida = Partida.Partida() + + self.ayudas = 9999 # Para que analice sin problemas + + self.siJugamosConBlancas = self.training["COLOR"] == "WHITE" + self.siRivalConBlancas = not self.siJugamosConBlancas + + self.pantalla.ponToolBar((k_mainmenu, k_ayuda, k_reiniciar)) + self.pantalla.activaJuego(True, False, siAyudas=False) + self.ponMensajero(self.mueveHumano) + self.ponPosicion(self.partida.ultPosicion) + self.mostrarIndicador(True) + self.quitaAyudas() + self.ponPiezasAbajo(self.siJugamosConBlancas) + self.pgnRefresh(True) + + self.ponCapInfoPorDefecto() + + self.estado = kJugando + + self.ponPosicionDGT() + + self.errores = 0 + self.ini_time = time.time() + self.muestraInformacion() + self.siguienteJugada() + + def calc_totalTiempo(self): + self.tm = 0 + for game in self.liGames: + for tr in game["TRIES"]: + self.tm += tr["TIME"] + + def ayuda(self): + self.siAyuda = True + self.pantalla.ponToolBar((k_mainmenu, k_reiniciar)) + self.tablero.dbVisual_setShowAllways(True) + + self.muestraAyuda() + self.muestraInformacion() + + def muestraInformacion(self): + li = [] + li.append("%s: %d" %(_("Errors"), self.errores)) + if self.siAyuda: + li.append(_("Help activated")) + self.ponRotulo1("\n".join(li)) + + tgm = 0 + for tr in self.game["TRIES"]: + tgm += tr["TIME"] + + mens = "\n" + "\n".join(self.liMensBasic) + mens += "\n%s:\n %s %s\n %s %s" % (_("Working time"), + time.strftime("%H:%M:%S", time.gmtime(tgm)), _("Current"), + time.strftime("%H:%M:%S", time.gmtime(self.tm)), _("Total")) + + self.ponRotulo2(mens) + + if self.siAyuda: + dicNAGs = TrListas.dicNAGs() + mens3 = "" + fenM2 = self.partida.ultPosicion.fenM2() + reg = self.dbop.getfenvalue(fenM2) + if reg: + mens3 = reg["COMENTARIO"] + ventaja = reg.get("VENTAJA", 0) + valoracion = reg.get("VALORACION", 0) + if ventaja: + mens3 += "\n %s" % dicNAGs[ventaja] + if valoracion: + mens3 += "\n %s" % dicNAGs[valoracion] + self.ponRotulo3(mens3 if mens3 else None) + + + def partidaTerminada(self, siCompleta): + tm = time.time() - self.ini_time + li = [_("Line finished.")] + if self.siAyuda: + li.append(_("Help activated")) + if self.errores > 0: + li.append("%s: %d" % (_("Errors"), self.errores)) + + if siCompleta: + QTUtil2.mensaje(self.pantalla, "\n".join(li)) + + dictry = { + "DATE": Util.hoy(), + "TIME": tm, + "AYUDA": self.siAyuda, + "ERRORS": self.errores + } + self.game["TRIES"].append(dictry) + + sinError = self.errores == 0 and not self.siAyuda + if siCompleta: + if sinError: + self.game["NOERROR"] += 1 + noError = self.game["NOERROR"] + if self.modo == "sequential": + salto = 2**(noError + 1) + numGames = len(self.liGames) + for x in range(salto, numGames): + game = self.liGames[x] + if game["NOERROR"] != noError: + salto = x + break + + liNuevo = self.liGames[1:salto] + liNuevo.append(self.game) + if numGames > salto: + liNuevo.extend(self.liGames[salto:]) + self.training["LIGAMES_SEQUENTIAL"] = liNuevo + self.pantalla.ponToolBar((k_mainmenu, k_siguiente)) + else: + self.pantalla.ponToolBar((k_mainmenu, k_reiniciar)) + else: + self.game["NOERROR"] -= 1 + + self.pantalla.ponToolBar((k_mainmenu, k_reiniciar)) + else: + if not sinError: + self.game["NOERROR"] -= 1 + self.game["NOERROR"] = max(0, self.game["NOERROR"]) + + self.dbop.setTraining(self.training) + self.estado = kFinJuego + self.calc_totalTiempo() + self.muestraInformacion() + + def muestraAyuda(self): + pv = self.liPV[len(self.partida)] + self.tablero.creaFlechaMov(pv[:2], pv[2:4], "mt80") + fenM2 = self.partida.ultPosicion.fenM2() + for pv1 in self.dicFENm2[fenM2]: + if pv1 != pv: + self.tablero.creaFlechaMov(pv1[:2], pv1[2:4], "ms40") + + def procesarAccion(self, clave): + if clave == k_mainmenu: + self.finPartida() + + elif clave == k_reiniciar : + self.reiniciar() + + elif clave == k_configurar: + self.configurar(siSonidos=True) + + elif clave == k_utilidades: + self.utilidades() + + elif clave == k_siguiente: + self.reinicio(self.dbop, self.modo, self.num_linea) + + elif clave == k_ayuda: + self.ayuda() + + else: + Gestor.Gestor.rutinaAccionDef(self, clave) + + def finalX(self): + return self.finPartida() + + def finPartida(self): + self.dbop.close() + self.tablero.restoreVisual() + self.procesador.inicio() + if self.modo == "static": + self.procesador.openingsTrainingStatic(self.pathFichero) + else: + self.procesador.openings() + return False + + def reiniciar(self): + if len(self.partida) > 0 and self.estado != kFinJuego: + self.partidaTerminada(False) + self.reinicio(self.dbop, self.modo, self.num_linea) + + def siguienteJugada(self): + self.muestraInformacion() + if self.estado == kFinJuego: + return + + self.estado = kJugando + + self.siJuegaHumano = False + self.ponVista() + + siBlancas = self.partida.ultPosicion.siBlancas + + self.ponIndicador(siBlancas) + self.refresh() + + siRival = siBlancas == self.siRivalConBlancas + + numJugadas = len(self.partida) + if numJugadas >= self.numPV: + self.partidaTerminada(True) + return + pv = self.liPV[numJugadas] + + if siRival: + self.desactivaTodas() + + self.rmRival = XMotorRespuesta.RespuestaMotor("Apertura", self.siRivalConBlancas) + self.rmRival.desde = pv[:2] + self.rmRival.hasta = pv[2:4] + self.rmRival.coronacion = pv[4:] + + self.mueveRival(self.rmRival) + self.siguienteJugada() + + else: + self.activaColor(siBlancas) + self.siJuegaHumano = True + if self.siAyuda: + self.muestraAyuda() + + def mueveHumano(self, desde, hasta, coronacion=""): + jg = self.checkMueveHumano(desde, hasta, coronacion) + if not jg: + return False + pvSel = desde + hasta + coronacion + pvObj = self.liPV[len(self.partida)] + + if pvSel != pvObj: + fenM2 = jg.posicionBase.fenM2() + li = self.dicFENm2[fenM2] + if pvSel in li: + mens = _("You have selected a correct move, but this line uses another one.") + QTUtil2.mensajeTemporal(self.pantalla, mens, 2, posicion="tb", background="#C3D6E8") + self.sigueHumano() + return False + + self.errores += 1 + mens = "%s: %d" % (_("Error"), self.errores) + QTUtil2.mensajeTemporal(self.pantalla, mens, 2, posicion="tb", background="#FF9B00") + self.muestraInformacion() + self.sigueHumano() + return False + + self.movimientosPiezas(jg.liMovs) + + self.masJugada(jg, True) + self.siguienteJugada() + return True + + def masJugada(self, jg, siNuestra): + self.partida.append_jg(jg) + if self.partida.pendienteApertura: + self.partida.asignaApertura() + + self.ponFlechaSC(jg.desde, jg.hasta) + self.beepExtendido(siNuestra) + + self.pgnRefresh(self.partida.ultPosicion.siBlancas) + self.refresh() + + self.ponPosicionDGT() + + def mueveRival(self, respMotor): + desde = respMotor.desde + hasta = respMotor.hasta + + coronacion = respMotor.coronacion + + siBien, mens, jg = Jugada.dameJugada(self.partida.ultPosicion, desde, hasta, coronacion) + if siBien: + self.partida.ultPosicion = jg.posicion + + self.masJugada(jg, False) + self.movimientosPiezas(jg.liMovs, True) + + self.error = "" + + return True + else: + self.error = mens + return False + + +class GestorOpeningLinesPositions(Gestor.Gestor): + def inicio(self, pathFichero): + self.pathFichero = pathFichero + dbop = OpeningLines.Opening(pathFichero) + self.reinicio(dbop) + + def reinicio(self, dbop): + self.dbop = dbop + self.tipoJuego = kJugOpeningLines + + self.training = self.dbop.training() + self.liTrainPositions = self.training["LITRAINPOSITIONS"] + self.trposition = self.liTrainPositions[0] + + self.tm = 0 + for game in self.liTrainPositions: + for tr in game["TRIES"]: + self.tm += tr["TIME"] + + self.liMensBasic = [ + "%s: %d" % (_("Moves"), len(self.liTrainPositions)), + ] + + self.siAyuda = False + + cp = ControlPosicion.ControlPosicion() + cp.leeFen(self.trposition["FENM2"] + " 0 1") + + self.partida = Partida.Partida(iniPosicion=cp) + + self.ayudas = 9999 # Para que analice sin problemas + + self.siJugamosConBlancas = self.training["COLOR"] == "WHITE" + self.siRivalConBlancas = not self.siJugamosConBlancas + + self.pantalla.ponToolBar((k_mainmenu, k_ayuda, k_reiniciar)) + self.pantalla.activaJuego(True, False, siAyudas=False) + self.ponMensajero(self.mueveHumano) + self.ponPosicion(cp) + self.mostrarIndicador(True) + self.quitaAyudas() + self.ponPiezasAbajo(self.siJugamosConBlancas) + self.pgnRefresh(True) + + self.ponCapInfoPorDefecto() + + self.estado = kJugando + + self.ponPosicionDGT() + + self.quitaInformacion() + + self.errores = 0 + self.ini_time = time.time() + self.muestraInformacion() + self.siguienteJugada() + + def ayuda(self): + self.siAyuda = True + self.pantalla.ponToolBar((k_mainmenu, k_reiniciar)) + + self.muestraAyuda() + self.muestraInformacion() + + def muestraInformacion(self): + li = [] + li.append("%s: %d" %(_("Errors"), self.errores)) + if self.siAyuda: + li.append(_("Help activated")) + self.ponRotulo1("\n".join(li)) + + tgm = 0 + for tr in self.trposition["TRIES"]: + tgm += tr["TIME"] + + mas = time.time() - self.ini_time + + mens = "\n" + "\n".join(self.liMensBasic) + mens += "\n%s:\n %s %s\n %s %s" % (_("Working time"), + time.strftime("%H:%M:%S", time.gmtime(tgm+mas)), _("Current"), + time.strftime("%H:%M:%S", time.gmtime(self.tm+mas)), _("Total")) + + self.ponRotulo2(mens) + + def posicionTerminada(self): + tm = time.time() - self.ini_time + li = [_("Finished.")] + if self.siAyuda: + li.append(_("Help activated")) + if self.errores > 0: + li.append("%s: %d" % (_("Errors"), self.errores)) + + QTUtil2.mensajeTemporal(self.pantalla, "\n".join(li), 1.2) + + dictry = { + "DATE": Util.hoy(), + "TIME": tm, + "AYUDA": self.siAyuda, + "ERRORS": self.errores + } + self.trposition["TRIES"].append(dictry) + + sinError = self.errores == 0 and not self.siAyuda + if sinError: + self.trposition["NOERROR"] += 1 + else: + self.trposition["NOERROR"] = max(0, self.trposition["NOERROR"]-1) + noError = self.trposition["NOERROR"] + salto = 2**(noError + 1) + 1 + numPosics = len(self.liTrainPositions) + for x in range(salto, numPosics): + posic = self.liTrainPositions[x] + if posic["NOERROR"] != noError: + salto = x + break + + liNuevo = self.liTrainPositions[1:salto] + liNuevo.append(self.trposition) + if numPosics > salto: + liNuevo.extend(self.liTrainPositions[salto:]) + self.training["LITRAINPOSITIONS"] = liNuevo + self.pantalla.ponToolBar((k_mainmenu, k_siguiente)) + + self.dbop.setTraining(self.training) + self.estado = kFinJuego + self.muestraInformacion() + + def muestraAyuda(self): + liMoves = self.trposition["MOVES"] + for pv in liMoves: + self.tablero.creaFlechaMov(pv[:2], pv[2:4], "mt80") + + def procesarAccion(self, clave): + if clave == k_mainmenu: + self.finPartida() + + elif clave == k_reiniciar : + self.reiniciar() + + elif clave == k_configurar: + self.configurar(siSonidos=True) + + elif clave == k_utilidades: + self.utilidades() + + elif clave == k_siguiente: + self.reinicio(self.dbop) + + elif clave == k_ayuda: + self.ayuda() + + else: + Gestor.Gestor.rutinaAccionDef(self, clave) + + def finalX(self): + return self.finPartida() + + def finPartida(self): + self.dbop.close() + self.procesador.inicio() + self.procesador.openings() + return False + + def reiniciar(self): + self.reinicio(self.dbop) + + def siguienteJugada(self): + self.muestraInformacion() + if self.estado == kFinJuego: + return + + self.estado = kJugando + + self.siJuegaHumano = False + self.ponVista() + + siBlancas = self.partida.ultPosicion.siBlancas + + self.ponIndicador(siBlancas) + self.refresh() + + self.activaColor(siBlancas) + self.siJuegaHumano = True + if self.siAyuda: + self.muestraAyuda() + + def mueveHumano(self, desde, hasta, coronacion=""): + jg = self.checkMueveHumano(desde, hasta, coronacion) + if not jg: + return False + pvSel = desde + hasta + coronacion + lipvObj = self.trposition["MOVES"] + + if pvSel not in lipvObj: + self.errores += 1 + mens = "%s: %d" % (_("Error"), self.errores) + QTUtil2.mensajeTemporal(self.pantalla, mens, 2, posicion="ad", background="#FF9B00") + self.muestraInformacion() + self.sigueHumano() + return False + + self.movimientosPiezas(jg.liMovs) + + self.masJugada(jg, True) + self.posicionTerminada() + return True + + def masJugada(self, jg, siNuestra): + self.partida.append_jg(jg) + if self.partida.pendienteApertura: + self.partida.asignaApertura() + + self.ponFlechaSC(jg.desde, jg.hasta) + self.beepExtendido(siNuestra) + + self.pgnRefresh(self.partida.ultPosicion.siBlancas) + self.refresh() + + self.ponPosicionDGT() \ No newline at end of file diff --git a/Code/GestorPGN.py b/Code/GestorPGN.py index 6289032..ba24e05 100644 --- a/Code/GestorPGN.py +++ b/Code/GestorPGN.py @@ -193,7 +193,7 @@ def miniatura(self): def mostrar(self, pgn, siRepiteFichero, siBlancas=None): self.pensando(True) self.partida.leeOtra(pgn.partida) - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() blancas = pgn.variable("WHITE") negras = pgn.variable("BLACK") resultado = pgn.variable("RESULT") diff --git a/Code/GestorPartida.py b/Code/GestorPartida.py index 96418e2..efcad16 100644 --- a/Code/GestorPartida.py +++ b/Code/GestorPartida.py @@ -184,7 +184,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: @@ -349,8 +349,7 @@ def configurarGS(self): return p = Partida.PartidaCompleta() p.leeOtra(partida) - if self.siCompleta: - p.asignaApertura(self.configuracion) + p.asignaApertura() p.setTags(unpgn.listaCabeceras()) self.reinicio = p.save() self.reiniciar() @@ -368,8 +367,7 @@ def configurarGS(self): return p = Partida.PartidaCompleta() p.leeOtra(partida) - if self.siCompleta: - p.asignaApertura(self.configuracion) + p.asignaApertura() p.setTags(unpgn.listaCabeceras()) self.reinicio = p.save() self.reiniciar() @@ -438,7 +436,7 @@ def atras(self): if self.partida.numJugadas(): self.partida.anulaSoloUltimoMovimiento() if not self.fen: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponteAlFinal() self.estado = kJugando self.refresh() diff --git a/Code/GestorPerson.py b/Code/GestorPerson.py index cb94dcd..aea2165 100644 --- a/Code/GestorPerson.py +++ b/Code/GestorPerson.py @@ -299,7 +299,7 @@ def atras(self): self.ayudas -= 1 self.partida.anulaUltimoMovimiento(self.siJugamosConBlancas) if not self.fen: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponteAlFinal() self.refresh() self.siguienteJugada() @@ -422,7 +422,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: diff --git a/Code/GestorPlayPGN.py b/Code/GestorPlayPGN.py index 6adeb38..2c60080 100644 --- a/Code/GestorPlayPGN.py +++ b/Code/GestorPlayPGN.py @@ -275,7 +275,7 @@ def masJugada(self, siNuestra, analisis=None, comentario=None): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.movimientosPiezas(jg.liMovs, True) self.tablero.ponPosicion(jg.posicion) self.ponFlechaSC(jg.desde, jg.hasta) diff --git a/Code/GestorResistance.py b/Code/GestorResistance.py index 91db0bb..fda1525 100644 --- a/Code/GestorResistance.py +++ b/Code/GestorResistance.py @@ -34,7 +34,6 @@ def inicio(self, resistance, numEngine, clave): self.apertura = Apertura.AperturaPol(5) # lee las aperturas # debe hacerse antes que rival - # arbitro = self.configuracion.buscaRival( "stockfish" ) self.xarbitro = self.procesador.creaGestorMotor(self.configuracion.tutor, self.segundos * 1000, None) self.xarbitro.anulaMultiPV() @@ -289,7 +288,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: diff --git a/Code/GestorRoutes.py b/Code/GestorRoutes.py index b14aec9..37c3076 100644 --- a/Code/GestorRoutes.py +++ b/Code/GestorRoutes.py @@ -1,7 +1,7 @@ import random import time -import LCEngine +import LCEngineV1 as LCEngine from Code import Util from Code import Books @@ -141,7 +141,7 @@ def finalX(self): def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: diff --git a/Code/GestorSolo.py b/Code/GestorSolo.py index b3467f9..2562198 100644 --- a/Code/GestorSolo.py +++ b/Code/GestorSolo.py @@ -152,12 +152,12 @@ def inicio(self, dic=None, fichero=None, pgn=None, jugadaInicial=None, siGrabar= if self.bloqueApertura: self.partida.reset() self.partida.leerPV(self.bloqueApertura.a1h8) - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() if "PARTIDA" in dic: self.partida.reset() self.partida.recuperaDeTexto(dic["PARTIDA"]) - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() cp = self.partida.iniPosicion # Para ver si las blancas abajo self.ponToolBar(siExterno, siGrabar) @@ -383,7 +383,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: @@ -585,9 +585,11 @@ def file(self): menu = QTVarios.LCMenu(self.pantalla) if self.ultimoFichero: menuR = menu.submenu(_("Save"), Iconos.Grabar()) - rpath = os.path.relpath(self.ultimoFichero) - if rpath.count("..") > 0: - rpath = self.ultimoFichero + rpath = self.ultimoFichero + if os.curdir[:1] == rpath[:1]: + rpath = os.path.relpath(rpath) + if rpath.count("..") > 0: + rpath = self.ultimoFichero menuR.opcion("save", "%s: %s" %( _("Save"), rpath), Iconos.Grabar()) menuR.separador() menuR.opcion("saveas", _("Save as"), Iconos.GrabarComo()) @@ -926,7 +928,7 @@ def atras(self): if self.partida.numJugadas(): self.partida.anulaSoloUltimoMovimiento() if not self.fen: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponteAlFinal() self.estado = kJugando self.refresh() diff --git a/Code/GestorTorneo.py b/Code/GestorTorneo.py index be55974..07a627a 100644 --- a/Code/GestorTorneo.py +++ b/Code/GestorTorneo.py @@ -4,7 +4,6 @@ from Code import Partida from Code.QT import PantallaTorneos from Code import Util -from Code import VarGen from Code import XGestorMotor from Code.Constantes import * @@ -17,7 +16,6 @@ def inicio(self, torneo, liGames): self.torneo = torneo self.torneoTMP = torneo.clone() self.torneoTMP._liGames = liGames - self.fenInicial = self.torneo.fenNorman() self.liGames = liGames self.pantalla.ponActivarTutor(False) self.ponPiezasAbajo(True) @@ -33,6 +31,7 @@ def inicio(self, torneo, liGames): numGames = len(self.liGames) for ng, gm in enumerate(self.liGames): self.siguienteJuego(gm, ng + 1, numGames) + self.procesador.pararMotores() if self.siTerminar: break if self.wresult: @@ -43,6 +42,7 @@ def inicio(self, torneo, liGames): def siguienteJuego(self, gm, ngame, numGames): self.estado = kJugando + self.fenInicial = self.torneo.fenNorman() self.gm = gm self.maxSegundos = self.gm.minutos() * 60.0 @@ -71,8 +71,8 @@ def siguienteJuego(self, gm, ngame, numGames): bk = rv.book() if bk == "*": - bk = VarGen.tbook - elif bk == "-": + bk = self.torneo.book() + if bk == "-": # Puede que el torneo tenga "-" bk = None if bk: self.book[color] = Books.Libro("P", bk, bk, True) @@ -118,8 +118,6 @@ def guiDispatch(self, rm): txt = "[%s] (%s) %s" % (rm.nombre, rm.abrTexto(), p.pgnSP()) self.ponRotulo3(txt) self.showPV(rm.pv, 3) - # self.ponFlechaSC( rm.pv[:2], rm.pv[2:4] ) - # QTUtil.refreshGUI() self.refresh() return not self.siTerminar @@ -300,7 +298,7 @@ def masJugada(self, jg): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: diff --git a/Code/GestorTurnOnLights.py b/Code/GestorTurnOnLights.py index 4cb51e9..b4bba1e 100644 --- a/Code/GestorTurnOnLights.py +++ b/Code/GestorTurnOnLights.py @@ -26,6 +26,7 @@ def inicio(self, num_theme, num_block, tol): self.calculation_mode = self.tol.is_calculation_mode() self.penaltyError = self.block.penaltyError(self.calculation_mode) self.penaltyHelp = self.block.penaltyHelp(self.calculation_mode) + # self.factorDistancia = self.block.factorDistancia() # No se usa es menor que 1.0 self.av_seconds = self.block.av_seconds() if self.av_seconds: @@ -167,8 +168,9 @@ def siguienteJugada(self): else: self.siJuegaHumano = True + self.base_time = time.time() if not (self.calculation_mode and self.ini_time is None): # Se inicia salvo que sea el principio de la linea - self.ini_time = time.time() + self.ini_time = self.base_time self.activaColor(siBlancas) if self.calculation_mode: self.tablero.setDispatchMove(self.dispatchMove) @@ -250,6 +252,8 @@ def finLinea(self): self.pantalla.ponToolBar(liOpciones) def mueveHumano(self, desde, hasta, coronacion=None): + if self.ini_time is None: + self.ini_time = self.base_time end_time = time.time() jg = self.checkMueveHumano(desde, hasta, coronacion) if not jg: diff --git a/Code/GestorVariantes.py b/Code/GestorVariantes.py index 304397d..fcbf28c 100644 --- a/Code/GestorVariantes.py +++ b/Code/GestorVariantes.py @@ -136,7 +136,7 @@ def atras(self): if self.partida.numJugadas(): self.partida.anulaSoloUltimoMovimiento() if not self.fen: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponteAlFinal() self.refresh() self.siguienteJugada() @@ -211,7 +211,7 @@ def masJugada(self, jg): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: diff --git a/Code/GestorWashing.py b/Code/GestorWashing.py index 721fa57..bc5904d 100644 --- a/Code/GestorWashing.py +++ b/Code/GestorWashing.py @@ -216,7 +216,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponFlechaSC(jg.desde, jg.hasta) self.beepExtendido(siNuestra) @@ -728,7 +728,7 @@ def masJugada(self, jg, siNuestra): self.partida.append_jg(jg) if self.partida.pendienteApertura: - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() resp = self.partida.si3repetidas() if resp: @@ -815,7 +815,7 @@ def atras(self): if self.partida.numJugadas(): self.analizaTerminar() self.partida.anulaUltimoMovimiento(self.siJugamosConBlancas) - self.listaAperturasStd.asignaApertura(self.partida) + self.partida.asignaApertura() self.ponteAlFinal() self.apertura = Apertura.AperturaPol(30, self.engine.elo) self.siAnalizadoTutor = False diff --git a/Code/Init.py b/Code/Init.py index c3cbca0..7aab268 100644 --- a/Code/Init.py +++ b/Code/Init.py @@ -9,7 +9,7 @@ from Code.Constantes import * DEBUG = False -VERSION = "11.05b" +VERSION = "11.06" if DEBUG: prlkn("DEBUG " * 20) diff --git a/Code/Jugada.py b/Code/Jugada.py index 094e7a4..eadc61d 100644 --- a/Code/Jugada.py +++ b/Code/Jugada.py @@ -113,14 +113,18 @@ def pgnSP(self): def pgnFigurinesSP(self): return self.pgnBase + self.resultadoSP() - def pgnHTML(self): + def pgnHTML(self, siFigurines): siBlancas = self.siBlancas() - li = [] - for c in self.pgnBase: - if c in "NBRQK": - c = dicHTMLFigs[c if siBlancas else c.lower()] - li.append(c) - resp = "".join(li) + self.resto() + if siFigurines: + li = [] + for c in self.pgnBase: + if c in "NBRQK": + c = dicHTMLFigs[c if siBlancas else c.lower()] + li.append(c) + resp = "".join(li) + else: + resp = self.pgnBaseSP() + resp += self.resto() result = self.resultado() if result: resp += " " + result @@ -427,6 +431,9 @@ def calc_elo(self, perfomance): self.bad_move = False self.verybad_move = False + def distancia(self): + return ControlPosicion.distancia(self.desde, self.hasta) + def dameJugada(posicionBase, desde, hasta, coronacion): posicion = posicionBase.copia() diff --git a/Code/LibChess.py b/Code/LibChess.py index fc7f546..ba0d14d 100644 --- a/Code/LibChess.py +++ b/Code/LibChess.py @@ -1,6 +1,6 @@ import random -import LCEngine +import LCEngineV1 as LCEngine import chess import chess.syzygy diff --git a/Code/Memoria.py b/Code/Memoria.py index 537ba65..bf91196 100644 --- a/Code/Memoria.py +++ b/Code/Memoria.py @@ -84,7 +84,6 @@ def lanzaNivel(self, numcategoria, nivel): return False def dameListaFen(self, piezas): - me = self.procesador.unMomento() li = [] diff --git a/Code/OpeningGuide.py b/Code/OpeningGuide.py index 743430e..b1e773c 100644 --- a/Code/OpeningGuide.py +++ b/Code/OpeningGuide.py @@ -2,7 +2,7 @@ import os import sqlite3 -import LCEngine +import LCEngineV1 as LCEngine from Code import AperturasStd from Code import Books diff --git a/Code/OpeningLines.py b/Code/OpeningLines.py index 2f05bec..72de83a 100644 --- a/Code/OpeningLines.py +++ b/Code/OpeningLines.py @@ -1,26 +1,47 @@ import os import sqlite3 -import atexit +import random -import LCEngine +import LCEngineV1 as LCEngine from Code import Util from Code import Partida from Code import PGNreader from Code import Books +from Code import DBgames from Code.QT import QTVarios from Code.QT import QTUtil2 - class ListaOpenings: def __init__(self, configuracion): self.folder = configuracion.folderOpenings - self.fichero = os.path.join(self.folder, "index.pk") + self.fichero = os.path.join(self.folder, "openinglines.pk") self.lista = Util.recuperaVar(self.fichero) if self.lista is None: - self.lista = self.read() + self.lista = self.read() # file, lines, title, pv self.save() + else: + self.testdates() + + def testdates(self): + index_date = Util.datefile(self.fichero) + + for pos, dic in enumerate(self.lista): + pathfile = os.path.join(self.folder, dic["file"]) + file_date = Util.datefile(pathfile) + if file_date is None: + self.reiniciar() + break + if file_date > index_date: + op = Opening(pathfile) + self.lista[pos]["lines"] = len(op) + op.close() + self.save() + + def reiniciar(self): + self.lista = self.read() + self.save() def __len__(self): return len(self.lista) @@ -29,9 +50,9 @@ def __getitem__(self, item): return self.lista[item] if self.lista and item < len(self.lista) else None def __delitem__(self, item): - name, pv, tit = self.lista[item] + dicline = self.lista[item] del self.lista[item] - os.remove(os.path.join(self.folder, name)) + os.remove(os.path.join(self.folder, dicline["file"])) self.save() def arriba(self, item): @@ -50,36 +71,20 @@ def abajo(self, item): else: return False - def refresh(self): - dic = {} - for entry in Util.listdir(self.folder): - if entry.name.endswith(".opk"): - op = Opening(entry.path) - dic[entry.name] = (entry.name, op.basePV, op.title) - op.close() - liborrar = [] - st_esta = set() - for n, (fichero, basePV, title) in enumerate(self.lista): - if fichero in dic: - self.lista[n] = dic[fichero] - st_esta.add(fichero) - else: - liborrar.append(n) - for x in range(len(liborrar)-1, -1, -1): - del self.lista[x] - - for fichero in dic: - if fichero not in st_esta: - self.lista.append(dic[fichero]) - self.save() - def read(self): li = [] for entry in Util.listdir(self.folder): fichero = entry.name if fichero.endswith(".opk"): op = Opening(entry.path) - li.append((fichero, op.basePV, op.title)) + dicline = { + "file": fichero, + "pv": op.basePV, + "title": op.title, + "lines": len(op), + "withtrainings": op.withTrainings() + } + li.append(dicline) op.close() return li @@ -88,9 +93,8 @@ def save(self): def select_filename(self, name): name = name.strip().replace(" ", "_") - name = Util.validNomFichero(name) - while "__" in name: - name = name.replace("__", "_") + name = Util.asciiNomFichero(name) + plant = name + "%d.opk" file = name + ".opk" num = 0 @@ -100,10 +104,17 @@ def select_filename(self, name): return file def filepath(self, num): - return os.path.join(self.folder, self.lista[num][0]) + return os.path.join(self.folder, self.lista[num]["file"]) def new(self, file, basepv, title): - self.lista.append((file, basepv, title)) + dicline = { + "file": file, + "pv": basepv, + "title": title, + "lines": 0, + "withtrainings": False + } + self.lista.append(dicline) op = Opening(self.filepath(len(self.lista)-1)) op.setbasepv(basepv) op.settitle(title) @@ -114,11 +125,14 @@ def change_title(self, num, title): op = Opening(self.filepath(num)) op.settitle(title) op.close() - file, pv, ant_title = self.lista[num] - self.lista[num] = (file, pv, title) + self.lista[num]["title"] = title - def remove(self, lines): - pass + def add_training_file(self, file): + for dicline in self.lista: + if file == dicline["file"]: + dicline["withtrainings"] = True + self.save() + return class Opening: @@ -126,7 +140,6 @@ def __init__(self, nomFichero): self.nomFichero = nomFichero self._conexion = sqlite3.connect(nomFichero) - atexit.register(self.close) self.cache = {} self.max_cache = 4000 @@ -141,10 +154,16 @@ def __init__(self, nomFichero): self.basePV = self.getconfig("BASEPV", "") self.title = self.getconfig("TITLE", os.path.basename(nomFichero).split(".")[0]) - def getOtras(self, configuracion): + self.tablero = None + + def setdbVisual_Tablero(self, tablero): + self.tablero = tablero + + def getOtras(self, configuracion, partida): liOp = ListaOpenings(configuracion) fich = os.path.basename(self.nomFichero) - liOp = [(fichero, titulo) for fichero, pv, titulo in liOp.lista if fichero != fich and pv.startswith(self.basePV)] + pvbase = partida.pv() + liOp = [(dic["file"], dic["title"]) for dic in liOp.lista if dic["file"] != fich and (pvbase.startswith(dic["pv"]) or dic["pv"].startswith(pvbase))] return liOp def getfenvalue(self, fenM2): @@ -154,12 +173,200 @@ def getfenvalue(self, fenM2): def setfenvalue(self, fenM2, dic): self.db_fenvalues[fenM2] = dic + def removeAnalisis(self, tmpBP, mensaje): + for n, fenM2 in enumerate(self.db_fenvalues.keys()): + tmpBP.inc() + tmpBP.mensaje(mensaje%n) + if tmpBP.siCancelado(): + break + dic = self.getfenvalue(fenM2) + if "ANALISIS" in dic: + del dic["ANALISIS"] + self.setfenvalue(fenM2, dic) + self.packAlTerminar() + def getconfig(self, key, default=None): return self.db_config.get(key, default) def setconfig(self, key, value): self.db_config[key] = value + def training(self): + return self.getconfig("TRAINING") + + def setTraining(self, reg): + return self.setconfig("TRAINING", reg) + + def preparaTraining(self, reg): + lilipv = [LCEngine.xpv2pv(xpv).split(" ") for xpv in self.li_xpv] + maxmoves = reg["MAXMOVES"] + if maxmoves: + for num, lipv in enumerate(lilipv): + if len(lipv) > maxmoves: + lilipv[num] = lipv[:maxmoves] + + siBlancas = reg["COLOR"] == "WHITE" + + if reg["RANDOM"]: + random.shuffle(lilipv) + + ligamesST = [] + ligamesSQ = [] + stPV = set() + dicFENm2 = {} + for lipv in lilipv: + # Siempre termina el usuario + if len(lipv) % 2 == (0 if siBlancas else 1): + lipv = lipv[:-1] + + # Duplicados + pv = "".join(lipv) + if pv in stPV: + continue + stPV.add(pv) + + game = {} + game["LIPV"] = lipv + game["NOERROR"] = 0 + game["TRIES"] = [] + + ligamesST.append(game) + game = dict(game) + ligamesSQ.append(game) + LCEngine.setFenInicial() + for pv in lipv: + fen = LCEngine.getFen() + fenM2 = LCEngine.fen2fenM2(fen) + if fenM2 not in dicFENm2: + dicFENm2[fenM2] = set() + dicFENm2[fenM2].add(pv) + LCEngine.makeMove(pv) + + reg["LIGAMES_STATIC"] = ligamesST + if reg["RANDOM"]: + random.shuffle(ligamesSQ) + reg["LIGAMES_SEQUENTIAL"] = ligamesSQ + reg["DICFENM2"] = dicFENm2 + + bcolor = " w " if siBlancas else " b " + liTrainPositions = [] + for fenM2 in dicFENm2: + if bcolor in fenM2: + data = {} + data["FENM2"] = fenM2 + data["MOVES"] = dicFENm2[fenM2] + data["NOERROR"] = 0 + data["TRIES"] = [] + liTrainPositions.append(data) + random.shuffle(liTrainPositions) + reg["LITRAINPOSITIONS"] = liTrainPositions + + def createTraining(self, reg, configuracion): + self.preparaTraining(reg) + + reg["DATECREATION"] = Util.hoy() + self.setconfig("TRAINING", reg) + self.setconfig("ULT_PACK", 100) # Se le obliga al VACUUM + + lo = ListaOpenings(configuracion) + lo.add_training_file(os.path.basename(self.nomFichero)) + + def withTrainings(self): + return "TRAINING" in self.db_config + + def updateTraining(self): + reg = self.training() + reg1 = {} + for key in ("MAXMOVES", "COLOR", "RANDOM"): + reg1[key] = reg[key] + self.preparaTraining(reg1) + + for tipo in ("LIGAMES_SEQUENTIAL", "LIGAMES_STATIC"): + # Los que estan pero no son, los borramos + liBorrados = [] + for pos, game in enumerate(reg[tipo]): + pv = " ".join(game["LIPV"]) + ok = False + for game1 in reg1[tipo]: + pv1 = " ".join(game1["LIPV"]) + if pv == pv1: + ok = True + break + if not ok: + liBorrados.append(pos) + if liBorrados: + li = reg[tipo] + liBorrados.sort(reverse=True) + for x in liBorrados: + del li[x] + reg[tipo] = li + + # Los que son pero no estan + liMas = [] + for game1 in reg1[tipo]: + pv1 = " ".join(game1["LIPV"]) + ok = False + for game in reg[tipo]: + pv = " ".join(game["LIPV"]) + if pv == pv1: + ok = True + break + if not ok: + liMas.append(game1) + if liMas: + li = reg[tipo] + liMas.sort(reverse=True) + for game in liMas: + li.insert(0, game) + reg[tipo] = li + + reg["DICFENM2"] = reg1["DICFENM2"] + + # Posiciones + + # Estan pero no son + liBorrados = [] + tipo = "LITRAINPOSITIONS" + for pos, data in enumerate(reg[tipo]): + fen = data["FENM2"] + ok = False + for data1 in reg1[tipo]: + fen1 = data1["FENM2"] + if fen == fen1: + ok = True + break + if not ok: + liBorrados.append(pos) + if liBorrados: + li = reg[tipo] + liBorrados.sort(reverse=True) + for x in liBorrados: + del li[x] + reg[tipo] = li + + # Los que son pero no estan + liMas = [] + for data1 in reg1[tipo]: + fen1 = data1["FENM2"] + ok = False + for data in reg[tipo]: + fen = data["FENM2"] + if fen == fen1: + ok = True + break + if not ok: + liMas.append(data) + if liMas: + li = reg[tipo] + li.insert(0, liMas) + reg[tipo] = li + + self.setconfig("TRAINING", reg) + self.packAlTerminar() + + def packAlTerminar(self): + self.setconfig("ULT_PACK", 100) # Se le obliga al VACUUM + def settitle(self, title): self.setconfig("TITLE", title) @@ -199,6 +406,7 @@ def init_database(self): sql = "select XPV from LINES ORDER BY XPV" cursor.execute(sql) li_xpv = [ raw[0] for raw in cursor.fetchall()] + cursor.close() return li_xpv def append(self, partida): @@ -246,6 +454,7 @@ def __setitem__(self, num, partida_nue): cursor.execute(sql, (xpv_nue, partida_nue.save2blob(), xpv_ant)) self._conexion.commit() self.add_cache(xpv_nue, partida_nue) + cursor.close() return num def __getitem__(self, num): @@ -260,6 +469,7 @@ def __getitem__(self, num): partida = Partida.Partida() partida.blob2restore(blob) self.add_cache(xpv, partida) + cursor.close() return partida def __delitem__(self, num): @@ -271,6 +481,7 @@ def __delitem__(self, num): del self.cache[xpv] del self.li_xpv[num] self._conexion.commit() + cursor.close() def __len__(self): return len(self.li_xpv) @@ -285,6 +496,13 @@ def close(self): self.db_config.close() self.db_config = None + self.db_fenvalues.close() + self.db_fenvalues = None + + if self.tablero: + self.tablero.dbVisual_close() + self.tablero = None + if si_pack: cursor = conexion.cursor() cursor.execute("VACUUM") @@ -293,17 +511,15 @@ def close(self): conexion.close() - def grabarPGN(self, owner, ficheroPGN, maxDepth): + def importarPGN(self, owner, partidabase, ficheroPGN, maxDepth): erroneos = duplicados = importados = 0 dlTmp = QTVarios.ImportarFicheroPGN(owner) dlTmp.hideDuplicados() dlTmp.show() - sql = "INSERT INTO LINES( XPV, LINE ) VALUES( ?, ? )" cursor = self._conexion.cursor() base = self.getconfig("BASEPV") - partidabase = self.getpartidabase() njugbase = partidabase.numJugadas() n = 0 @@ -320,9 +536,9 @@ def grabarPGN(self, owner, ficheroPGN, maxDepth): continue def haz_partida(partida, liMoves): - njg = partida.numJugadas() + njg = len(partida) if len(liMoves) + njg > maxDepth: - liMoves = liMoves[:maxDepth+njg] + liMoves = liMoves[:maxDepth - njg] pv = " ".join([move.pv for move in liMoves]) partida.leerPV(pv) pv = partida.pv() @@ -351,7 +567,7 @@ def haz_partida(partida, liMoves): partida = Partida.Partida() haz_partida(partida, g.moves.liMoves) - if n %1000: + if n % 50: self._conexion.commit() cursor.close() @@ -361,7 +577,32 @@ def haz_partida(partida, liMoves): dlTmp.actualiza(n, erroneos, duplicados, importados) dlTmp.ponContinuar() - def grabarPolyglot(self, ventana, ficheroBIN, depth, whiteBest, blackBest): + def guardaPartidas(self, liPartidas): + partidabase = self.getpartidabase() + sql_insert = "INSERT INTO LINES( XPV, LINE ) VALUES( ?, ? )" + sql_update = "UPDATE LINES SET XPV=?, LINE=? WHERE XPV=?" + cursor = self._conexion.cursor() + for partida in liPartidas: + if partida.numJugadas() > partidabase.numJugadas(): + xpv = LCEngine.pv2xpv(partida.pv()) + if xpv not in self.li_xpv: + line_blob = partida.save2blob() + updated = False + for npos, xpv_ant in enumerate(self.li_xpv): + if xpv.startswith(xpv_ant): + cursor.execute(sql_update, (xpv, line_blob, xpv_ant)) + self.li_xpv[npos] = xpv + updated = True + break + if not updated: + cursor.execute(sql_insert, (xpv, line_blob)) + self.li_xpv.append(xpv) + + cursor.close() + self._conexion.commit() + self.li_xpv.sort() + + def importarPolyglot(self, ventana, partidabase, ficheroBIN, depth, siWhite): titulo = _("Import a polyglot book") bp = QTUtil2.BarraProgreso1(ventana, titulo) bp.ponTotal(0) @@ -371,9 +612,6 @@ def grabarPolyglot(self, ventana, ficheroBIN, depth, whiteBest, blackBest): book = Books.Libro("P", ficheroBIN, ficheroBIN, True) book.polyglot() - stFenM2 = set() # para que no se produzca un circulo vicioso - - partidabase = self.getpartidabase() cp = partidabase.ultPosicion liPartidas = [] @@ -381,71 +619,112 @@ def grabarPolyglot(self, ventana, ficheroBIN, depth, whiteBest, blackBest): setFen = LCEngine.setFen makeMove = LCEngine.makeMove getFen = LCEngine.getFen - fen2fenM2 = LCEngine.fen2fenM2 - - def hazFEN(fen, ply, lipv_ant): - plyN = ply + 1 - siWhite = " w " in fen - siMax = False - if whiteBest: - siMax = siWhite - if blackBest: - siMax = siMax or not siWhite - - liPV = book.miraListaPV(fen, siMax) - if liPV: - sigue = False + + def hazFEN(fen, lipv_ant): + if bp.siCancelado(): + return + siWhite1 = " w " in fen + + liPV = book.miraListaPV(fen, siWhite1 == siWhite) + if liPV and len(lipv_ant) < depth: for pv in liPV: setFen(fen) makeMove(pv) fenN = getFen() - lipv_nue = lipv_ant[:] lipv_nue.append(pv) - if plyN < depth: - fenM2 = fen2fenM2(fenN) - if fenM2 not in stFenM2: - stFenM2.add(fenM2) - hazFEN(fenN, plyN, lipv_nue) - sigue = True + hazFEN(fenN, lipv_nue) else: - sigue = False - if not sigue: p = Partida.Partida() - p.leerPV(" ".join(lipv_ant)) + p.leerLIPV(lipv_ant) liPartidas.append(p) bp.ponTotal(len(liPartidas)) bp.pon(len(liPartidas)) - hazFEN(cp.fen(), 0, partidabase.lipv()) + hazFEN(cp.fen(), partidabase.lipv()) bp.ponRotulo(_("Writing...")) + self.guardaPartidas(liPartidas) + bp.cerrar() - sql_insert = "INSERT INTO LINES( XPV, LINE ) VALUES( ?, ? )" - sql_update = "UPDATE LINES SET XPV=?, LINE=? WHERE XPV=?" - cursor = self._conexion.cursor() - for partida in liPartidas: - if partida.numJugadas() > partidabase.numJugadas(): - xpv = LCEngine.pv2xpv(partida.pv()) - if xpv not in self.li_xpv: - line_blob = partida.save2blob() - updated = False - for npos, xpv_ant in enumerate(self.li_xpv): - if xpv.startswith(xpv_ant): - cursor.execute(sql_update, (xpv, line_blob, xpv_ant)) - self.li_xpv[npos] = xpv - updated = True - break - if not updated: - cursor.execute(sql_insert, (xpv, line_blob)) - self.li_xpv.append(xpv) + return True - cursor.close() - self._conexion.commit() - self.li_xpv.sort() + def importarSummary(self, ventana, partidabase, ficheroSummary, depth, siWhite): + titulo = _("Importing the summary of a database") + bp = QTUtil2.BarraProgreso1(ventana, titulo) + bp.ponTotal(0) + bp.ponRotulo(_X(_("Reading %1"), os.path.basename(ficheroSummary))) + bp.mostrar() + dbSTAT = DBgames.TreeSTAT(ficheroSummary) + + if depth == 0: + depth = 99999 + + pvBase = partidabase.pv() + len_partidabase = len(partidabase) + + liPartidas = [] + + def hazPV(lipv_ant): + if bp.siCancelado(): + return + siWhite1 = len(lipv_ant) % 2 == 0 + + pv_ant = " ".join(lipv_ant) + liChildren = dbSTAT.children(pv_ant, False) + + if len(liChildren) == 0 or len(lipv_ant) > depth: + p = Partida.Partida() + p.leerLIPV(lipv_ant) + if len(p) > len_partidabase: + liPartidas.append(p) + bp.ponTotal(len(liPartidas)) + bp.pon(len(liPartidas)) + return + + if siWhite1 == siWhite: + alm_max = None + tt_max = 0 + for alm in liChildren: + tt = alm.W + alm.B + alm.O + alm.D + if tt > tt_max: + tt_max = tt + alm_max = alm + liChildren = [] if tt_max == 0 else [alm_max,] + + for alm in liChildren: + li = lipv_ant[:] + li.append(alm.move) + hazPV(li) + + hazPV(pvBase.split(" ")) + + bp.ponRotulo(_("Writing...")) + self.guardaPartidas(liPartidas) bp.cerrar() return True - + def importarOtra(self, pathFichero, partida): + xpvbase = LCEngine.pv2xpv(partida.pv()) + tambase = len(xpvbase) + otra = Opening(pathFichero) + liPartidas = [] + for n, xpv in enumerate(otra.li_xpv): + if xpv.startswith(xpvbase) and tambase < len(xpv): + liPartidas.append(otra[n]) + otra.close() + self.guardaPartidas(liPartidas) + + def getAllFen(self): + stFENm2 = set() + lilipv = [LCEngine.xpv2pv(xpv).split(" ") for xpv in self.li_xpv] + for lipv in lilipv: + LCEngine.setFenInicial() + for pv in lipv: + fen = LCEngine.getFen() + fenM2 = LCEngine.fen2fenM2(fen) + stFENm2.add(fenM2) + LCEngine.makeMove(pv) + return stFENm2 diff --git a/Code/PGNreader.py b/Code/PGNreader.py index cbacb41..e35ca3f 100644 --- a/Code/PGNreader.py +++ b/Code/PGNreader.py @@ -1,4 +1,4 @@ -import LCEngine +import LCEngineV1 as LCEngine from Code import Util diff --git a/Code/Partida.py b/Code/Partida.py index 5ac10d2..0a5a854 100644 --- a/Code/Partida.py +++ b/Code/Partida.py @@ -42,7 +42,7 @@ def jugada(self, num): try: return self.liJugadas[num] except: - return None + return self.liJugadas[-1] if len(self) > 0 else None def append_jg(self, jg): self.liJugadas.append(jg) @@ -115,9 +115,12 @@ def si3repetidas(self): return None def leerPV(self, pvBloque): + return self.leerLIPV(pvBloque.split(" ")) + + def leerLIPV(self, lipv): posicion = self.ultPosicion pv = [] - for mov in pvBloque.split(" "): + for mov in lipv: if len(mov) >= 4 and mov[0] in "abcdefgh" and mov[1] in "12345678" and mov[2] in "abcdefgh" \ and mov[3] in "12345678": pv.append(mov) @@ -266,7 +269,7 @@ def pgnHTML(self, numJugada=None, hastaJugada=9999, siFigurines=True): numJugada += 1 else: x = "" - liResp.append(x + (jg.pgnHTML() if siFigurines else jg.pgnSP())) + liResp.append(x + (jg.pgnHTML(siFigurines))) return " ".join(liResp) def siTerminada(self): @@ -474,6 +477,27 @@ def calc_elos(self, configuracion): return elos + def asignaApertura(self): + AperturasStd.ap.asignaApertura(self) + + def asignaTransposition(self): + AperturasStd.ap.asignaTransposition(self) + + def rotuloApertura(self): + return self.apertura.trNombre if hasattr(self, "apertura") and self.apertura is not None else None + + def rotuloTransposition(self): + if hasattr(self, "transposition"): + ap = self.transposition + if ap is not None: + return "%s %s" % (self.jg_transposition.pgnSP(), ap.trNombre) + return None + + def test_apertura(self): + if not hasattr(self, "apertura") or self.pendienteApertura: + self.asignaApertura() + self.asignaTransposition() + def pv_san(fen, pv): p = Partida(fen=fen) @@ -529,17 +553,11 @@ def readPGN(self, configuracion, pgn): if not unpgn.leeTexto(pgn): return None self.recuperaDeTexto(unpgn.partida.guardaEnTexto()) - self.asignaApertura(configuracion) + self.asignaApertura() self.liTags = unpgn.listaCabeceras() return self - def asignaApertura_raw(self, ap): - ap.asignaApertura(self) - - def asignaApertura(self, configuracion): - AperturasStd.ap.asignaApertura(self) - def pgn(self): li = ['[%s "%s"]\n'%(k,v) for k,v in self.liTags] txt = "".join(li) diff --git a/Code/Presentacion.py b/Code/Presentacion.py index 1634612..4cd4e7e 100644 --- a/Code/Presentacion.py +++ b/Code/Presentacion.py @@ -2,7 +2,7 @@ import time import codecs -import LCEngine +import LCEngineV1 as LCEngine from Code import Util from Code import ControlPosicion diff --git a/Code/Procesador.py b/Code/Procesador.py index 37a9a0d..85f8ed2 100644 --- a/Code/Procesador.py +++ b/Code/Procesador.py @@ -24,6 +24,7 @@ from Code import GestorMateMap from Code import GestorMicElo from Code import GestorCompeticion +from Code import GestorOpeningLines from Code import GestorPGN from Code import GestorPerson from Code import GestorRoutes @@ -32,6 +33,7 @@ from Code import GestorPartida from Code import GestorTorneo from Code import Presentacion +from Code import OpeningLines from Code import GestorWashing from Code import GestorPlayPGN from Code.QT import DatosNueva @@ -62,7 +64,7 @@ from Code.QT import QTVarios from Code.QT import PantallaDatabase from Code.QT import PantallaManualSave -from Code.QT import WBDatabaseFEN +from Code.QT import PantallaDatabaseFEN from Code.QT import WOpeningGuide from Code.QT import PantallaKibitzers from Code.QT import POLines @@ -180,6 +182,8 @@ def reset(self): self.pantalla.activaJuego(False, False) self.tablero.exePulsadoNum = None self.tablero.ponPosicion(self.posicionInicial) + self.tablero.borraMovibles() + self.tablero.quitaFlechas() self.pantalla.ajustaTam() self.pantalla.ponTitulo() self.pararMotores() @@ -707,8 +711,6 @@ def trainingMap(self, mapa): self.gestor.inicio(resp) def tools(self): - # self.openings() - # return menu = QTVarios.LCMenu(self.pantalla) menu.opcion("juega_solo", _("Create your own game"), Iconos.JuegaSolo()) @@ -742,7 +744,7 @@ def tools(self): menu1.separador() menu1.opcion("bookguide", _("Personal Opening Guide"), Iconos.BookGuide()) menu1.separador() - menu1.opcion("openings", _("Opening lines") + " [WORK IN PROGRESS]", Iconos.OpeningLines()) + menu1.opcion("openings", _("Opening lines"), Iconos.OpeningLines()) menu.separador() menu1 = menu.submenu(_("Engines"), Iconos.Motores()) @@ -790,12 +792,42 @@ def tools(self): self.openings() def openings(self): - result = POLines.openingLines(self) - if result: - fichero, basepv, title = result - POLines.study(self, fichero) + dicline = POLines.openingLines(self) + if dicline: + if "TRAIN" in dicline: + resp = "tr_%s" % dicline["TRAIN"] + else: + resp = POLines.study(self, dicline["file"]) + if resp is None: + self.openings() + else: + pathFichero = os.path.join(self.configuracion.folderOpenings, dicline["file"]) + if resp == "tr_sequential": + self.openingsTrainingSequential(pathFichero) + elif resp == "tr_static": + self.openingsTrainingStatic(pathFichero) + elif resp == "tr_positions": + self.openingsTrainingPositions(pathFichero) + + + def openingsTrainingSequential(self, pathFichero): + self.gestor = GestorOpeningLines.GestorOpeningLines(self) + self.gestor.inicio(pathFichero, "sequential", 0) + + def openingsTrainingStatic(self, pathFichero): + dbop = OpeningLines.Opening(pathFichero) + num_linea = POLines.selectLine(self, dbop) + dbop.close() + if num_linea is not None: + self.gestor = GestorOpeningLines.GestorOpeningLines(self) + self.gestor.inicio(pathFichero, "static", num_linea) + else: self.openings() + def openingsTrainingPositions(self, pathFichero): + self.gestor = GestorOpeningLines.GestorOpeningLinesPositions(self) + self.gestor.inicio(pathFichero) + def kibitzers(self): w = PantallaKibitzers.WKibitzers(self.pantalla, self) w.exec_() @@ -819,7 +851,7 @@ def database(self): # self.procesarAccion(k_terminar) def databaseFEN(self): # TODO - w = WBDatabaseFEN.WBDatabaseFEN(self.pantalla, self) + w = PantallaDatabaseFEN.WBDatabaseFEN(self.pantalla, self) w.exec_() def manual_save(self): diff --git a/Code/QT/Delegados.py b/Code/QT/Delegados.py index 115c22a..939989c 100644 --- a/Code/QT/Delegados.py +++ b/Code/QT/Delegados.py @@ -302,6 +302,31 @@ def paint(self, painter, option, index): painter.restore() +class PmIconosWeather(QtGui.QStyledItemDelegate): + def __init__(self, parent=None): + QtGui.QStyledItemDelegate.__init__(self, parent) + + self.dicIconos = { + "0": Iconos.pmInvierno(), + "1": Iconos.pmLluvia(), + "2": Iconos.pmSolNubesLluvia(), + "3": Iconos.pmSolNubes(), + "4": Iconos.pmSol(), + } + + def paint(self, painter, option, index): + pos = str(index.model().data(index, QtCore.Qt.DisplayRole)) + if pos not in self.dicIconos: + if pos.isdigit(): + pos = "4" if int(pos)> 4 else "0" + else: + return + painter.save() + painter.translate(option.rect.x(), option.rect.y()) + painter.drawPixmap(4, 4, self.dicIconos[pos]) + painter.restore() + + class HTMLDelegate(QtGui.QStyledItemDelegate): def paint(self, painter, option, index): options = QtGui.QStyleOptionViewItemV4(option) diff --git a/Code/QT/Iconos.py b/Code/QT/Iconos.py index aab1f15..d6368d5 100644 --- a/Code/QT/Iconos.py +++ b/Code/QT/Iconos.py @@ -2057,31 +2057,37 @@ def Maps(): return QtGui.QIcon(pmMaps()) def pmSol(): - return PM(860091,866443) + return PM(860091,861017) def Sol(): return QtGui.QIcon(pmSol()) def pmSolNubes(): - return PM(866443,872300) + return PM(861017,861880) def SolNubes(): return QtGui.QIcon(pmSolNubes()) -def pmNubes(): - return PM(872300,875432) +def pmSolNubesLluvia(): + return PM(861880,862840) -def Nubes(): - return QtGui.QIcon(pmNubes()) +def SolNubesLluvia(): + return QtGui.QIcon(pmSolNubesLluvia()) -def pmTormenta(): - return PM(875432,880083) +def pmLluvia(): + return PM(862840,863679) -def Tormenta(): - return QtGui.QIcon(pmTormenta()) +def Lluvia(): + return QtGui.QIcon(pmLluvia()) + +def pmInvierno(): + return PM(863679,865255) + +def Invierno(): + return QtGui.QIcon(pmInvierno()) def pmWords(): - return PM(880083,883868) + return PM(865255,869040) def Words(): return QtGui.QIcon(pmWords()) @@ -2099,31 +2105,31 @@ def FixedElo(): return QtGui.QIcon(pmFixedElo()) def pmX_Microfono(): - return PM(883868,886321) + return PM(869040,871493) def X_Microfono(): return QtGui.QIcon(pmX_Microfono()) def pmSoundTool(): - return PM(886321,888780) + return PM(871493,873952) def SoundTool(): return QtGui.QIcon(pmSoundTool()) def pmImportar(): - return PM(888780,891448) + return PM(873952,876620) def Importar(): return QtGui.QIcon(pmImportar()) def pmVoyager1(): - return PM(891448,893898) + return PM(876620,879070) def Voyager1(): return QtGui.QIcon(pmVoyager1()) def pmTrain(): - return PM(893898,895268) + return PM(879070,880440) def Train(): return QtGui.QIcon(pmTrain()) @@ -2141,67 +2147,67 @@ def Measure(): return QtGui.QIcon(pmMeasure()) def pmPlayGame(): - return PM(895268,899626) + return PM(880440,884798) def PlayGame(): return QtGui.QIcon(pmPlayGame()) def pmScanner(): - return PM(899626,899967) + return PM(884798,885139) def Scanner(): return QtGui.QIcon(pmScanner()) def pmMenos(): - return PM(899967,900492) + return PM(885139,885664) def Menos(): return QtGui.QIcon(pmMenos()) def pmSchool(): - return PM(900492,901033) + return PM(885664,886205) def School(): return QtGui.QIcon(pmSchool()) def pmLaw(): - return PM(901033,901649) + return PM(886205,886821) def Law(): return QtGui.QIcon(pmLaw()) def pmLearnGame(): - return PM(901649,902082) + return PM(886821,887254) def LearnGame(): return QtGui.QIcon(pmLearnGame()) def pmUniversity(): - return PM(902082,902502) + return PM(887254,887674) def University(): return QtGui.QIcon(pmUniversity()) def pmLonghaul(): - return PM(902502,903428) + return PM(887674,888600) def Longhaul(): return QtGui.QIcon(pmLonghaul()) def pmTrekking(): - return PM(903428,904122) + return PM(888600,889294) def Trekking(): return QtGui.QIcon(pmTrekking()) def pmPassword(): - return PM(904122,904575) + return PM(889294,889747) def Password(): return QtGui.QIcon(pmPassword()) def pmSQL_RAW(): - return PM(895268,899626) + return PM(880440,884798) def SQL_RAW(): return QtGui.QIcon(pmSQL_RAW()) @@ -2219,128 +2225,158 @@ def Light(): return QtGui.QIcon(pmLight()) def pmLight32(): - return PM(904575,906275) + return PM(889747,891447) def Light32(): return QtGui.QIcon(pmLight32()) def pmTOL(): - return PM(906275,906984) + return PM(891447,892156) def TOL(): return QtGui.QIcon(pmTOL()) def pmUned(): - return PM(902082,902502) + return PM(887254,887674) def Uned(): return QtGui.QIcon(pmUned()) def pmUwe(): - return PM(906984,907953) + return PM(892156,893125) def Uwe(): return QtGui.QIcon(pmUwe()) def pmThinking(): - return PM(907953,908325) + return PM(893125,893497) def Thinking(): return QtGui.QIcon(pmThinking()) def pmWashingMachine(): - return PM(908325,908988) + return PM(893497,894160) def WashingMachine(): return QtGui.QIcon(pmWashingMachine()) def pmTerminal(): - return PM(908988,912532) + return PM(894160,897704) def Terminal(): return QtGui.QIcon(pmTerminal()) def pmManualSave(): - return PM(912532,913115) + return PM(897704,898287) def ManualSave(): return QtGui.QIcon(pmManualSave()) def pmSettings(): - return PM(913115,913553) + return PM(898287,898725) def Settings(): return QtGui.QIcon(pmSettings()) def pmStrength(): - return PM(913553,914224) + return PM(898725,899396) def Strength(): return QtGui.QIcon(pmStrength()) def pmSingular(): - return PM(914224,915079) + return PM(899396,900251) def Singular(): return QtGui.QIcon(pmSingular()) def pmScript(): - return PM(915079,915648) + return PM(900251,900820) def Script(): return QtGui.QIcon(pmScript()) def pmScriptFree(): - return PM(915648,916208) + return PM(900820,901380) def ScriptFree(): return QtGui.QIcon(pmScriptFree()) def pmTexto(): - return PM(916208,919053) + return PM(901380,904225) def Texto(): return QtGui.QIcon(pmTexto()) def pmLampara(): - return PM(919053,919762) + return PM(904225,904934) def Lampara(): return QtGui.QIcon(pmLampara()) def pmFile(): - return PM(919762,922062) + return PM(904934,907234) def File(): return QtGui.QIcon(pmFile()) def pmCalculo(): - return PM(922062,922988) + return PM(907234,908160) def Calculo(): return QtGui.QIcon(pmCalculo()) def pmOpeningLines(): - return PM(922988,923666) + return PM(908160,908838) def OpeningLines(): return QtGui.QIcon(pmOpeningLines()) def pmStudy(): - return PM(923666,924703) + return PM(908838,909875) def Study(): return QtGui.QIcon(pmStudy()) def pmLichess(): - return PM(924703,925593) + return PM(909875,910765) def Lichess(): return QtGui.QIcon(pmLichess()) def pmMiniatura(): - return PM(925593,926520) + return PM(910765,911692) def Miniatura(): return QtGui.QIcon(pmMiniatura()) +def pmLocomotora(): + return PM(911692,912473) + +def Locomotora(): + return QtGui.QIcon(pmLocomotora()) + +def pmPositions(): + return PM(912473,914064) + +def Positions(): + return QtGui.QIcon(pmPositions()) + +def pmTrainSequential(): + return PM(914064,915009) + +def TrainSequential(): + return QtGui.QIcon(pmTrainSequential()) + +def pmTrainStatic(): + return PM(915009,915979) + +def TrainStatic(): + return QtGui.QIcon(pmTrainStatic()) + +def pmTrainPositions(): + return PM(915979,916833) + +def TrainPositions(): + return QtGui.QIcon(pmTrainPositions()) + diff --git a/Code/QT/InfoBase.py b/Code/QT/InfoBase.py index 7e35e1b..7a1f80d 100644 --- a/Code/QT/InfoBase.py +++ b/Code/QT/InfoBase.py @@ -61,8 +61,8 @@ def listaMotores(self, bloque): ["Rybka 2.3.2a 32-bit", "Vasik Rajlich", "http://rybkachess.com/"], ["Critter 1.6a 32bits", "Richard Vida", "http://www.vlasak.biz/critter/"], ["Texel 1.07", "Peter Österlund", "http://hem.bredband.net/petero2b/javachess/index.html#texel"], - ["Stockfish 8", "Tord Romstad, Marco Costalba, Joona Kiiski", "http://stockfishchess.org/"], - ["McBrain 4.0", "Michael Byrne (based on stockfish)", "https://github.com/MichaelB7/Stockfish/releases"], + ["Stockfish 9", "Tord Romstad, Marco Costalba, Joona Kiiski", "http://stockfishchess.org/"], + ["McBrain 9", "Michael Byrne", "https://github.com/MichaelB7/Stockfish/releases"], ["Gull 3", "Vadim Demichev", "https://sourceforge.net/projects/gullchess/"], ["Delfi 5.4", "Fabio Cavicchio", "http://www.msbsoftware.it/delfi/"], # ["SmartThink 1.97", "Sergei S. Markoff", "http://genes1s.net/smarthink.php"], @@ -124,7 +124,7 @@ def version(num, liBase, liResto): return txt # Version 11 - liBase = ["Alfonso Solbes", "Max Aloyau", "tico-tico", "Nils Andersson", "Bernhard", "Ed Smith", "Rob", "Giovanni di Maria", "vga", "Remes", "Péter Rabi"] + liBase = ["Alfonso Solbes", "Max Aloyau", "tico-tico", "Nils Andersson", "Bernhard", "Ed Smith", "Rob", "Giovanni di Maria", "vga", "Remes", "Péter Rabi", "Iñaki Rodriguez"] liResto = ["Immortalchess forum",] txt += version(11, liBase, liResto) @@ -391,7 +391,7 @@ def Engines(self, orden): txt += "" if "McBrain" in nombre: txt += '%s (%s)' % (nombre, _("default")) - txt += '%s' % autor + txt += '%s' % autor else: txt += "%s" % nombre txt += "%s" % autor @@ -425,7 +425,6 @@ def Programming(self): (_("GUI"), "PyQt4 - GPL", "http://www.riverbankcomputing.co.uk"), (_("Audio"), "PyAudio v0.2.4 - MIT License", "http://people.csail.mit.edu/hubert/pyaudio/"), ("psutil", _X(_("Created by %1"), "Giampaolo Rodola"), "http://code.google.com/p/psutil/"), - ("pygal", _X(_("Created by %1"), "Kozea"), "http://pygal.org"), ("chardet", _X(_("Created by %1"), "Ian Cordasco"), "https://github.com/chardet/chardet"), (_("Polyglot books"), _X(_("Based on work by %1"), "Michel Van den Bergh"), "http://alpha.uhasselt.be/Research/Algebra/Toga/book_format.html"), ("Polyglot1.4w", _X(_("Created by %1"), "Fabien Letouzy") + ". " + _X(_("Modified by %1"), "Fonzy Bluemers"), "http://www.geenvis.net/"), diff --git a/Code/QT/POLAnalisis.py b/Code/QT/POLAnalisis.py index 77d7f61..8a11eca 100644 --- a/Code/QT/POLAnalisis.py +++ b/Code/QT/POLAnalisis.py @@ -1,6 +1,6 @@ import os -import LCEngine +import LCEngineV1 as LCEngine from PyQt4 import QtGui, QtCore @@ -27,6 +27,9 @@ def __init__(self, tabsAnalisis, procesador, configuracion): self.posicion = None self.li_analysis = [] self.gestor_motor = None + self.current_mrm = None + + self.dbop = tabsAnalisis.dbop self.procesador = procesador self.configuracion = configuracion @@ -38,11 +41,16 @@ def __init__(self, tabsAnalisis, procesador, configuracion): self.bt_stop.hide() self.lb_engine = Controles.LB(self, _("Engine") + ":") - liMotores = configuracion.comboMotoresCompleto() - self.cb_engine = Controles.CB(self, liMotores, configuracion.tutor.clave).capturaCambiado(self.reset_motor) - + liMotores = configuracion.comboMotoresCompleto() # (nombre, clave) + default = configuracion.tutor.clave + engine = self.dbop.getconfig("ENGINE", default) + if len([clave for nombre,clave in liMotores if clave==engine]) == 0: + engine = default + self.cb_engine = Controles.CB(self, liMotores, engine).capturaCambiado(self.reset_motor) + + multipv = self.dbop.getconfig("ENGINE_MULTIPV", 10) lb_multipv = Controles.LB(self, _("Multi PV")+": ") - self.sb_multipv = Controles.SB(self, 10, 1, 500).tamMaximo(50) + self.sb_multipv = Controles.SB(self, multipv, 1, 500).tamMaximo(50) self.lb_analisis = Controles.LB(self, "").ponFondoN("#C9D2D7").ponTipoLetra(puntos=configuracion.puntosPGN) @@ -66,7 +74,19 @@ def __init__(self, tabsAnalisis, procesador, configuracion): self.reset_motor() + def saveCurrent(self): + if self.current_mrm: + fenM2 = self.current_posicion.fenM2() + dic = self.dbop.getfenvalue(fenM2) + if "ANALISIS" in dic: + mrm_ant = dic["ANALISIS"] + if mrm_ant.getdepth0() > self.current_mrm.getdepth0(): + return + dic["ANALISIS"] = self.current_mrm + self.dbop.setfenvalue(fenM2, dic) + def setData(self, label, posicion): + self.saveCurrent() self.posicion = posicion self.lb_analisis.ponTexto(label) if self.analyzing: @@ -76,8 +96,18 @@ def setData(self, label, posicion): self.gestor_motor.ac_inicio(partida) self.analyzing = True QtCore.QTimer.singleShot(1000, self.lee_analisis) + else: + fenM2 = posicion.fenM2() + dic = self.dbop.getfenvalue(fenM2) + if "ANALISIS" in dic: + self.show_analisis(dic["ANALISIS"]) + else: + self.li_analysis = [] + self.grid_analysis.refresh() def start(self): + self.current_mrm = None + self.current_posicion = None self.sb_multipv.setDisabled(True) self.cb_engine.setDisabled(True) self.analyzing = True @@ -98,6 +128,8 @@ def show_stop(self): self.bt_stop.show() def show_analisis(self, mrm): + self.current_mrm = mrm + self.current_posicion = self.posicion li = [] for rm in mrm.liMultiPV: partida = Partida.Partida(self.posicion) @@ -112,7 +144,10 @@ def show_analisis(self, mrm): pgn0 = lit[1] pgn1 = " ".join(lit[2:]) - partida.ms_sol = pgn0, siBlancas, None, None, None, None, False, False + if self.siFigurines: + partida.ms_sol = pgn0, siBlancas, None, None, None, None, False, False + else: + partida.ms_sol = pgn0 partida.ms_pgn = pgn1 partida.ms_pdt = rm.abrTextoPDT() li.append(partida) @@ -126,6 +161,7 @@ def lee_analisis(self): QtCore.QTimer.singleShot(2000, self.lee_analisis) def stop(self): + self.saveCurrent() self.sb_multipv.setDisabled(False) self.cb_engine.setDisabled(False) self.analyzing = False @@ -134,6 +170,7 @@ def stop(self): self.gestor_motor.ac_final(0) def reset_motor(self): + self.saveCurrent() clave = self.cb_engine.valor() if not clave: return @@ -157,6 +194,10 @@ def gridDato(self, grid, fila, oColumna): else: return self.li_analysis[fila].ms_pgn + def saveConfig(self): + self.dbop.setconfig("ENGINE", self.cb_engine.valor()) + self.dbop.setconfig("ENGINE_MULTIPV", self.sb_multipv.valor()) + class TabBook(QtGui.QWidget): def __init__(self, tabsanalisis, book, configuracion): @@ -170,8 +211,10 @@ def __init__(self, tabsanalisis, book, configuracion): book.polyglot() self.li_moves = [] + self.siFigurines = configuracion.figurinesPGN + oColumnas = Columnas.ListaColumnas() - delegado = Delegados.EtiquetaPOS(True, siLineas=False) if configuracion.figurinesPGN else None + delegado = Delegados.EtiquetaPOS(True, siLineas=False) if self.siFigurines else None for x in range(20): oColumnas.nueva(x, "", 80, siCentrado=True, edicion = delegado) self.grid_moves = Grid.Grid(self, oColumnas, siSelecFilas=True, siCabeceraMovible=False, siCabeceraVisible=False) @@ -190,8 +233,11 @@ def gridDato(self, grid, fila, oColumna): li = mv.dato key = int(oColumna.clave) pgn = li[key] - siBlancas = " w " in mv.fen - return pgn, siBlancas, None, None, None, None, False, True + if self.siFigurines: + siBlancas = " w " in mv.fen + return pgn, siBlancas, None, None, None, None, False, True + else: + return pgn def gridDobleClick(self, grid, fila, oColumna): self.lee_subnivel(fila) @@ -201,7 +247,6 @@ def gridBotonDerecho(self, grid, fila, columna, modificadores): self.borra_subnivel(fila) self.grid_moves.refresh() - def setData(self, posicion): self.posicion = posicion self.start() @@ -327,7 +372,7 @@ def __init__(self, panelOpening, procesador, configuracion): self.tabs.tabCloseRequested.connect(self.tabCloseRequested) layout = Colocacion.V() - layout.control(self.tabs).margen(3) + layout.control(self.tabs).margen(0) self.setLayout(layout) def changedNextMove(self): @@ -363,6 +408,7 @@ def creaTab(self): pos = len(self.liTabs)-1 self.tabs.nuevaTab(tabbook, book.nombre, pos) self.tabs.setTabIcon(pos, Iconos.Libros()) + self.setPosicion(self.partida, self.njg, pos) elif resp == "dbase": nomfichgames = QTVarios.selectDB(self, self.configuracion, False, True) if nomfichgames: @@ -371,11 +417,14 @@ def creaTab(self): self.liTabs.append((resp, tabdb)) pos = len(self.liTabs) - 1 self.setPosicion(self.partida, self.njg, pos) - self.tabs.nuevaTab(tabdb, _("Database"), pos) + nombre = os.path.basename(nomfichgames)[:-4] + self.tabs.nuevaTab(tabdb, nombre, pos) self.tabs.setTabIcon(pos, Iconos.Database()) self.tabs.activa(pos) def setPosicion(self, partida, njg, numTab=None): + if partida is None: + return jg = partida.jugada(njg) self.partida = partida self.njg = njg @@ -403,7 +452,6 @@ def setPosicion(self, partida, njg, numTab=None): tab.setData(data) tab.start() - def seleccionaLibro(self): listaLibros = Books.ListaLibros() listaLibros.recuperaVar(self.configuracion.ficheroBooks) @@ -426,7 +474,13 @@ def seleccionaLibro(self): nombre = os.path.basename(fbin)[:-4] book = Books.Libro("P", nombre, fbin, True) listaLibros.nuevo(book) + listaLibros.guardaVar(self.configuracion.ficheroBooks) else: book = None return book + def saveConfig(self): + for tipo, wtab in self.liTabs: + if tipo == "engine": + wtab.saveConfig() + diff --git a/Code/QT/POLBoard.py b/Code/QT/POLBoard.py index df30935..d3d30f1 100644 --- a/Code/QT/POLBoard.py +++ b/Code/QT/POLBoard.py @@ -24,21 +24,31 @@ def __init__(self, panelOpening, configuracion): self.panelOpening = panelOpening self.dbop = panelOpening.dbop + self.partidabase = panelOpening.partidabase + self.num_jg_inicial = len(self.partidabase) + self.posJugada = self.num_jg_inicial + confTablero = configuracion.confTablero("POSLINES", 32) self.tablero = Tablero.Tablero(self, confTablero) self.tablero.crea() self.tablero.ponerPiezasAbajo(True) self.tablero.ponMensajero(self.mueveHumano) self.tablero.dispatchSize(self.ajustaAncho) + self.tablero.dbVisual_setFichero(self.dbop.nomFichero) + self.tablero.dbVisual_setShowAllways(True) + + self.dbop.setdbVisual_Tablero(self.tablero) # To close self.intervalo = 1400 + tipoLetra = Controles.TipoLetra(puntos=configuracion.puntosPGN) + lybt, bt = QTVarios.lyBotonesMovimiento(self, "", siTiempo=True, siLibre=False, tamIcon=24) self.lbPGN = Controles.LB(self).ponWrap() self.lbPGN.colocate = self.colocatePartida self.lbPGN.setStyleSheet("QLabel{ border-style: groove; border-width: 2px; border-color: LightSlateGray; padding: 8px;}") - self.lbPGN.ponTipoLetra(puntos=configuracion.puntosPGN) + self.lbPGN.ponFuente(tipoLetra) self.lbPGN.setOpenExternalLinks(False) def muestraPos(txt): self.colocatePartida(int(txt)) @@ -68,19 +78,24 @@ def muestraPos(txt): # Valoracion liOpciones = [(tit[0], k, tit[1]) for k, tit in self.dicValoracion.iteritems()] - self.lbValoracion = Controles.LB(self, _("Rating") + ":") self.cbValoracion = Controles.CB(self, liOpciones, 0).capturaCambiado(self.cambiadoValoracion) + self.cbValoracion.ponFuente(tipoLetra) # Ventaja liOpciones = [(tit, k, icon) for k, (tit, icon) in self.dicVentaja.iteritems()] self.cbVentaja = Controles.CB(self, liOpciones, 0).capturaCambiado(self.cambiadoVentaja) + self.cbVentaja.ponFuente(tipoLetra) # Comentario self.emComentario = Controles.EM(self, siHTML=False).capturaCambios(self.cambiadoComentario) - - lyVal = Colocacion.H().control(self.lbValoracion).control(self.cbValoracion).control(self.cbVentaja).relleno() + self.emComentario.ponFuente(tipoLetra) + self.emComentario.altoFijo(5*configuracion.altoFilaPGN) + lyVal = Colocacion.H().control(self.cbValoracion).control(self.cbVentaja) lyEd = Colocacion.V().otro(lyVal).control(self.emComentario) + # Apertura + self.lbApertura = Controles.LB(self).alinCentrado().ponFuente(tipoLetra).ponWrap() + lyt = Colocacion.H().relleno().control(self.tablero).relleno() lya = Colocacion.H().relleno().control(self.lbPGN).relleno() @@ -90,6 +105,7 @@ def muestraPos(txt): layout.otro(lybt) layout.otro(lya) layout.otro(lyEd) + layout.control(self.lbApertura) layout.relleno() self.setLayout(layout) @@ -97,6 +113,8 @@ def muestraPos(txt): self.siReloj = False + self.ponPartida(self.partidabase) + def analisis(self): x = self.gb_analysis.isChecked() if not x: @@ -104,23 +122,17 @@ def analisis(self): else: self.wanalisis.show() - def gridNumDatos(self, grid): - return 0 - - def reset_motor(self): - pass - - def start(self): - pass - - def stop(self): - pass - - def ponPartidaBase(self, partida): - self.num_jg_inicial = partida.numJugadas()-1 - def ponPartida(self, partida): + partida.test_apertura() self.partida = partida + rotulo = partida.rotuloApertura() + if rotulo is not None: + trans = partida.rotuloTransposition() + if trans is not None: + rotulo += "\n%s: %s" % (_("Transposition"), trans) + else: + rotulo = "" + self.lbApertura.ponTexto(rotulo) def procesarTB(self): getattr(self, self.sender().clave)() @@ -153,7 +165,7 @@ def camposEdicion(self, siVisible): self.emComentario.setVisible(siVisible) def mueveHumano(self, desde, hasta, coronacion=""): - cpActual = self.partida.jugada(self.posJugada).posicion + cpActual = self.partida.jugada(self.posJugada).posicion if self.posJugada >= 0 else self.partida.iniPosicion if cpActual.siPeonCoronando(desde, hasta): coronacion = self.tablero.peonCoronando(cpActual.siBlancas) if coronacion is None: @@ -177,20 +189,24 @@ def resetValues(self): def colocatePartida(self, pos): self.fenM2 = None - if not self.partida.numJugadas(): + num_jugadas = self.partida.numJugadas() + if num_jugadas == 0: + self.posJugada = -1 self.lbPGN.ponTexto("") self.tablero.ponPosicion(self.partida.iniPosicion) self.resetValues() + self.activaPiezas() return - lh = self.partida.numJugadas() - 1 - if pos >= lh: + if pos >= num_jugadas: self.siReloj = False - pos = lh + pos = num_jugadas - 1 + elif pos < self.num_jg_inicial-1: + pos = self.num_jg_inicial-1 p = self.partida - numJugada = p.primeraJugada() + numJugada = 1 pgn = "" style_number = "color:teal; font-weight: bold;" style_moves = "color:black;" @@ -201,7 +217,7 @@ def colocatePartida(self, pos): pgn += '%d.' % (style_number, numJugada) numJugada += 1 - xp = jg.pgnHTML() if self.siFigurines else jg.pgnSP() + xp = jg.pgnHTML(self.siFigurines) if n == pos: xp = '%s' % (style_select, xp) else: @@ -216,13 +232,15 @@ def colocatePartida(self, pos): if pos < 0: self.tablero.ponPosicion(self.partida.iniPosicion) self.resetValues() + self.activaPiezas() return jugada = self.partida.jugada(self.posJugada) - posicion = jugada.posicion + posicion = jugada.posicion if jugada else self.partida.iniPosicion self.tablero.ponPosicion(posicion) - self.tablero.ponFlechaSC(jugada.desde, jugada.hasta) + if jugada: + self.tablero.ponFlechaSC(jugada.desde, jugada.hasta) self.fenM2 = posicion.fenM2() dic = self.dbop.getfenvalue(self.fenM2) @@ -242,14 +260,16 @@ def colocatePartida(self, pos): def activaPiezas(self): self.tablero.desactivaTodas() - if not self.siReloj and self.posJugada >= self.num_jg_inicial: - jg = self.partida.jugada(self.posJugada) - self.tablero.activaColor(not jg.siBlancas()) + if not self.siReloj and self.posJugada >= self.num_jg_inicial-1: + if self.posJugada >= 0: + jg = self.partida.jugada(self.posJugada) + color = not jg.siBlancas() + else: + color = True + self.tablero.activaColor(color) def MoverInicio(self): - self.posJugada = -1 - posicion = self.partida.iniPosicion - self.tablero.ponPosicion(posicion) + self.colocatePartida(0) def MoverAtras(self): self.colocatePartida(self.posJugada - 1) diff --git a/Code/QT/POLines.py b/Code/QT/POLines.py index 4e1de80..2c7b6f7 100644 --- a/Code/QT/POLines.py +++ b/Code/QT/POLines.py @@ -1,13 +1,14 @@ import os.path +import copy -from PyQt4 import QtCore +from PyQt4 import QtCore, QtGui +from Code import Util from Code import Partida from Code import OpeningLines from Code.QT import Colocacion from Code.QT import Columnas from Code.QT import Controles -from Code.QT import FormLayout from Code.QT import Grid from Code.QT import Iconos from Code.QT import PantallaAperturas @@ -18,6 +19,7 @@ from Code.QT import POLBoard from Code.QT import POLAnalisis from Code.QT import Voyager +from Code.QT import FormLayout class WOpeningLines(QTVarios.WDialogo): @@ -34,9 +36,9 @@ def __init__(self, procesador): oColumnas = Columnas.ListaColumnas() oColumnas.nueva("TITLE", _("Name"), 240) oColumnas.nueva("BASEPV", _("First moves"), 280) + oColumnas.nueva("NUMLINES", _("Lines"), 80, siCentrado=True) oColumnas.nueva("FILE", _("File"), 200) self.glista = Grid.Grid(self, oColumnas, siSelecFilas=True) - self.glista.setMinimumWidth(self.glista.anchoColumnas() + 20) liAcciones = ( (_("Close"), Iconos.MainMenu(), self.terminar), None, @@ -45,17 +47,58 @@ def __init__(self, procesador): (_("Up"), Iconos.Arriba(), self.arriba), (_("Down"), Iconos.Abajo(), self.abajo), None, (_("Remove"), Iconos.Borrar(), self.borrar), None, + (_("Reinit"), Iconos.Reiniciar(), self.reiniciar), None, ) tb = Controles.TBrutina(self, liAcciones) + sp = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + tb.setSizePolicy(sp) + + liAcciones = ( + (_("Sequential"), Iconos.TrainSequential(), self.tr_sequential), None, + (_("Static"), Iconos.TrainStatic(), self.tr_static), None, + (_("Positions"), Iconos.TrainPositions(), self.tr_positions), + ) + tbtrain = Controles.TBrutina(self, liAcciones, siTexto=False) + + lbtrain = Controles.LB(self, _("Trainings")).alinCentrado().ponFondoN("lightgray") + lytrain = Colocacion.V().control(lbtrain).control(tbtrain).margen(0) + self.wtrain = QtGui.QWidget() + self.wtrain.setLayout(lytrain) + # Colocamos - ly = Colocacion.V().control(tb).control(self.glista).margen(3) + + lytb = Colocacion.H().control(tb).control(self.wtrain) + ly = Colocacion.V().otro(lytb).control(self.glista).margen(3) self.setLayout(ly) self.registrarGrid(self.glista) self.recuperarVideo() + self.wtrain.setVisible(False) + self.glista.gotop() + + def tr_(self, tipo): + recno = self.glista.recno() + op = self.listaOpenings[recno] + op["TRAIN"] = tipo + self.resultado = op + self.guardarVideo() + self.accept() + + def tr_sequential(self): + self.tr_("sequential") + + def tr_static(self): + self.tr_("static") + + def tr_positions(self): + self.tr_("positions") + + def reiniciar(self): + self.listaOpenings.reiniciar() + self.glista.refresh() self.glista.gotop() def arriba(self): @@ -98,24 +141,19 @@ def new(self): if si_expl: QTUtil2.mensaje(self, _("Secondly you have to choose a name for this opening studio.")) - while True: - liGen = [(None, None)] - liGen.append((_("Opening studio name") + ":", name)) - resultado = FormLayout.fedit(liGen, title=_("Opening studio name"), parent=self, icon=Iconos.OpeningLines(), anchoMinimo=460) - if resultado: - accion, liResp = resultado - name = liResp[0] - if name: - file = self.listaOpenings.select_filename(name) - break - else: - return - else: - return - self.listaOpenings.new(file, pv, name) - self.glista.gobottom() - self.glista.refresh() + liGen = [(None, None)] + liGen.append((_("Opening studio name") + ":", name)) + resultado = FormLayout.fedit(liGen, title=_("Opening studio name"), parent=self, icon=Iconos.OpeningLines(), anchoMinimo=460) + if resultado: + accion, liResp = resultado + name = liResp[0] + if name: + file = self.listaOpenings.select_filename(name) + self.listaOpenings.new(file, pv, name) + self.resultado = self.listaOpenings[-1] + self.guardarVideo() + self.accept() def borrar(self): li = self.glista.recnosSeleccionados() @@ -123,7 +161,7 @@ def borrar(self): mens = _("Do you want to delete all selected records?") mens += "\n" for num, fila in enumerate(li, 1): - mens += "\n%d. %s" % (num, self.listaOpenings[fila][2]) + mens += "\n%d. %s" % (num, self.listaOpenings[fila]["title"]) if QTUtil2.pregunta(self, mens): li.sort(reverse=True) for fila in li: @@ -137,14 +175,16 @@ def gridDato(self, grid, fila, oColumna): col = oColumna.clave op = self.listaOpenings[fila] if col == "TITLE": - return op[2] + return op["title"] elif col == "FILE": - return op[0] + return op["file"] + elif col == "NUMLINES": + return op["lines"] elif col == "BASEPV": - pv = op[1] + pv = op["pv"] if pv: p = Partida.Partida() - p.leerPV(op[1]) + p.leerPV(pv) return p.pgnBaseRAW() else: return "" @@ -154,6 +194,14 @@ def gridPonValor(self, grid, fila, columna, valor): if valor: self.listaOpenings.change_title(fila, valor) + def gridCambiadoRegistro(self, grid, fila, columna): + ok = False + if fila >= 0: + op = self.listaOpenings[fila] + ok = op["withtrainings"] + + self.wtrain.setVisible(ok) + def closeEvent(self, event): # Cierre con X self.guardarVideo() @@ -162,30 +210,31 @@ def terminar(self): self.reject() -class WStudy(QTVarios.WDialogo): +class WLines(QTVarios.WDialogo): def __init__(self, procesador, dbop): - self.dbop = dbop title = dbop.gettitle() - QTVarios.WDialogo.__init__(self, procesador.pantalla, title, Iconos.Study(), "studyOpening") + QTVarios.WDialogo.__init__(self, procesador.pantalla, title, Iconos.OpeningLines(), "studyOpening") self.procesador = procesador self.configuracion = procesador.configuracion self.partidabase = self.dbop.getpartidabase() self.num_jg_inicial = self.partidabase.numJugadas() + self.num_jg_actual = None self.partida = None + + self.resultado = None siFigurinesPGN = self.configuracion.figurinesPGN liAcciones = ( (_("Close"), Iconos.MainMenu(), self.terminar), None, - # (_("New"), Iconos.Nuevo(), self.new), None, (_("Remove"), Iconos.Borrar(), self.borrar), None, - # (_("Train"), Iconos.Study(), self.train), None, - (_("Voyager 2"), Iconos.Voyager1(), self.voyager2), None, - (_("Import"), Iconos.Mezclar(), self.importar), None + (_("Import"), Iconos.Mezclar(), self.importar), None, + (_("Utilities"), Iconos.Utilidades(), self.utilidades), None, + (_("Train"), Iconos.Study(), self.train), None, ) - self.tb = tb = Controles.TBrutina(self, liAcciones) + self.tb = Controles.TBrutina(self, liAcciones) oColumnas = Columnas.ListaColumnas() oColumnas.nueva("LINE", _("Line"), 35, edicion=Delegados.EtiquetaPOS(False, True)) @@ -199,110 +248,352 @@ def __init__(self, procesador, dbop): self.glines.ponAltoFila(self.configuracion.altoFilaPGN) self.pboard = POLBoard.BoardLines(self, self.configuracion) - self.pboard.ponPartidaBase(self.partidabase) - self.pboard.ponPartida(self.partidabase) self.tabsanalisis = POLAnalisis.TabsAnalisis(self, self.procesador, self.configuracion) - lyg = Colocacion.H().control(self.tabsanalisis).margen(3) - self.gb_analysis = Controles.GB(self, _("Analysis"), lyg) - self.gb_analysis.conectar(self.analisis) - self.gb_analysis.setChecked(True) - - lyLV = Colocacion.V().control(self.glines).control(self.gb_analysis) - ly1 = Colocacion.H().control(self.pboard).otro(lyLV) - layout = Colocacion.V().control(tb).otro(ly1) + + splitter = QtGui.QSplitter(self) + splitter.setOrientation(QtCore.Qt.Vertical) + splitter.addWidget(self.glines) + splitter.addWidget(self.tabsanalisis) + + sp = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + splitter.setSizePolicy(sp) + + self.registrarSplitter(splitter, "SPLITTER") + + lyLV = Colocacion.V().control(splitter) + lyTB = Colocacion.V().control(self.tb).control(self.pboard) + layout = Colocacion.H().otro(lyTB).otro(lyLV).margen(3) self.setLayout(layout) self.colorPar = QTUtil.qtColor("#DBDAD9") self.colorNon = QTUtil.qtColor("#F1EFE9") self.colorLine = QTUtil.qtColor("#CDCCCB") - self.analisis() self.partida = self.partidabase + self.pboard.MoverFinal() self.recuperarVideo() - def voyager2(self): + def utilidades(self): + menu = QTVarios.LCMenu(self) + submenu = menu.submenu(_("Analysis"), Iconos.Analizar()) + submenu.opcion(self.ta_massive, _("Mass analysis"), Iconos.Analizar()) + submenu.separador() + submenu.opcion(self.ta_remove, _("Delete all previous analysis"), Iconos.Delete()) + menu.separador() + resp = menu.lanza() + if resp: + resp() + + def ta_massive(self): + dicVar = self.configuracion.leeVariables("MASSIVE_OLINES") + + liGen = [FormLayout.separador] + + config = FormLayout.Combobox(_("Engine"), self.configuracion.comboMotoresMultiPV10(4)) + liGen.append((config, dicVar.get("ENGINE", self.configuracion.tutor))) + + liGen.append((_("Duration of engine analysis (secs)") + ":", dicVar.get("SEGUNDOS", float(self.configuracion.tiempoTutor / 1000.0)))) + liDepths = [("--", 0)] + for x in range(1, 51): + liDepths.append((str(x), x)) + config = FormLayout.Combobox(_("Depth"), liDepths) + liGen.append((config, dicVar.get("DEPTH", self.configuracion.depthTutor))) + + li = [(_("Maximum"), 0)] + for x in (1, 3, 5, 10, 15, 20, 30, 40, 50, 75, 100, 150, 200): + li.append((str(x), x)) + config = FormLayout.Combobox(_("Number of moves evaluated by engine(MultiPV)"), li) + liGen.append((config, dicVar.get("MULTIPV", self.configuracion.tutorMultiPV))) + + liGen.append(FormLayout.separador) + liGen.append((_("Redo any existing prior analyses (if they exist)") + ":", dicVar.get("REDO", False))) + + resultado = FormLayout.fedit(liGen, title=_("Mass analysis"), parent=self, anchoMinimo=460, + icon=Iconos.Analizar()) + if resultado is None: + return + + claveMotor, tiempo, depth, multiPV, redo = resultado[1] + ms = int(tiempo * 1000) + if ms == 0 and depth == 0: + return + + dicVar["ENGINE"] = claveMotor + dicVar["SEGUNDOS"] = tiempo + dicVar["DEPTH"] = depth + dicVar["MULTIPV"] = multiPV + dicVar["REDO"] = redo + self.configuracion.escVariables("MASSIVE_OLINES", dicVar) + + um = QTUtil2.unMomento(self) + stFensM2 = self.dbop.getAllFen() + if redo == False: + liBorrar = [] + for fenM2 in stFensM2: + dic = self.dbop.getfenvalue(fenM2) + if "ANALISIS" in dic: + liBorrar.append(fenM2) + for fenM2 in liBorrar: + stFensM2.remove(fenM2) + + conf_engine = copy.deepcopy(self.configuracion.buscaMotor(claveMotor)) + conf_engine.actMultiPV(multiPV) + xgestor = self.procesador.creaGestorMotor(conf_engine, ms, depth, True) + + um.final() + + mensaje = _("Move") + " %d/" + str(len(stFensM2)) + tmpBP = QTUtil2.BarraProgreso(self, _("Mass analysis"), "", len(stFensM2)) + + done = 0 + + for n, fenM2 in enumerate(stFensM2, 1): + + if tmpBP.siCancelado(): + break + + tmpBP.inc() + tmpBP.mensaje(mensaje % n) + + mrm = xgestor.analiza(fenM2 + " 0 1") + dic = self.dbop.getfenvalue(fenM2) + dic["ANALISIS"] = mrm + self.dbop.setfenvalue(fenM2, dic) + done += 1 + + tmpBP.cerrar() + + def ta_remove(self): + if QTUtil2.pregunta(self, _("Are you sure?")): + total = len(self.dbop.db_fenvalues) + mensaje = _("Move") + " %d/" + str(total) + tmpBP = QTUtil2.BarraProgreso(self, "", "", total) + self.dbop.removeAnalisis(tmpBP, mensaje) + tmpBP.cerrar() + self.glines.refresh() + + + def train(self): + if self.train_test(): + menu = QTVarios.LCMenu(self) + menu.opcion("tr_sequential", _("Sequential"), Iconos.TrainSequential()) + menu.separador() + menu.opcion("tr_static", _("Static"), Iconos.TrainStatic()) + menu.separador() + menu.opcion("tr_positions", _("Positions"), Iconos.TrainPositions()) + menu.separador() + submenu = menu.submenu(_("Configuration"), Iconos.Configurar()) + submenu.opcion("update", _("Update current trainings"), Iconos.Reindexar()) + submenu.separador() + submenu.opcion("new", _("Re-create all trainings"), Iconos.Modificar()) + resp = menu.lanza() + if resp is None: + return + if resp.startswith("tr_"): + self.resultado = resp + self.accept() + elif resp == "new": + self.trainNew() + elif resp == "update": + self.trainUpdate() + + def train_test(self): + if len(self.dbop) == 0: + return False + training = self.dbop.training() + if training is None: + return self.trainNew() + return True + + def trainNew(self): + training = self.dbop.training() + if training is None: + color = "WHITE" + random_order = False + max_moves = 0 + else: + color = training["COLOR"] + random_order = training["RANDOM"] + max_moves = training["MAXMOVES"] + + liGen = [(None, None)] + + liJ = [(_("White"), "WHITE"), (_("Black"), "BLACK")] + config = FormLayout.Combobox(_("Play with"), liJ) + liGen.append((config, color)) + + liGen.append((None, None)) + liGen.append((_("Random order"), random_order)) + + liGen.append((None, None)) + liGen.append((_("Maximum number of moves (0=all)"), max_moves)) + + resultado = FormLayout.fedit(liGen, title=_("New training"), parent=self, anchoMinimo=360, icon=Iconos.Study()) + if resultado is None: + return + + accion, liResp = resultado + + reg = {} + + reg["COLOR"], reg["RANDOM"], reg["MAXMOVES"] = liResp + + self.dbop.createTraining(reg, self.configuracion) + + QTUtil2.mensaje(self, _("The trainings of this opening has been created")) + + def trainUpdate(self): + self.dbop.updateTraining() + QTUtil2.mensaje(self, _("The trainings have been updated")) + + def addPartida(self, partida): + if partida.pv().startswith(self.partidabase.pv()): + siNueva, num_linea, siAppend = self.dbop.posPartida(partida) + if siNueva: + self.dbop.append(partida) + else: + if siAppend: + self.dbop[num_linea] = partida + self.glines.refresh() + else: + QTUtil2.mensError(self, _X("New line must begin with %1", self.partidabase.pgnSP())) + + def partidaActual(self): partida = Partida.Partida() - partida.leeOtra(self.partidabase) - ptxt = Voyager.voyagerPartida(self.owner, partida) + numcol = self.glines.posActualN()[1] + partida.leeOtra(self.partida if self.partida and numcol> 0 else self.partidabase) + if self.num_jg_actual is not None \ + and self.num_jg_inicial <= self.num_jg_actual < len(partida): + partida.liJugadas = partida.liJugadas[:self.num_jg_actual+1] + partida.ultPosicion = partida.jugada(-1).posicion + return partida + + def voyager2(self, partida): + ptxt = Voyager.voyagerPartida(self, partida) if ptxt: partida = Partida.Partida() partida.recuperaDeTexto(ptxt) - if self.partidabase.pv() in partida.pv(): - siNueva, num_linea, siAppend = self.dbop.posPartida(partida) - if siNueva: - self.dbop.append(partida) - else: - if siAppend: - self.dbop[num_linea] = partida - self.glines.refresh() - else: - QTUtil2.mensError(self, _X("New line must begin with %1", self.partidabase.pgnSP())) + self.addPartida(partida) def importar(self): menu = QTVarios.LCMenu(self) - liOp = self.dbop.getOtras(self.configuracion) - if liOp: - otra = menu.submenu(_("Other line openings"), Iconos.OpeningLines()) - for fichero, titulo in liOp: - otra.opcion(("ol", fichero), titulo, Iconos.PuntoVerde()) + def haz_menu(frommenu, part): + liOp = self.dbop.getOtras(self.configuracion, part) + if liOp: + otra = frommenu.submenu(_("Other opening lines"), Iconos.OpeningLines()) + for fichero, titulo in liOp: + otra.opcion(("ol", (fichero, part)), titulo, Iconos.PuntoVerde()) + frommenu.separador() + frommenu.opcion(("pgn", part), _("PGN with variants"), Iconos.Tablero()) + frommenu.separador() + frommenu.opcion(("polyglot", part), _("Polyglot book"), Iconos.Libros()) + frommenu.separador() + frommenu.opcion(("summary", part), _("Database summary"), Iconos.DatabaseC()) + frommenu.separador() + frommenu.opcion(("voyager2", part), _("Voyager 2"), Iconos.Voyager1()) + frommenu.separador() + frommenu.opcion(("opening", part), _("Opening"), Iconos.Apertura()) + + partida = self.partidaActual() + if len(partida) > len(self.partidabase): + sub1 = menu.submenu(_("From current position"), Iconos.MoverLibre()) + haz_menu(sub1, partida) menu.separador() - menu.opcion(("pgn", None), _("PGN with variants"), Iconos.Tablero()) - menu.separador() - menu.opcion(("polyglot", None), _("Polyglot book"), Iconos.Libros()) - menu.separador() - menu.opcion(("summary", None), _("Database summary"), Iconos.DatabaseC()) - menu.separador() + sub2 = menu.submenu(_("From base position"), Iconos.MoverInicio()) + haz_menu(sub2, self.partidabase) + else: + haz_menu(menu, self.partidabase) + resp = menu.lanza() if resp is None: return - tipo, arg = resp + tipo, partida = resp if tipo == "pgn" : - self.importarPGN() + self.importarPGN(partida) elif tipo == "polyglot": - self.importarPolyglot() + self.importarPolyglot(partida) elif tipo == "summary": - pass + self.importarSummary(partida) + elif tipo == "voyager2": + self.voyager2(partida) + elif tipo == "opening": + self.importarApertura(partida) elif tipo == "ol": - pass - - def importarPolyglot(self): - previo = self.configuracion.leeVariables("WBG_MOVES") - carpeta = previo.get("CARPETABIN", "") + fichero, partida = partida + self.importarOtra(fichero, partida) + + def importarOtra(self, fichero, partida): + um = QTUtil2.unMomento(self) + pathFichero = os.path.join(self.configuracion.folderOpenings, fichero) + self.dbop.importarOtra(pathFichero, partida) + um.final() + self.glines.refresh() + self.glines.gotop() - ficheroBIN = QTUtil2.leeFichero(self, carpeta, "%s (*.bin)" % _("Polyglot book"), titulo=_("File to import")) - if not ficheroBIN: - return - previo["CARPETABIN"] = os.path.dirname(ficheroBIN) - self.configuracion.escVariables("WBG_MOVES", previo) + def importarApertura(self, partida): + partida.asignaApertura() + w = PantallaAperturas.WAperturas(self, self.configuracion, partida.apertura) + if w.exec_(): + ap = w.resultado() + partida = Partida.Partida() + partida.leerPV(ap.a1h8) + self.addPartida(partida) + def importarLeeParam(self, titulo, dicData): liGen = [(None, None)] liGen.append((None, _("Select a maximum number of moves (plies)
to consider from each game"))) - - liGen.append((FormLayout.Spinbox(_("Depth"), 3, 99, 50), 30)) - liGen.append((None, None)) - - liGen.append((_("Only white best moves"), False)) + liGen.append((FormLayout.Spinbox(_("Depth"), 3, 99, 50), dicData.get("DEPTH", 30))) liGen.append((None, None)) - liGen.append((_("Only black best moves"), False)) + li = [(_("Only white best moves"), True), (_("Only black best moves"), False)] + config = FormLayout.Combobox(_("Moves"), li) + liGen.append((config, dicData.get("SIWHITE", True))) liGen.append((None, None)) - resultado = FormLayout.fedit(liGen, title=os.path.basename(ficheroBIN), parent=self, anchoMinimo=360, - icon=Iconos.PuntoNaranja()) - + resultado = FormLayout.fedit(liGen, title=titulo, parent=self, anchoMinimo=360, icon=Iconos.PuntoNaranja()) if resultado: accion, liResp = resultado - depth, whiteBest, blackBest = liResp - self.dbop.grabarPolyglot(self, ficheroBIN, depth, whiteBest, blackBest) + depth, siWhite = liResp + dicData["DEPTH"] = depth + dicData["SIWHITE"] = siWhite + self.configuracion.escVariables("WBG_MOVES", dicData) + return dicData + return None + + def importarSummary(self, partida): + nomfichgames = QTVarios.selectDB(self, self.configuracion, False, True) + if nomfichgames: + previo = self.configuracion.leeVariables("OPENINGLINES") + dicData = self.importarLeeParam(_("Database summary"), previo) + if dicData: + ficheroSummary = nomfichgames + "_s1" + depth, siWhite = dicData["DEPTH"], dicData["SIWHITE"] + self.dbop.importarSummary(self, partida, ficheroSummary, depth, siWhite) + self.glines.refresh() + self.glines.gotop() + + def importarPolyglot(self): + dicData = self.configuracion.leeVariables("OPENINGLINES") + carpeta = dicData.get("CARPETABIN", "") + + ficheroBIN = QTUtil2.leeFichero(self, carpeta, "%s (*.bin)" % _("Polyglot book"), titulo=_("File to import")) + if not ficheroBIN: + return + + dicData["CARPETABIN"] = os.path.dirname(ficheroBIN) + dicData = self.importarLeeParam(dicData) + if dicData: + depth, siWhite = dicData["DEPTH"], dicData["SIWHITE"] + self.dbop.importarPolyglot(self, ficheroBIN, depth, siWhite) self.glines.refresh() self.glines.gotop() - def importarPGN(self): + def importarPGN(self, partida): previo = self.configuracion.leeVariables("OPENINGLINES") carpeta = previo.get("CARPETAPGN", "") @@ -326,23 +617,16 @@ def importarPGN(self): accion, liResp = resultado depth = liResp[0] - self.dbop.grabarPGN(self, ficheroPGN, depth) + self.dbop.importarPGN(self, partida, ficheroPGN, depth) self.glines.refresh() self.glines.gotop() - def analisis(self): - x = self.gb_analysis.isChecked() - if not x: - self.tabsanalisis.hide() - else: - self.tabsanalisis.show() - def gridColorFondo(self, grid, fila, oColumna): col = oColumna.clave - linea = fila//2 if col == "LINE": return self.colorLine else: + linea = fila // 2 return self.colorPar if linea % 2 == 0 else self.colorNon def gridCambiadoRegistro(self, grid, fila, oColumna): @@ -355,7 +639,8 @@ def gridCambiadoRegistro(self, grid, fila, oColumna): if not iswhite: njug += 1 else: - njug = self.partida.numJugadas()-1 + njug = None + self.num_jg_actual = njug self.pboard.ponPartida(self.partida) self.pboard.colocatePartida(njug) self.glines.setFocus() @@ -415,8 +700,12 @@ def gridNumDatos(self, grid): return len(self.dbop)*2 def gridTeclaControl(self, grid, k, siShift, siControl, siAlt): - if k == QtCore.Qt.Key_Delete: - self.borrar_move() + if k in (QtCore.Qt.Key_Delete, QtCore.Qt.Key_Backspace): + fila, col = self.glines.posActual() + if col.clave == "LINE": + self.borrar() + else: + self.borrar_move() def borrar_move(self): fila, col = self.glines.posActual() @@ -438,7 +727,8 @@ def borrar_move(self): if njug == self.num_jg_inicial: return self.borrar() - if QTUtil2.pregunta(self, _("Do you want to eliminate this move?")): + siUltimo = njug == len(partida)-1 # si es el ultimo no se pregunta + if siUltimo or QTUtil2.pregunta(self, _("Do you want to eliminate this move?")): partida.liJugadas = partida.liJugadas[:njug] partida.ultPosicion = partida.jugada(-1).posicion self.dbop[linea] = partida @@ -446,12 +736,75 @@ def borrar_move(self): self.goto_finlinea() def borrar(self): - fila = self.glines.recno() - linea = fila//2 - if 0 <= linea < len(self.dbop): - if QTUtil2.pregunta(self, _X("Do you want to remove line %1", str(linea+1))): - del self.dbop[linea] - self.goto_finlinea() + tam_dbop = len(self.dbop) + if tam_dbop == 0: + return + current = self.glines.recno()//2 + li = [] + if 0 <= current < tam_dbop: + li.append(["current", _("Remove line %d") % (current+1,), Iconos.Mover()]) + if tam_dbop > 1: + li.append(["lines", _("Remove a list of lines"), Iconos.MoverLibre()]) + + if len(li) > 0: + menu = QTVarios.LCMenu(self) + for key, title, ico in li: + menu.opcion(key, title, ico) + menu.separador() + resp = menu.lanza() + + if resp == "current": + del self.dbop[current] + self.goto_inilinea() + + else: + liGen = [FormLayout.separador] + config = FormLayout.Editbox("
" + _("Lines") + "
" + + _("By example:") + " -5,8-12,14,19-", + rx="[0-9,\-,\,]*") + liGen.append((config, "")) + resultado = FormLayout.fedit(liGen, title=_("Remove a list of lines"), parent=self, anchoMinimo=460, icon=Iconos.OpeningLines()) + if resultado: + accion, liResp = resultado + clista = liResp[0] + if clista: + ln = Util.ListaNumerosImpresion(clista) + li = ln.selected(range(1, tam_dbop+1)) + sli = [] + cad = "" + for num in li: + if cad: + cad += "," + str(num) + else: + cad = str(num) + if len(cad) > 80: + sli.append(cad) + cad = "" + if cad: + sli.append(cad) + cli = "\n".join(sli) + if QTUtil2.pregunta(self, _("Do you want to remove the next lines ?") + "\n\n" + cli): + li.sort(reverse=True) + um = QTUtil2.unMomento(self, _("Working...")) + for num in li: + del self.dbop[num-1] + self.glines.refresh() + self.goto_inilinea() + um.final() + + def goto_inilinea(self): + nlines = len(self.dbop) + if nlines == 0: + return + + linea = self.glines.recno() // 2 + if linea >= nlines: + linea = nlines-1 + + fila = linea*2 + ncol = 0 + self.glines.goto(fila, ncol) + self.glines.refresh() def goto_finlinea(self): nlines = len(self.dbop) @@ -478,6 +831,7 @@ def goto_finlinea(self): self.glines.refresh() def terminar(self): + self.tabsanalisis.saveConfig() self.guardarVideo() self.accept() @@ -485,9 +839,12 @@ def mueveHumano(self, partida): # Estamos en la misma linea ? # recno = self.glines.recno() # Buscamos en las lineas si hay alguna que el pv sea parcial o totalmente igual + partida.pendienteApertura = True siNueva, num_linea, siAppend = self.dbop.posPartida(partida) siBlancas = partida.jugada(-1).siBlancas() - ncol = (partida.numJugadas() - self.num_jg_inicial) // 2 + 1 + ncol = (partida.numJugadas() - self.num_jg_inicial + 1) // 2 + if self.num_jg_inicial%2 == 1 and siBlancas: + ncol += 1 if siNueva: self.dbop.append(partida) else: @@ -504,6 +861,86 @@ def mueveHumano(self, partida): self.glines.refresh() +class WStaticTraining(QTVarios.WDialogo): + def __init__(self, procesador, dbop): + self.training = dbop.training() + self.ligames = self.training["LIGAMES_STATIC"] + self.num_games = len(self.ligames) + self.elems_fila = 10 + if self.num_games < self.elems_fila: + self.elems_fila = self.num_games + self.num_filas = (self.num_games-1) / self.elems_fila + 1 + self.seleccionado = None + + titulo = "%s - %s" % (_("Opening lines"), _("Static training")) + + extparam = "openlines_static_%s" % dbop.nomFichero + + QTVarios.WDialogo.__init__(self, procesador.pantalla, titulo, Iconos.TrainStatic(), extparam) + + lb = Controles.LB(self, dbop.gettitle()) + lb.ponFondoN("#BDDBE8").alinCentrado().ponTipoLetra(puntos=14) + + # Toolbar + tb = Controles.TBrutina(self) + tb.new(_("Close"), Iconos.MainMenu(), self.terminar) + + # Lista + ancho = 42 + oColumnas = Columnas.ListaColumnas() + oColumnas.nueva("FILA", "", 36, siCentrado=True) + for x in range(self.elems_fila): + oColumnas.nueva("COL%d" % x, "%d" % (x+1,), ancho, siCentrado=True, edicion=Delegados.PmIconosWeather()) + + self.grid = Grid.Grid(self, oColumnas, altoFila=ancho, background="white") + self.grid.setAlternatingRowColors(False) + self.grid.tipoLetra(puntos=10, peso=500) + nAnchoPgn = self.grid.anchoColumnas() + 20 + self.grid.setMinimumWidth(nAnchoPgn) + + ly = Colocacion.V().control(lb).control(tb).control(self.grid) + self.setLayout(ly) + + alto = self.num_filas*ancho + 146 + self.recuperarVideo(siTam=True, altoDefecto=alto, anchoDefecto=nAnchoPgn) + + def terminar(self): + + self.guardarVideo() + self.reject() + + def gridNumDatos(self, grid): + return self.num_filas + + def gridDato(self, grid, fila, oColumna): + col = oColumna.clave + if col == "FILA": + return "%d" % (fila + 1) + elif col.startswith("COL"): + num = fila*self.elems_fila + int(col[3:]) + if num >= self.num_games: + return None + game = self.ligames[num] + sinerror = game["NOERROR"] + return str(sinerror) if sinerror < 4 else "4" + + def gridDobleClick(self, grid, fila, oColumna): + col = oColumna.clave + if col.startswith("COL"): + num = fila*self.elems_fila + int(col[3:]) + if num >= self.num_games: + return + self.seleccionado = num + self.guardarVideo() + self.accept() + + +def selectLine(procesador, dbop): + w = WStaticTraining(procesador, dbop) + w.exec_() + return w.seleccionado + + def openingLines(procesador): w = WOpeningLines(procesador) return w.resultado if w.exec_() else None @@ -512,7 +949,8 @@ def openingLines(procesador): def study(procesador, fichero): with QTUtil.EscondeWindow(procesador.pantalla): dbop = OpeningLines.Opening(os.path.join(procesador.configuracion.folderOpenings, fichero)) - w = WStudy(procesador, dbop) + w = WLines(procesador, dbop) w.exec_() dbop.close() + return w.resultado diff --git a/Code/QT/PantallaAnalisis.py b/Code/QT/PantallaAnalisis.py index daca169..1ecc15c 100644 --- a/Code/QT/PantallaAnalisis.py +++ b/Code/QT/PantallaAnalisis.py @@ -74,7 +74,7 @@ def xcol(): confTablero = VarGen.configuracion.confTablero("ANALISISGRAPH", 48) self.tablero = Tablero.Tablero(self, confTablero) self.tablero.crea() - self.tablero.ponerPiezasAbajo(True) + self.tablero.ponerPiezasAbajo(alm.siBlancasAbajo) self.tablero.dispatchSize(self.tableroSizeChanged) self.capturas = WCapturas.CapturaLista(self, self.tablero) @@ -431,7 +431,8 @@ def grabarTodos(self): if resp: for pos, tp in enumerate(self.um.listaRM): rm = tp[0] - partida = Partida.Partida(self.um.jg.posicionBase).leerPV(rm.pv) + partida = Partida.Partida(self.um.jg.posicionBase) + partida.leerPV(rm.pv) self.um.grabarBase(partida, rm, resp) self.um.ponVistaGestor() diff --git a/Code/QT/PantallaAnalisisParam.py b/Code/QT/PantallaAnalisisParam.py index 5dd4ea8..ef3439c 100644 --- a/Code/QT/PantallaAnalisisParam.py +++ b/Code/QT/PantallaAnalisisParam.py @@ -6,7 +6,7 @@ from Code import Util from Code import EngineThread -SEPARADOR = (None, None) +SEPARADOR = FormLayout.separador def leeDicParametros(configuracion): diff --git a/Code/QT/PantallaAperturas.py b/Code/QT/PantallaAperturas.py index 6614d1f..de00cfb 100644 --- a/Code/QT/PantallaAperturas.py +++ b/Code/QT/PantallaAperturas.py @@ -150,7 +150,7 @@ def ponActivas(self): self.tablero.ponPosicion(self.partida.ultPosicion) - self.apStd.asignaApertura(self.partida) + self.partida.asignaApertura() txt = self.partida.pgnSP() if self.partida.apertura: txt = '%s
%s' % (self.partida.apertura.nombre, txt) @@ -333,7 +333,7 @@ def leeBloques(self, liPV): for pv in liPV: p = Partida.Partida() p.leerPV(pv) - self.listaAperturasStd.asignaApertura(p) + p.asignaApertura() ap = p.apertura if ap is None: ap = AperturasStd.AperturasStd(_("Unknown")) diff --git a/Code/QT/PantallaArbol.py b/Code/QT/PantallaArbol.py index 015dc15..ea4699b 100644 --- a/Code/QT/PantallaArbol.py +++ b/Code/QT/PantallaArbol.py @@ -1,6 +1,6 @@ import collections -import LCEngine +import LCEngineV1 as LCEngine from PyQt4 import QtGui, QtCore diff --git a/Code/QT/PantallaColores.py b/Code/QT/PantallaColores.py index f7641b1..56b0da2 100644 --- a/Code/QT/PantallaColores.py +++ b/Code/QT/PantallaColores.py @@ -438,18 +438,13 @@ def l2mas1(lyG, fila, a, b, c): self.tablero.ponPosicion(cp) self.rehazFlechas() - liAcciones = [(_("Accept"), Iconos.Aceptar(), "aceptar"), - None, - (_("Cancel"), Iconos.Cancelar(), "cancelar"), - None, - (_("Your themes"), Iconos.Temas(), "temas"), - None, - (_("Import"), Iconos.Mezclar(), "importar"), - None, - (_("Export"), Iconos.Grabar(), "exportar"), - None, + liAcciones = [(_("Accept"), Iconos.Aceptar(), self.aceptar), None, + (_("Cancel"), Iconos.Cancelar(), self.cancelar), None, + (_("Your themes"), Iconos.Temas(), self.temas), None, + (_("Import"), Iconos.Mezclar(), self.importar), None, + (_("Export"), Iconos.Grabar(), self.exportar), None, ] - tb = Controles.TB(self, liAcciones) + tb = Controles.TBrutina(self, liAcciones) # tam tablero self.lbTamTablero = Controles.LB(self, "%d px" % self.tablero.width()) @@ -495,10 +490,10 @@ def extendedColor(self): def rehazFlechas(self): self.tablero.quitaFlechas() - self.tablero.creaFlechaTmp("b8", "b4", True) - self.tablero.creaFlechaTmp("f5", "c2", False) - self.tablero.creaFlechaMov("d1", "h5", "ms100") - self.tablero.creaFlechaMov("d8", "d5", "mt100") + self.tablero.creaFlechaTmp("f2", "f4", True) + self.tablero.creaFlechaTmp("d1", "d4", False) + self.tablero.creaFlechaMov("f5", "d7", "ms100") + self.tablero.creaFlechaMov("d6", "b4", "mt100") def cambiadoTema(self): fichTema = self.cbTemas.valor() @@ -573,27 +568,16 @@ def defectoTemas(self): self.actualizaTablero() - def procesarTB(self): - accion = self.sender().clave - if accion == "aceptar": + def aceptar(self): + self.confTablero.guardaEnDisco() + self.tableroOriginal.reset(self.confTablero) - self.confTablero.guardaEnDisco() - self.tableroOriginal.reset(self.confTablero) + self.guardarVideo() + self.accept() - self.guardarVideo() - self.accept() - elif accion == "cancelar": - self.guardarVideo() - self.reject() - - elif accion == "temas": - self.temas() - - elif accion == "importar": - self.importar() - - elif accion == "exportar": - self.exportar() + def cancelar(self): + self.guardarVideo() + self.reject() def importar(self): # import os diff --git a/Code/QT/PantallaConfig.py b/Code/QT/PantallaConfig.py index 049caf1..9e76ee9 100644 --- a/Code/QT/PantallaConfig.py +++ b/Code/QT/PantallaConfig.py @@ -116,6 +116,16 @@ def opciones(parent, configuracion): liTT.append(separador) liTT.append((_("Tutor enabled"), configuracion.tutorActivoPorDefecto)) liTT.append(separador) + + # Mostrando el tutor + # kTutorH, kTutorH2_1, kTutorH1_2, kTutorV + liPosTutor = [configuracion.vistaTutor, (kTutorH, _("Horizontal")), + (kTutorH2_1, _("Horizontal") + " 2+1"), + (kTutorH1_2, _("Horizontal") + " 1+2"), + (kTutorV, _("Vertical"))] + liTT.append((_("Tutor boards position") + ":", liPosTutor)) + liTT.append(separador) + liTT.append(separador) liTT.append((None, _("Sensitivity"))) liTT.append((FormLayout.Spinbox(_("Minimum difference in points"), 0, 1000, 70), configuracion.tutorDifPts)) liTT.append((FormLayout.Spinbox(_("Minimum difference in %"), 0, 1000, 70), configuracion.tutorDifPorc)) @@ -158,56 +168,49 @@ def d(num): liSA.append((config, configuracion.salvarCSV)) # Boards - liT = [separador] + liB = [separador] - # Mostrando el tutor - # kTutorH, kTutorH2_1, kTutorH1_2, kTutorV - liPosTutor = [configuracion.vistaTutor, (kTutorH, _("Horizontal")), - (kTutorH2_1, _("Horizontal") + " 2+1"), - (kTutorH1_2, _("Horizontal") + " 1+2"), - (kTutorV, _("Vertical"))] - liT.append((_("Tutor boards position") + ":", liPosTutor)) - liT.append(separador) - liT.append((_("Visual effects") + ":", configuracion.efectosVisuales)) + liB.append((_("Visual effects") + ":", configuracion.efectosVisuales)) drap = {1: 100, 2: 150, 3: 200, 4: 250, 5: 300, 6: 350, 7: 400, 8: 450, 9: 500} drapV = {} for x in drap: drapV[drap[x]] = x - liT.append((FormLayout.Dial("%s (%s=1)" % (_("Speed"), _("Default")), 1, len(drap), siporc=False), + liB.append((FormLayout.Dial("%s (%s=1)" % (_("Speed"), _("Default")), 1, len(drap), siporc=False), drapV.get(configuracion.rapidezMovPiezas, 100))) - liT.append(separador) + liB.append(separador) liMouseSH = [configuracion.siAtajosRaton, (False, _("Type fixed: you must always indicate origin and destination")), (True, _("Type predictive: program tries to guess your intention"))] - liT.append((_("Mouse shortcuts") + ":", liMouseSH)) - liT.append((_("Show candidates") + ":", configuracion.showCandidates)) - liT.append((_("Show arrows of variants") + ":", configuracion.showVariantes)) - liT.append((_("Always promote to queen\nALT key allows to change") + ":", configuracion.autocoronacion)) - liT.append((_("Show cursor when engine is thinking") + ":", configuracion.cursorThinking)) - liT.append(separador) - liT.append((_("Enable captured material window by default") + ":", configuracion.siActivarCapturas)) + liB.append((_("Mouse shortcuts") + ":", liMouseSH)) + liB.append((_("Show candidates") + ":", configuracion.showCandidates)) + liB.append((_("Show arrows of variants") + ":", configuracion.showVariantes)) + liB.append((_("Always promote to queen\nALT key allows to change") + ":", configuracion.autocoronacion)) + liB.append((_("Show cursor when engine is thinking") + ":", configuracion.cursorThinking)) + liB.append(separador) + liB.append((_("Enable captured material window by default") + ":", configuracion.siActivarCapturas)) liMat = [configuracion.tipoMaterial, ("D", _("Difference material")), ("C", _("Captured material at beginning")), ("M", _("Material advantage"))] - liT.append((_("Show material") + ":", liMat)) - liT.append(separador) - liT.append((_("Enable information panel by default") + ":", configuracion.siActivarInformacion)) - liT.append(separador) - liT.append((_X(_("Enable %1"), _("DGT board")) + ":", configuracion.siDGT)) - liT.append(separador) + liB.append((_("Show material") + ":", liMat)) + liB.append(separador) + liB.append((_("Enable information panel by default") + ":", configuracion.siActivarInformacion)) + liB.append(separador) + liB.append((_X(_("Enable %1"), _("DGT board")) + ":", configuracion.siDGT)) + liB.append(separador) # liT.append((FormLayout.Dial(_("Opacity of tool icon"), 1, 9, siporc=False), configuracion.opacityToolBoard)) - liT.append((_("Show configuration icon"), configuracion.opacityToolBoard > 6)) + liB.append((_("Show configuration icon"), configuracion.opacityToolBoard > 6)) liPos = [configuracion.positionToolBoard, ("B", _("Bottom")), ("T", _("Top"))] - liT.append((_("Configuration icon position") + ":", liPos)) - liT.append(separador) - liT.append((_("Show icon when position has graphic information"), configuracion.directorIcon)) - liT.append(separador) + liB.append((_("Configuration icon position") + ":", liPos)) + liB.append(separador) + liB.append((_("Show icon when position has graphic information"), configuracion.directorIcon)) + + liB.append(separador) lista = [] lista.append((liGen, _("General"), "")) lista.append((liSon, _("Sounds"), "")) lista.append((liTT, _("Tutor"), "")) - lista.append((liT, _("Boards"), "")) + lista.append((liB, _("Boards"), "")) lista.append((liEng, _("Engines"), "")) lista.append((liAsp, _("Appearance"), "")) lista.append((liPR, _("Performance"), "")) @@ -220,7 +223,7 @@ def d(num): if resultado: accion, resp = resultado - liGen, liSon, liTT, liT, liEng, liAsp, liPR, liSA, liNC = resp + liGen, liSon, liTT, liB, liEng, liAsp, liPR, liSA, liNC = resp (configuracion.jugador, configuracion.estilo, configuracion.traductor, configuracion.checkforupdate) = liGen @@ -237,23 +240,25 @@ def d(num): if configuracion.familia == "System": configuracion.familia = "" - (configuracion.siSuenaBeep, configuracion.siSuenaResultados, configuracion.siSuenaJugada, configuracion.siSuenaNuestro) = liSon + (configuracion.siSuenaBeep, configuracion.siSuenaResultados, + configuracion.siSuenaJugada, configuracion.siSuenaNuestro) = liSon (configuracion.tutor.clave, tiempoTutor, configuracion.depthTutor, configuracion.tutorMultiPV, - configuracion.tutorActivoPorDefecto, configuracion.tutorDifPts, configuracion.tutorDifPorc) = liTT + configuracion.tutorActivoPorDefecto, configuracion.vistaTutor, + configuracion.tutorDifPts, configuracion.tutorDifPorc ) = liTT configuracion.tiempoTutor = int(tiempoTutor * 1000) - (configuracion.eloNC, configuracion.micheloNC, configuracion.ficsNC, configuracion.fideNC, configuracion.lichessNC) = liNC + (configuracion.eloNC, configuracion.micheloNC, configuracion.ficsNC, + configuracion.fideNC, configuracion.lichessNC) = liNC (configuracion.centipawns, configuracion.bmi2, configuracion.notbackground, configuracion.siLogEngines) = liEng - (configuracion.vistaTutor, - configuracion.efectosVisuales, rapidezMovPiezas, + (configuracion.efectosVisuales, rapidezMovPiezas, configuracion.siAtajosRaton, configuracion.showCandidates, configuracion.showVariantes, configuracion.autocoronacion, configuracion.cursorThinking, configuracion.siActivarCapturas, configuracion.tipoMaterial, configuracion.siActivarInformacion, siDGT, toolIcon, configuracion.positionToolBoard, - configuracion.directorIcon) = liT + configuracion.directorIcon) = liB configuracion.opacityToolBoard = 10 if toolIcon else 1 configuracion.rapidezMovPiezas = drap[rapidezMovPiezas] if configuracion.siDGT != siDGT: diff --git a/Code/QT/PantallaDailyTest.py b/Code/QT/PantallaDailyTest.py index 5349c87..9fc0a17 100644 --- a/Code/QT/PantallaDailyTest.py +++ b/Code/QT/PantallaDailyTest.py @@ -62,7 +62,7 @@ def __init__(self, procesador): def leeParametros(self): param = Util.DicSQL(self.configuracion.ficheroDailyTest, tabla="parametros") - motor = param.get("MOTOR", "stockfish") + motor = param.get("MOTOR", "mcbrain") segundos = param.get("SEGUNDOS", 7) pruebas = param.get("PRUEBAS", 5) fns = param.get("FNS", "") @@ -216,7 +216,7 @@ def __init__(self, owner, liFens, motor, segundos, fns): if motor.startswith("*"): motor = motor[1:] - confMotor = self.configuracion.buscaTutor(motor, "stockfish") + confMotor = self.configuracion.buscaTutor(motor, "mcbrain") self.xtutor = self.procesador.creaGestorMotor(confMotor, segundos * 1000, None) self.xtutor.maximizaMultiPV() diff --git a/Code/QT/WBDatabaseFEN.py b/Code/QT/PantallaDatabaseFEN.py similarity index 100% rename from Code/QT/WBDatabaseFEN.py rename to Code/QT/PantallaDatabaseFEN.py diff --git a/Code/QT/PantallaEverest.py b/Code/QT/PantallaEverest.py index de5f0c8..75f14aa 100644 --- a/Code/QT/PantallaEverest.py +++ b/Code/QT/PantallaEverest.py @@ -1,7 +1,7 @@ import os.path import random -import LCEngine +import LCEngineV1 as LCEngine from PyQt4 import QtSvg, QtCore from Code import Everest diff --git a/Code/QT/PantallaGM.py b/Code/QT/PantallaGM.py index 2a61bd7..533ca73 100644 --- a/Code/QT/PantallaGM.py +++ b/Code/QT/PantallaGM.py @@ -192,7 +192,10 @@ def __init__(self, procesador, siWoman): self.recuperarVideo(anchoDefecto=450) def cambiadoDepth(self, num): - self.edJtiempo.ponFloat(0.0 if num > 0 else 3.0) + tiempo = self.edJtiempo.textoFloat() + if int(tiempo)*10 == 0: + tiempo = 3.0 + self.edJtiempo.ponFloat(0.0 if num > 0 else tiempo) self.edJtiempo.setEnabled(num == 0) def closeEvent(self, event): @@ -606,7 +609,7 @@ def importarGM(ownerGM, siWoman): # Primero nos tenemos que traer la lista de la web fichz = "_listaGM.zip" ficht = "_listaGM.txt" - fichtg = "gm/listaGM.txt" + fichtg = "GM/listaGM.txt" if siWoman: fichtg = "w" + fichtg try: @@ -647,7 +650,7 @@ def importarGM(ownerGM, siWoman): linea = linea.strip() if linea: gm, nombre, ctam, cpart = linea.split(VarGen.XSEP) - fichero = "gm/%s.xgm" % gm + fichero = "GM/%s.xgm" % gm if siWoman: fichero = "w" + fichero if Util.tamFichero(fichero) != int(ctam): # si no existe tam = -1 @@ -673,7 +676,7 @@ def importarGM(ownerGM, siWoman): zfobj = zipfile.ZipFile(fzip) for name in zfobj.namelist(): - fichero = "gm/%s" % name + fichero = "GM/%s" % name if siWoman: fichero = "w" + fichero outfile = open(fichero, 'wb') diff --git a/Code/QT/PantallaHorses.py b/Code/QT/PantallaHorses.py index a6adbe8..24d64db 100644 --- a/Code/QT/PantallaHorses.py +++ b/Code/QT/PantallaHorses.py @@ -3,7 +3,7 @@ import random import time -import LCEngine +import LCEngineV1 as LCEngine from Code import ControlPosicion from Code.QT import Colocacion diff --git a/Code/QT/PantallaJuicio.py b/Code/QT/PantallaJuicio.py index 74b0382..025c74d 100644 --- a/Code/QT/PantallaJuicio.py +++ b/Code/QT/PantallaJuicio.py @@ -180,7 +180,8 @@ def gridColorFondo(self, grid, fila, oColumna): return None def gridCambiadoRegistro(self, grid, fila, columna): - self.partida = Partida.Partida(self.posicion).leerPV(self.listaRM[fila].rm.pv) + self.partida = Partida.Partida(self.posicion) + self.partida.leerPV(self.listaRM[fila].rm.pv) self.maxMoves = self.partida.numJugadas() self.mueve(siInicio=True) diff --git a/Code/QT/PantallaManualSave.py b/Code/QT/PantallaManualSave.py index 0e308b6..7165a7a 100644 --- a/Code/QT/PantallaManualSave.py +++ b/Code/QT/PantallaManualSave.py @@ -193,7 +193,7 @@ def inicializa(self): self.sb_number.ponValor(dic_vars.get("NUMBER",0)) - self.cb_engine.ponValor(dic_vars.get("ENGINE", "stockfish")) + self.cb_engine.ponValor(dic_vars.get("ENGINE", "mcbrain")) self.sb_multipv.ponValor(dic_vars.get("MULTIPV", 1)) diff --git a/Code/QT/PantallaMotores.py b/Code/QT/PantallaMotores.py index 07b95a6..8eacbdf 100644 --- a/Code/QT/PantallaMotores.py +++ b/Code/QT/PantallaMotores.py @@ -297,7 +297,7 @@ def __init__(self, wParent, listaMotores, motorExterno, siTorneo=False): # # Comprobamos que todos esten accesibles self.listaLibros.comprueba() li = [(x.nombre, x.path) for x in self.listaLibros.lista] - li.insert(0, ("* " + _("Engine book"), "-")) + li.insert(0, ("* " + _("None"), "-")) li.insert(0, ("* " + _("Default"), "*")) self.cbBooks = Controles.CB(self, li, motorExterno.book()) btNuevoBook = Controles.PB(self, "", self.nuevoBook, plano=False).ponIcono(Iconos.Nuevo(), tamIcon=16) diff --git a/Code/QT/PantallaTabDirVisual.py b/Code/QT/PantallaTabDirVisual.py index 8d930eb..c080673 100644 --- a/Code/QT/PantallaTabDirVisual.py +++ b/Code/QT/PantallaTabDirVisual.py @@ -44,19 +44,21 @@ def __init__(self, owner, tablero): self.guion = TabVisual.Guion(tablero, self) # Guion - liAcciones = [(_("Close"), Iconos.MainMenu(), self.terminar), - (_("Cancel"), Iconos.Cancelar(), self.cancelar), - (_("Save"), Iconos.Grabar(), self.grabar), - (_("New"), Iconos.Nuevo(), self.gnuevo), - (_("Insert"), Iconos.Insertar(), self.ginsertar), - (_("Remove"), Iconos.Borrar(), self.gborrar), None, - (_("Up"), Iconos.Arriba(), self.garriba), - (_("Down"), Iconos.Abajo(), self.gabajo), None, - (_("Mark"), Iconos.Marcar(), self.gmarcar), None, - (_("File"), Iconos.Recuperar(), self.gfile), None - ] + liAcciones = [ + (_("Close"), Iconos.MainMenu(), self.terminar), + (_("Cancel"), Iconos.Cancelar(), self.cancelar), + (_("Save"), Iconos.Grabar(), self.grabar), + (_("New"), Iconos.Nuevo(), self.gnuevo), + (_("Insert"), Iconos.Insertar(), self.ginsertar), + (_("Remove"), Iconos.Borrar(), self.gborrar), None, + (_("Up"), Iconos.Arriba(), self.garriba), + (_("Down"), Iconos.Abajo(), self.gabajo), None, + (_("Mark"), Iconos.Marcar(), self.gmarcar), None, + (_("File"), Iconos.Recuperar(), self.gfile), None + ] self.tb = Controles.TBrutina(self, liAcciones, siTexto=False, tamIcon=24) self.tb.setAccionVisible(self.grabar, False) + oColumnas = Columnas.ListaColumnas() oColumnas.nueva("NUMERO", _("N."), 20, siCentrado=True) oColumnas.nueva("MARCADO", "", 20, siCentrado=True, siChecked=True) @@ -74,9 +76,9 @@ def __init__(self, owner, tablero): self.selectBanda = PantallaTab.SelectBanda(self) lyG = Colocacion.V().control(self.g_guion).control(self.chbSaveWhenFinished) - lySG = Colocacion.H().control(self.selectBanda).otro(lyG).relleno(1) layout = Colocacion.V().control(self.tb).otro(lySG).margen(3) + self.setLayout(layout) self.recuperarVideo() @@ -247,11 +249,11 @@ def creaTarea(self, tp, xid, a1h8, fila): if tarea is None: return None, None tarea.registro((tp, xid, a1h8)) + self.g_guion.goto(fila, 0) self.ponMarcado(fila, True) - self.refresh_guion() return tarea, fila def editaNombre(self, nombre): @@ -637,22 +639,6 @@ def editarBanda(self, cid): if len(self.guion): self.ponSiGrabar() - def cierraRecursos(self): - if self.guion is not None: - self.guion.cierraPizarra() - self.dbConfig["SELECTBANDA"] = self.selectBanda.guardar() - self.dbConfig["SELECTBANDANUM"] = self.selectBanda.numSeleccionada() - self.dbConfig["SAVEWHENFINISHED"] = self.chbSaveWhenFinished.valor() - self.dbConfig.close() - self.dbFlechas.close() - self.dbMarcos.close() - self.dbSVGs.close() - self.dbMarkers.close() - - self.guardarVideo() - self.guion.restoreTablero() - self.guion = None - def test_siGrabar(self): if self.siGrabar: if self.chbSaveWhenFinished.valor(): @@ -743,6 +729,22 @@ def leeRecursos(self): self.dbSVGs = Util.DicSQL(fdb, tabla="SVGs") self.dbMarkers = Util.DicSQL(fdb, tabla="Markers") + def cierraRecursos(self): + if self.guion is not None: + self.guion.cierraPizarra() + self.dbConfig["SELECTBANDA"] = self.selectBanda.guardar() + self.dbConfig["SELECTBANDANUM"] = self.selectBanda.numSeleccionada() + self.dbConfig["SAVEWHENFINISHED"] = self.chbSaveWhenFinished.valor() + self.dbConfig.close() + self.dbFlechas.close() + self.dbMarcos.close() + self.dbSVGs.close() + self.dbMarkers.close() + + self.guardarVideo() + self.guion.restoreTablero() + self.guion = None + def actualizaBandas(self): self.selectBanda.iniActualizacion() @@ -855,7 +857,7 @@ def tableroRemove(self, itemSC): self.gborrar() -class DirVisual(): +class DirVisual: def __init__(self, tablero): self.tablero = tablero self.ultTareaSelect = None @@ -905,72 +907,50 @@ def keyPressEvent(self, event): return False def mousePressEvent(self, event): + siRight = event.button() == QtCore.Qt.RightButton p = event.pos() a1h8 = self.punto2a1h8(p) - if event.button() == QtCore.Qt.LeftButton: - m = int(event.modifiers()) - siCtrl = (m & QtCore.Qt.ControlModifier) > 0 - if siCtrl: - li_tareas = self.guion.tareasPosicion(p) - if li_tareas: - pos_guion, tarea = li_tareas[0] - self.w.g_guion.goto(pos_guion, 0) - self.w.gborrar([pos_guion,]) - return - a1h8 = self.punto2a1h8(p) - pz_borrar = self.tablero.dameNomPiezaEn(a1h8) + m = int(event.modifiers()) + if siRight and (m & QtCore.Qt.ControlModifier) > 0: + self.terminar() + return True + + li_tareas = self.guion.tareasPosicion(p) + + if siRight: + pz_borrar = self.tablero.dameNomPiezaEn(a1h8) + menu = Controles.Menu(self.tablero) + dicPieces = TrListas.dicNomPiezas() + icoPiece = self.tablero.piezas.icono + + if pz_borrar or len(li_tareas): + mrem = menu.submenu(_("Remove"), Iconos.Delete()) if pz_borrar: + rotulo = dicPieces[pz_borrar.upper()] + mrem.opcion(("rem_pz", None), rotulo, icoPiece(pz_borrar)) + mrem.separador() + for pos_guion, tarea in li_tareas: + rotulo = "%s - %s - %s" % (tarea.txt_tipo(), tarea.nombre(), tarea.info()) + mrem.opcion(("rem_gr", pos_guion), rotulo, Iconos.Delete()) + mrem.separador() + menu.separador() + + for pz in "KQRBNPkqrbnp": + if pz != pz_borrar: + if pz == "k": + menu.separador() + menu.opcion(("create", pz), dicPieces[pz.upper()], icoPiece(pz)) + resp = menu.lanza() + if resp is not None: + orden, arg = resp + if orden == "rem_gr": + self.w.g_guion.goto(arg, 0) + self.w.gborrar() + elif orden == "rem_pz": self.w.creaTarea("B", pz_borrar, a1h8, -1) - return - - elif event.button() == QtCore.Qt.RightButton: - m = int(event.modifiers()) - siCtrl = (m & QtCore.Qt.ControlModifier) > 0 - li_tareas = self.guion.tareasPosicion(p) - if siCtrl and len(li_tareas) > 0: - pos_guion, tarea = li_tareas[0] - self.w.gborrar([pos_guion,]) - return - - siAlt = (m & QtCore.Qt.AltModifier) > 0 - - if siAlt: - pz_borrar = self.tablero.dameNomPiezaEn(a1h8) - menu = Controles.Menu(self.tablero) - dicPieces = TrListas.dicNomPiezas() - icoPiece = self.tablero.piezas.icono - - if pz_borrar or len(li_tareas): - mrem = menu.submenu(_("Remove"), Iconos.Delete()) - if pz_borrar: - rotulo = dicPieces[pz_borrar.upper()] - mrem.opcion(("rem_pz", None), rotulo, icoPiece(pz_borrar)) - mrem.separador() - for pos_guion, tarea in li_tareas: - rotulo = "%s - %s - %s" % (tarea.txt_tipo(), tarea.nombre(), tarea.info()) - mrem.opcion(("rem_gr", pos_guion), rotulo, Iconos.Delete()) - mrem.separador() - menu.separador() - - for pz in "KQRBNPkqrbnp": - if pz != pz_borrar: - if pz == "k": - menu.separador() - menu.opcion(("create", pz), dicPieces[pz.upper()], icoPiece(pz)) - resp = menu.lanza() - if resp is not None: - orden, arg = resp - if orden == "rem_gr": - self.w.g_guion.goto(arg, 0) - self.w.gborrar() - elif orden == "rem_pz": - self.w.creaTarea("B", pz_borrar, a1h8, -1) - - elif orden == "create": - self.w.creaTarea("C", arg, a1h8, -1) - else: - self.terminar() + elif orden == "create": + self.w.creaTarea("C", arg, a1h8, -1) return True if self.director: @@ -1059,5 +1039,4 @@ def mouseReleaseEvent(self, event): def terminar(self): if self.w: self.w.terminar() - self.w = None - + self.w = None \ No newline at end of file diff --git a/Code/QT/PantallaTorneos.py b/Code/QT/PantallaTorneos.py index 17a8bc1..01562e3 100644 --- a/Code/QT/PantallaTorneos.py +++ b/Code/QT/PantallaTorneos.py @@ -132,7 +132,7 @@ def __init__(self, wParent, torneo): titulo = _("Competition") icono = Iconos.Torneos() - extparam = "untorneo" + extparam = "untorneo_v1" QTVarios.WDialogo.__init__(self, wParent, titulo, icono, extparam) self.configuracion = VarGen.configuracion @@ -175,7 +175,7 @@ def __init__(self, wParent, torneo): # Comprobamos que todos esten accesibles self.listaLibros.comprueba() li = [(x.nombre, x.path) for x in self.listaLibros.lista] - li.insert(0, ("* " + _("Default"), "")) + li.insert(0, ("* " + _("None"), "-")) self.cbBooks = Controles.CB(self, li, torneo.book()) btNuevoBook = Controles.PB(self, "", self.nuevoBook, plano=False).ponIcono(Iconos.Nuevo(), tamIcon=16) lyBook = Colocacion.H().control(self.cbBooks).control(btNuevoBook).relleno() @@ -291,8 +291,8 @@ def __init__(self, wParent, torneo): oColumnas.nueva("NUMERO", _("N."), 35, siCentrado=True) oColumnas.nueva("MOTOR", _("Engine"), 190, siCentrado=True) oColumnas.nueva("GANADOS", _("Wins"), 120, siCentrado=True) - oColumnas.nueva("PERDIDOS", _("Lost"), 120, siCentrado=True) oColumnas.nueva("TABLAS", _("Draw"), 120, siCentrado=True) + oColumnas.nueva("PERDIDOS", _("Lost"), 120, siCentrado=True) oColumnas.nueva("PUNTOS", _("Points"), 120, siCentrado=True) self.gridResult = Grid.Grid(self, oColumnas, siSelecFilas=True, xid="R") self.registrarGrid(self.gridResult) @@ -402,6 +402,11 @@ def gridDatoEnginesValores(self, fila, columna): def borraResult(self): self.liResult = None + def resultLabel(self, lirs): + w, b = lirs + t = w + b + return "0" if t == 0 else "%d (%s:%d - %s:%d)" % (t, _("White"), w, _("Black"), b) + def gridDatoResult(self, fila, columna): if self.liResult is None: self.liResult = self.torneo.rehacerResult() @@ -410,21 +415,15 @@ def gridDatoResult(self, fila, columna): return str(fila + 1) elif columna == "MOTOR": return rs["EN"].alias - elif columna == "GANADOS": - w, b = rs["WIN"] - t = w + b - return "0" if t == 0 else "%d (%d-%d)" % (t, w, b) - elif columna == "PERDIDOS": - w, b = rs["LOST"] - t = w + b - return "0" if t == 0 else "%d (%d-%d)" % (t, w, b) - elif columna == "TABLAS": - w, b = rs["DRAW"] - t = w + b - return "0" if t == 0 else "%d (%d-%d)" % (t, w, b) elif columna == "PUNTOS": p = rs["PTS"] return "%d.%d" % (p / 10, p % 10) + elif columna == "GANADOS": + return self.resultLabel(rs["WIN"]) + elif columna == "PERDIDOS": + return self.resultLabel(rs["LOST"]) + elif columna == "TABLAS": + return self.resultLabel(rs["DRAW"]) def gridDatoGames(self, fila, columna): gm = self.torneo.liGames()[fila] diff --git a/Code/QT/PantallaVisualiza.py b/Code/QT/PantallaVisualiza.py index 21b8afc..07b8d2c 100644 --- a/Code/QT/PantallaVisualiza.py +++ b/Code/QT/PantallaVisualiza.py @@ -1,7 +1,7 @@ import os.path import time -import LCEngine +import LCEngineV1 as LCEngine from PyQt4 import QtCore from Code import ControlPosicion diff --git a/Code/QT/TabElementos.py b/Code/QT/TabElementos.py index 576b597..0740461 100644 --- a/Code/QT/TabElementos.py +++ b/Code/QT/TabElementos.py @@ -1,3 +1,5 @@ +import base64 + from PyQt4 import QtCore, QtGui from Code.QT import Controles @@ -384,9 +386,8 @@ def __init__(self, escena, bloqueImagen, pixmap=None, rutina = None): self.pixmap = pixmap else: self.pixmap = QtGui.QPixmap() - import base64 - self.pixmap.loadFromData(base64.b64decode(bloqueImagen.pixmap), "PNG") + r = self.pixmap.rect() self.pmRect = QtCore.QRectF(0, 0, r.width(), r.height()) diff --git a/Code/QT/Tablero.py b/Code/QT/Tablero.py index db168b4..da765b1 100644 --- a/Code/QT/Tablero.py +++ b/Code/QT/Tablero.py @@ -664,7 +664,7 @@ def lanzaMenuVisual(self, siIzquierdo=False): menu.separador() if self.siDirector: - menu.opcion("director", _("Director") + " [F1..F10]", Iconos.Director()) + menu.opcion("director", _("Director") + " [%s] " %_("F1-F10 or Ctrl Right button"), Iconos.Director()) menu.separador() if self.siPosibleRotarTablero: @@ -830,6 +830,10 @@ def mousePressEvent(self, event): siDentro = (minimo < x < maximo) and (minimo < y < maximo) if event.button() == QtCore.Qt.RightButton: if siDentro: + if not self.dirvisual: + m = int(event.modifiers()) + if (m & QtCore.Qt.ControlModifier) == 0: + return self.lanzaDirector() # if siDentro and hasattr(self.pantalla, "boardRightMouse") and not self.dirvisual: # m = int(event.modifiers()) @@ -1000,7 +1004,7 @@ def dbVisual_open(self): return self.dbVisual def dbVisual_close(self): - if self.dbVisual: + if self.dbVisual is not None: self.dbVisual.close() self.dbVisual = None @@ -1013,6 +1017,30 @@ def dbVisual_lista(self, fenM2): def dbVisual_save(self, fenM2, lista): self.dbVisual_open()[fenM2] = lista + def saveVisual(self): + alm = self.almSaveVisual = Util.Almacen() + alm.siMenuVisual = self.siMenuVisual + alm.siDirector = self.siDirector + alm.siDirectorIcon = self.siDirectorIcon + alm.dirvisual = self.dirvisual + alm.guion = self.guion + alm.lastFenM2 = self.lastFenM2 + alm.dbVisual = self.dbVisual + alm.nomdbVisual = self.nomdbVisual + alm.dbVisual_showAllways = self.dbVisual_showAllways + + def restoreVisual(self): + alm = self.almSaveVisual + self.siMenuVisual = alm.siMenuVisual + self.siDirector = alm.siDirector + self.siDirectorIcon = alm.siDirectorIcon + self.dirvisual = alm.dirvisual + self.guion = alm.guion + self.lastFenM2 = alm.lastFenM2 + self.dbVisual = alm.dbVisual + self.nomdbVisual = alm.nomdbVisual + self.dbVisual_showAllways = alm.dbVisual_showAllways + def setUltPosicion(self, posicion): self.cierraGuion() self.ultPosicion = posicion diff --git a/Code/QT/WBG_InfoMove.py b/Code/QT/WBG_InfoMove.py index 538f287..4289b7b 100644 --- a/Code/QT/WBG_InfoMove.py +++ b/Code/QT/WBG_InfoMove.py @@ -243,7 +243,7 @@ def colocatePartida(self, pos): pgn += '%d.' % (style_number, numJugada) numJugada += 1 - xp = jg.pgnHTML() if self.siFigurines else jg.pgnSP() + xp = jg.pgnHTML(self.siFigurines) if n == pos: xp = '%s' % (style_select, xp) else: diff --git a/Code/QT/WBG_Summary.py b/Code/QT/WBG_Summary.py index f735277..e0aabcf 100644 --- a/Code/QT/WBG_Summary.py +++ b/Code/QT/WBG_Summary.py @@ -502,7 +502,7 @@ def cambiaInfoMove(self): p = Partida.Partida() p.leerPV(pv) p.siTerminada() - self.aperturasStd.asignaApertura(p) + p.asignaApertura() self.infoMove.modoPartida(p, 9999) self.setFocus() self.grid.setFocus() diff --git a/Code/Torneo.py b/Code/Torneo.py index 86b821c..d1d1480 100644 --- a/Code/Torneo.py +++ b/Code/Torneo.py @@ -16,7 +16,7 @@ def __init__(self): self._depth = 0 self._time = 0 - self._book = "*" # "*":por defecto "-":el propio del motor otro:path to libro polyglot + self._book = "-" # "*":por defecto "-":el propio del motor otro:path to libro polyglot self._bookRR = "ap" def ponHuella(self, liEngines): diff --git a/Code/TurnOnLights.py b/Code/TurnOnLights.py index 48e8ed5..0c1d954 100644 --- a/Code/TurnOnLights.py +++ b/Code/TurnOnLights.py @@ -3,6 +3,7 @@ import random import datetime +from Code import ControlPosicion from Code import PGNreader from Code import Util from Code import VarGen @@ -58,6 +59,17 @@ def penaltyHelp(self, think_mode): else: return 10.0 + def factorDistancia(self): + nummoves = 0 + distancia = 0.0 + for line in self.lines: + d, n = line.distancia_moves() + nummoves += n + distancia += d + base_dist = ControlPosicion.distancia("a1", "a4") + + return (distancia/nummoves)/base_dist + def add_line(self, line): self.lines.append(line) @@ -136,6 +148,10 @@ def get_move(self, num_move): def total_moves(self): return len(self.limoves) + def distancia_moves(self): + dt = sum( ControlPosicion.distancia(pv[:2], pv[2:4]) for pv in self.limoves ) + return dt, len(self.limoves) + class TOL_level: def __init__(self, lines_per_block, num_level): diff --git a/Code/Tutor.py b/Code/Tutor.py index ac5fdb0..4fb426a 100644 --- a/Code/Tutor.py +++ b/Code/Tutor.py @@ -1,7 +1,7 @@ import codecs import os -import LCEngine +import LCEngineV1 as LCEngine from Code import Analisis from Code import Partida @@ -97,7 +97,8 @@ def elegir(self, siPuntos, liApPosibles=None): pvBloque = "" if pvBloque: - self.partidaRival = Partida.Partida(self.ultPosicion).leerPV(pvBloque) + self.partidaRival = Partida.Partida(self.ultPosicion) + self.partidaRival.leerPV(pvBloque) self.posRival = 0 self.maxRival = len(self.partidaRival.liJugadas) - 1 if self.maxRival >= 0: @@ -165,7 +166,8 @@ def hazListaRM(self, posUsuario): def cambiadoRM(self, pos): self.posRM = pos rm = self.listaRM[pos][0] - self.partidaTutor = Partida.Partida(self.ultPosicion).leerPV(rm.getPV()) + self.partidaTutor = Partida.Partida(self.ultPosicion) + self.partidaTutor.leerPV(rm.getPV()) self.w.ponPuntuacionTutor(rm.texto()) @@ -319,7 +321,8 @@ def ponTablerosGUI(self, tableroTutor, tableroUsuario, tableroRival, tableroAper self.tableroAperturas = tableroAperturas def cambiarApertura(self, numero): - self.partidaAperturas = Partida.Partida(self.ultPosicion).leerPV(self.liApPosibles[numero].a1h8) + self.partidaAperturas = Partida.Partida(self.ultPosicion) + self.partidaAperturas.leerPV(self.liApPosibles[numero].a1h8) self.tableroAperturas.ponPosicion(self.partidaAperturas.jugada(0).posicion) self.maxApertura = len(self.partidaAperturas) self.mueveApertura(siInicio=True) diff --git a/Code/Util.py b/Code/Util.py index 2c7b282..aa219f3 100644 --- a/Code/Util.py +++ b/Code/Util.py @@ -566,6 +566,28 @@ def validNomFichero(nombre): return nombre +def asciiNomFichero(nombre): + nombre = validNomFichero(nombre) + li = [] + for x in nombre: + if not(31 < ord(x) < 127): + li.append("_") + else: + li.append(x) + nombre = "".join(li) + while "__" in nombre: + nombre = nombre.replace("__", "_") + return nombre + + +def datefile(pathfile): + try: + mtime = os.path.getmtime(pathfile) + return datetime.datetime.fromtimestamp(mtime) + except: + return None + + class Timer: def __init__(self, tiempoPendiente): diff --git a/Code/XGestorMotor.py b/Code/XGestorMotor.py index 91bf1ce..e3cd47b 100644 --- a/Code/XGestorMotor.py +++ b/Code/XGestorMotor.py @@ -1,6 +1,6 @@ import os -import LCEngine +import LCEngineV1 as LCEngine from Code import VarGen from Code import XMotor diff --git a/Code/XMotorRespuesta.py b/Code/XMotorRespuesta.py index 9e0b55c..657c7c4 100644 --- a/Code/XMotorRespuesta.py +++ b/Code/XMotorRespuesta.py @@ -624,6 +624,9 @@ def bestmoves(self): break return li + def getdepth0(self): + return self.liMultiPV[0].depth if self.liMultiPV else 0 + def mejorMovDetectaBlunders(self, fdbg, mindifpuntos, maxmate): rm0 = self.liMultiPV[0] if maxmate: @@ -688,7 +691,8 @@ def x(clave): # Miramos todas las propuestas for num, rm in enumerate(self.liMultiPV): - partida = Partida.Partida(cp).leerPV(rm.pv) + partida = Partida.Partida(cp) + partida.leerPV(rm.pv) jg0 = partida.jugada(0) ps0 = partida.iniPosicion psZ = partida.ultPosicion @@ -802,7 +806,8 @@ def x(clave): if dbg: fdbg.write("Result :\n") for num, rm in enumerate(self.liMultiPV): - partida = Partida.Partida(cp).leerPV(rm.pv) + partida = Partida.Partida(cp) + partida.leerPV(rm.pv) pgn = "" si = False diff --git a/Engines/Linux32/mcbrain/.DS_Store b/Engines/Linux32/mcbrain/.DS_Store deleted file mode 100644 index 01c2e2c..0000000 Binary files a/Engines/Linux32/mcbrain/.DS_Store and /dev/null differ diff --git a/Engines/Linux32/mcbrain/.travis.yml b/Engines/Linux32/mcbrain/.travis.yml index 87a3e7d..0a214d1 100644 --- a/Engines/Linux32/mcbrain/.travis.yml +++ b/Engines/Linux32/mcbrain/.travis.yml @@ -18,11 +18,12 @@ matrix: compiler: clang addons: apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['clang', 'g++-multilib', 'valgrind', 'expect'] + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] + packages: ['clang-5.0', 'llvm-5.0-dev', 'g++-multilib', 'valgrind', 'expect'] env: - - COMPILER=clang++ + - COMPILER=clang++-5.0 - COMP=clang + - LDFLAGS=-fuse-ld=gold - os: osx compiler: gcc @@ -44,30 +45,30 @@ before_script: - cd src script: - # - # checking bench for various build types - # - # obtain reference from git log - - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9][0-9]*\)/\1/g" > git_sig + # Obtain bench reference from git log + - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig - export benchref=$(cat git_sig) - echo "Reference bench:" $benchref - # verify against reference - - make clean && make ARCH=x86-64 build > /dev/null && ../tests/signature.sh $benchref - - make clean && make ARCH=x86-32 build > /dev/null && ../tests/signature.sh $benchref - # - # perft - # - - make clean && make ARCH=x86-64 build > /dev/null && ../tests/perft.sh # - # reproducible search + # Verify bench number against various builds + - export CXXFLAGS=-Werror + - make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-64 build && ../tests/signature.sh $benchref # - - make clean && make ARCH=x86-64 build > /dev/null && ../tests/reprosearch.sh + # Check perft and reproducible search + - ../tests/perft.sh + - ../tests/reprosearch.sh # - # valgrind + # Valgrind # - - if [ -x "$(command -v valgrind )" ]; then make clean && make ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi + - export CXXFLAGS=-O1 + - if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi + - if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi # - # sanitizer + # Sanitizer # - # use g++-6 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc - - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make ARCH=x86-64 sanitize=yes build > /dev/null && ../tests/instrumented.sh --sanitizer; fi + # Use g++-6 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=thread optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi diff --git a/Engines/Linux32/mcbrain/AUTHORS b/Engines/Linux32/mcbrain/AUTHORS index 3095efc..9b91b93 100644 --- a/Engines/Linux32/mcbrain/AUTHORS +++ b/Engines/Linux32/mcbrain/AUTHORS @@ -1,6 +1,5 @@ # Generated with 'git shortlog -sn | cut -c8-', which sorts by commits, manually ordered the first four authors, merged duplicates -Michael Byrne (McBrain) Tord Romstad Marco Costalba (mcostalba) Joona Kiiski (zamar) @@ -95,4 +94,5 @@ ppigazzini renouve sf-x thaspel +unknown diff --git a/Engines/Linux32/mcbrain/McBrain-9_x32_linux b/Engines/Linux32/mcbrain/McBrain-9_x32_linux new file mode 100644 index 0000000..241f2bb Binary files /dev/null and b/Engines/Linux32/mcbrain/McBrain-9_x32_linux differ diff --git a/Engines/Linux32/mcbrain/Readme.md b/Engines/Linux32/mcbrain/Readme.md index e3b36cc..46f2609 100644 --- a/Engines/Linux32/mcbrain/Readme.md +++ b/Engines/Linux32/mcbrain/Readme.md @@ -98,9 +98,9 @@ for a quick reference. ### Resource For Understanding the Code Base -* [Chessprogramingwiki](https://chessprogramming.wikispaces.com) has good overall chess engines explanations +* [Chess Programming Wiki](https://chessprogramming.wikispaces.com) has good overall chess engines explanations (techniques used here are well explained like hash maps etc), it was -also recommended by the [support at stockfish.](http://support.stockfishchess.org/discussions/questions/1132-how-to-understand-stockfish-sources) +also recommended by the [support team at stockfish.](http://support.stockfishchess.org/discussions/questions/1132-how-to-understand-stockfish-sources) * [Here](https://chessprogramming.wikispaces.com/Stockfish) you can find a set of features and techniques used by stockfish and each of them is explained at the wiki, however, it's a generic way rather than focusing on stockfish's own implementation, but it will still help you. diff --git a/Engines/Linux32/mcbrain/Top CPU Contributors.txt b/Engines/Linux32/mcbrain/Top CPU Contributors.txt index 0bb2a92..4c2aa47 100644 --- a/Engines/Linux32/mcbrain/Top CPU Contributors.txt +++ b/Engines/Linux32/mcbrain/Top CPU Contributors.txt @@ -1,93 +1,132 @@ -Contributors with >10,000 CPU hours as of November 3, 2016 +Contributors with >10,000 CPU hours as of January 23, 2018 Thank you! -Username CPU Hours Games played -cw 220301 16924200 -glinscott 186639 13936027 -fastgm 184045 14608140 -mibere 165859 13563572 -crunchy 160974 14091929 -spams 143806 10956698 -bking_US 136938 10558137 -dsmith 103332 7622414 -BrunoBanani 100008 7448565 -ctoks 99216 7989224 -JojoM 96528 8138437 -vdbergh 88372 6322455 -drabel 75214 6034715 -velislav 71485 5483953 -sqrt2 70825 5471595 -BRAVONE 66265 5356681 -malala 57618 4480635 -psk 54292 4337164 -leszek 52415 4254611 -marrco 51573 4132787 -Freja 48348 3773248 -Thanar 47723 4062940 -Fisherman 46361 3865994 -renouve 46003 3544864 -CSU_Dynasty 45136 4096148 -rap 44619 3219490 -dv8silencer 44175 3961325 -tinker 43975 3261777 -tvijlbrief 42291 2965762 -sunu 41289 3172937 -mhunt 38278 2697512 -Antihistamine 37735 2795761 -finfish 36001 2734928 -brabos 32630 2566008 -jromang 32044 2166097 -robnjr 31781 2726352 -CoffeeOne 29940 2597953 -sterni1971 28924 2737221 -EthanOConnor 28429 2143255 -Pyafue 27266 1986098 -jkiiski 27009 1925255 -biffhero 26557 2033420 -nssy 25231 2037166 -mgrabiak 24000 1974653 -slakovv 23548 2031279 -Sharaf_DG 22175 1790697 -homyur 21415 1705644 -team-oh 20347 1653708 -Zirie 20204 1493227 -nabildanial 19538 1586321 -nesoneg 19306 1493435 -cuistot 19105 1387031 -Patrick_G 19027 1406466 -mhoram 18304 1396701 -rkl 17566 1409460 -ville 17541 1540130 -oryx 17480 1578240 -rstoesser 17264 1335177 -xor12 16786 1492708 -jundery 16786 1115855 -bigpen0r 16700 1287118 -iisiraider 16366 1089410 -davar 16266 1328093 -vdv 16072 1629971 -VoyagerOne 16049 1485459 -Bobo1239 15837 1550883 -DragonLord 15791 1251348 -purplefishies 15602 1106850 -Isidor 14598 1317485 -speedycpu 14215 874201 -OssumOpossum 14078 1029265 -enedene 13378 935618 -bpfliegel 12944 886523 -AdrianSA 12921 924980 -JanErik 12782 1106788 -dju 12600 901552 -jpulman 12015 854815 -ttruscott 11929 976348 -fatmurphy 11726 901134 -ElbertoOne 11641 1082697 -j3corre 11638 973654 -chris 11450 1228430 -pb00067 11248 1021031 -modolief 11185 926456 -Dark_wizzie 10933 1017910 -SC 10637 925516 -Thomas A. 10485 736094 -mschmidt 10354 818594 -infinity 10020 746397 +Username CPU Hours Games played +mibere 518300 41835669 +crunchy 375564 29121434 +cw 371664 28748719 +fastgm 299773 20765374 +JojoM 220590 15299913 +glinscott 204517 13932027 +bking_US 187568 12233168 +ctoks 169342 13475495 +spams 149531 10940322 +Thanar 137015 11714855 +velislav 127305 10047749 +vdbergh 121741 9056874 +malala 117291 8126488 +vdv 117218 8289983 +leszek 114825 8331897 +dsmith 114010 7622414 +CSU_Dynasty 113516 9582758 +sqrt2 112407 8782694 +marrco 111143 8222921 +drabel 108168 9061580 +BrunoBanani 104938 7448565 +Data 94621 8433010 +CoffeeOne 90394 3964243 +BRAVONE 80811 5341681 +psk 77195 6156031 +brabos 70284 5685893 +Fisherman 66650 5572406 +nssy 64587 5369140 +Pking_cda 64499 5704075 +sterni1971 63488 5070004 +mgrabiak 62385 5420812 +tvijlbrief 58957 4154234 +jromang 58854 4704502 +dv8silencer 57421 3961325 +sunu 56620 4609155 +tinker 56039 4204914 +biffhero 55743 4810039 +teddybaer 52982 4740444 +bcross 50548 5071599 +renouve 50318 3544864 +Freja 50296 3805120 +robnjr 47504 4131742 +eva42 46542 4044694 +davar 46538 4030604 +finfish 46244 3481661 +rap 46201 3219490 +ttruscott 45037 3645430 +solarlight 44155 4074841 +TueRens 41372 3891510 +ElbertoOne 41321 3920894 +Antihistamine 39218 2792761 +mhunt 38991 2697512 +bigpen0r 37820 3149955 +homyur 35569 3009637 +VoyagerOne 35137 3302650 +mhoram 34770 2684128 +racerschmacer 33022 3231055 +speedycpu 32043 2531964 +EthanOConnor 31638 2143255 +oryx 29574 2767730 +Pyafue 28885 1986098 +jkiiski 28014 1923255 +Garf 27579 2770144 +slakovv 27017 2031279 +Bobo1239 27000 2488707 +pb00067 26817 2306694 +robal 26337 2316795 +hyperbolic.tom 26248 2200777 +rkl 24898 2236013 +SC 23988 2126825 +nabildanial 23524 1586321 +achambord 23495 1942546 +Sharaf_DG 22975 1790697 +chriswk 22876 1947731 +anst 22568 2013953 +Patrick_G 22435 1682293 +cuistot 22201 1383031 +gri 21901 1820968 +Prcuvu 21182 1890546 +Zirie 21171 1493227 +JanErik 20596 1791991 +Isidor 20560 1730290 +xor12 20535 1819280 +team-oh 20364 1653708 +nesoneg 20264 1493435 +rstoesser 19802 1335177 +grandphish2 19402 1834196 +sg4032 18427 1671742 +dew 18263 1423326 +ianh2105 18133 1668562 +MazeOfGalious 18022 1644593 +ville 17900 1539130 +j3corre 17607 975954 +eudhan 17502 1424648 +iisiraider 17175 1118788 +jundery 17172 1115855 +SFTUser 16635 1363975 +purplefishies 16621 1106850 +DragonLord 16599 1252348 +chris 15274 1575333 +xoto 14900 1486261 +dju 14861 901552 +dex 14647 1228763 +nordlandia 14551 1369718 +ronaldjerum 14361 1210607 +OssumOpossum 14149 1029265 +IgorLeMasson 13844 1228391 +enedene 13762 935618 +ako027ako 13442 1250249 +AdrianSA 13324 924980 +bpfliegel 13318 886523 +ncfish1 13056 932344 +wei 12863 1369596 +jpulman 12776 854815 +horst.prack 12436 1151505 +joster 12424 986622 +cisco2015 12265 1205019 +fatmurphy 12015 901134 +modolief 11228 926456 +Dark_wizzie 11214 1017910 +mschmidt 10973 818594 +eastorwest 10970 1117836 +infinity 10762 746397 +SapphireBrand 10692 1024604 +Thomas A. 10553 736094 +pgontarz 10294 878746 +Andrew Grant 10195 922933 +stocky 10083 718114 diff --git a/Engines/Linux32/mcbrain/appveyor.yml b/Engines/Linux32/mcbrain/appveyor.yml index a46a0f3..c711dd6 100644 --- a/Engines/Linux32/mcbrain/appveyor.yml +++ b/Engines/Linux32/mcbrain/appveyor.yml @@ -1,5 +1,5 @@ version: 1.0.{build} -clone_depth: 5 +clone_depth: 50 branches: only: @@ -13,10 +13,11 @@ os: Visual Studio 2015 platform: - x86 - x64 - - Any CPU # build Configuration, i.e. Debug, Release, etc. -configuration: Debug +configuration: + - Debug + - Release matrix: # The build fail immediately once one of the job fails @@ -28,18 +29,43 @@ init: - msbuild /version before_build: - - cd src - - echo project (Stockfish) >> CMakeLists.txt - - echo add_executable(stockfish benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp >> CMakeLists.txt - - echo main.cpp material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp >> CMakeLists.txt - - echo search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp syzygy/tbprobe.cpp) >> CMakeLists.txt - - echo set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src) >> CMakeLists.txt -# - echo target_compile_options(stockfish PUBLIC "/Ox") >> CMakeLists.txt + - ps: | + # Get sources + $src = get-childitem -Path *.cpp -Recurse | select -ExpandProperty FullName + $src = $src -join ' ' + $src = $src.Replace("\", "/") + + # Build CMakeLists.txt + $t = 'cmake_minimum_required(VERSION 3.8)', + 'project(Stockfish)', + 'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)', + 'set(source_files', $src, ')', + 'add_executable(stockfish ${source_files})' + + # Write CMakeLists.txt withouth BOM + $MyPath = (Get-Item -Path "." -Verbose).FullName + '\CMakeLists.txt' + $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False + [System.IO.File]::WriteAllLines($MyPath, $t, $Utf8NoBomEncoding) + + # Obtain bench reference from git log + $b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1 + $bench = $b -match '\D+(\d+)' | % { $matches[1] } + Write-Host "Reference bench:" $bench + $g = "Visual Studio 14 2015" + If (${env:PLATFORM} -eq 'x64') { $g = $g + ' Win64' } + cmake -G "${g}" . + Write-Host "Generated files for: " $g build_script: - - cmake -G "Visual Studio 14 2015 Win64" . - - cmake --build . + - cmake --build . --config %CONFIGURATION% -- /verbosity:minimal before_test: - - cd Debug - - stockfish.exe bench > null + - cd src/%CONFIGURATION% + - ps: | + # Verify bench number + ./stockfish bench 2> out.txt 1> null + $s = (gc "./out.txt" | out-string) + $r = ($s -match 'Nodes searched \D+(\d+)' | % { $matches[1] }) + Write-Host "Engine bench:" $r + Write-Host "Reference bench:" $bench + If ($r -ne $bench) { exit 1 } diff --git a/Engines/Linux32/mcbrain/src/Makefile b/Engines/Linux32/mcbrain/src/Makefile index afd2f7c..6e66fbb 100644 --- a/Engines/Linux32/mcbrain/src/Makefile +++ b/Engines/Linux32/mcbrain/src/Makefile @@ -1,22 +1,24 @@ -# Stockfish, a UCI chess playing engine derived from Glaurung 2.1 -# Copyright (C) 2004-2008 Tord Romstad (Glaurung author) -# Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad -# Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad -# -# Stockfish is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Stockfish is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - +### ========================================================================== +### Copyright Notice and License Information +### ========================================================================== +# McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 +# Copyright (C) 2004-2008 Tord Romstad (Glaurung Author) +# Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) +# Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) +# Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + +# McBrain is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# McBrain is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . ### ========================================================================== ### Section 1. General Configuration ### ========================================================================== @@ -28,8 +30,8 @@ ifeq ($(KERNEL),Linux) endif ### Executable name -EXE = SF-McBrain-27 -# bench = 5127083 +EXE = McBrain-9 +### bench nodes -> Nodes searched : 6871658 ### Installation dir definitions PREFIX = /usr/local @@ -42,7 +44,7 @@ PGOBENCH2 = ./$(EXE) bench 16 2 10 ### Object files OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \ material.o misc.o movegen.o movepick.o pawns.o position.o psqt.o \ - search.o thread.o timeman.o tt.o uci.o ucioption.o tbprobe.o tzbook.o + search.o thread.o timeman.o tt.o uci.o ucioption.o polybook.o tbprobe.o ### ========================================================================== ### Section 2. High-level Configuration @@ -143,7 +145,7 @@ endif ### 3.1 Selecting compiler (default = gcc) -CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -fno-rtti -std=c++11 $(EXTRACXXFLAGS) +CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++11 $(EXTRACXXFLAGS) DEPENDFLAGS += -std=c++11 LDFLAGS += $(EXTRALDFLAGS) @@ -154,7 +156,7 @@ endif ifeq ($(COMP),gcc) comp=gcc CXX=g++ - CXXFLAGS += -pedantic -Wextra -Wshadow -static-libstdc++ + CXXFLAGS += -pedantic -Wextra -Wshadow ifeq ($(ARCH),armv7) ifeq ($(OS),Android) @@ -207,7 +209,9 @@ ifeq ($(COMP),clang) CXX=clang++ CXXFLAGS += -pedantic -Wextra -Wshadow ifneq ($(KERNEL),Darwin) +ifneq ($(KERNEL),OpenBSD) LDFLAGS += -latomic +endif endif ifeq ($(ARCH),armv7) @@ -276,7 +280,7 @@ endif ### 3.3 Optimization ifeq ($(optimize),yes) - CXXFLAGS += -O3 + CXXFLAGS += -O3 -mtune=native ifeq ($(comp),gcc) @@ -302,8 +306,6 @@ ifeq ($(optimize),yes) ifeq ($(comp),clang) ifeq ($(KERNEL),Darwin) - CXXFLAGS += -flto - LDFLAGS += $(CXXFLAGS) ifeq ($(arch),i386) CXXFLAGS += -mdynamic-no-pic endif @@ -349,24 +351,20 @@ endif ### 3.8 Link Time Optimization, it works since gcc 4.5 but not on mingw under Windows. ### This is a mix of compile and link time options because the lto link phase ### needs access to the optimization flags. -ifeq ($(comp),gcc) - ifeq ($(optimize),yes) - ifeq ($(debug),no) - CXXFLAGS += -flto -fno-rtti +ifeq ($(optimize),yes) +ifeq ($(debug), no) + ifeq ($(comp),$(filter $(comp),gcc clang)) + CXXFLAGS += -flto LDFLAGS += $(CXXFLAGS) endif - endif -endif -ifeq ($(comp),mingw) + ifeq ($(comp),mingw) ifeq ($(KERNEL),Linux) - ifeq ($(optimize),yes) - ifeq ($(debug),no) CXXFLAGS += -flto LDFLAGS += $(CXXFLAGS) endif endif - endif +endif endif ### 3.9 Android 5 can only run position independent executables. Note that this @@ -434,7 +432,6 @@ help: build: config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all strip $(EXE) - cp $(EXE) /Users/michaelbyrne/cluster.mfb/$(EXE) profile-build: config-sanity objclean profileclean @echo "" @@ -454,7 +451,12 @@ profile-build: config-sanity objclean profileclean @echo "Step 5/5. Deleting profile data ..." $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean strip $(EXE) - cp $(EXE) /Users/michaelbyrne/cluster.mfb/$(EXE) + +cprom: + make -j profile-build ARCH=x86-64-modern COMP=clang + +cprob: + make -j profile-build ARCH=x86-64-bmi2 COMP=clang strip: strip $(EXE) diff --git a/Engines/Linux32/mcbrain/src/benchmark.cpp b/Engines/Linux32/mcbrain/src/benchmark.cpp index 75c7d1a..807f9de 100644 --- a/Engines/Linux32/mcbrain/src/benchmark.cpp +++ b/Engines/Linux32/mcbrain/src/benchmark.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include #include diff --git a/Engines/Linux32/mcbrain/src/bitbase.cpp b/Engines/Linux32/mcbrain/src/bitbase.cpp index d206210..a651e27 100644 --- a/Engines/Linux32/mcbrain/src/bitbase.cpp +++ b/Engines/Linux32/mcbrain/src/bitbase.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include #include @@ -111,7 +112,7 @@ namespace { ksq[WHITE] = Square((idx >> 0) & 0x3F); ksq[BLACK] = Square((idx >> 6) & 0x3F); us = Color ((idx >> 12) & 0x01); - psq = make_square(File((idx >> 13) & 0x3), RANK_7 - Rank((idx >> 15) & 0x7)); + psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7))); // Check if two pieces are on the same square or if a king can be captured if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 diff --git a/Engines/Linux32/mcbrain/src/bitboard.cpp b/Engines/Linux32/mcbrain/src/bitboard.cpp index e3c9140..9dd637b 100644 --- a/Engines/Linux32/mcbrain/src/bitboard.cpp +++ b/Engines/Linux32/mcbrain/src/bitboard.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include @@ -54,7 +55,7 @@ namespace { Bitboard RookTable[0x19000]; // To store rook attacks Bitboard BishopTable[0x1480]; // To store bishop attacks - void init_magics(Bitboard table[], Magic magics[], Square deltas[]); + void init_magics(Bitboard table[], Magic magics[], Direction directions[]); // bsf_index() returns the index into BSFTable[] to look up the bitscan. Uses // Matt Taylor's folding for 32 bit case, extended to 64 bit by Kim Walisch. @@ -188,7 +189,7 @@ void Bitboards::init() { for (Square s = SQ_A1; s <= SQ_H8; ++s) for (int i = 0; steps[pt][i]; ++i) { - Square to = s + Square(c == WHITE ? steps[pt][i] : -steps[pt][i]); + Square to = s + Direction(c == WHITE ? steps[pt][i] : -steps[pt][i]); if (is_ok(to) && distance(s, to) < 3) { @@ -199,11 +200,11 @@ void Bitboards::init() { } } - Square RookDeltas[] = { NORTH, EAST, SOUTH, WEST }; - Square BishopDeltas[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; + Direction RookDirections[] = { NORTH, EAST, SOUTH, WEST }; + Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; - init_magics(RookTable, RookMagics, RookDeltas); - init_magics(BishopTable, BishopMagics, BishopDeltas); + init_magics(RookTable, RookMagics, RookDirections); + init_magics(BishopTable, BishopMagics, BishopDirections); for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) { @@ -225,14 +226,14 @@ void Bitboards::init() { namespace { - Bitboard sliding_attack(Square deltas[], Square sq, Bitboard occupied) { + Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied) { Bitboard attack = 0; for (int i = 0; i < 4; ++i) - for (Square s = sq + deltas[i]; - is_ok(s) && distance(s, s - deltas[i]) == 1; - s += deltas[i]) + for (Square s = sq + directions[i]; + is_ok(s) && distance(s, s - directions[i]) == 1; + s += directions[i]) { attack |= s; @@ -249,7 +250,7 @@ namespace { // chessprogramming.wikispaces.com/Magic+Bitboards. In particular, here we // use the so called "fancy" approach. - void init_magics(Bitboard table[], Magic magics[], Square deltas[]) { + void init_magics(Bitboard table[], Magic magics[], Direction directions[]) { // Optimal PRNG seeds to pick the correct magics in the shortest time int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, @@ -269,7 +270,7 @@ namespace { // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. Magic& m = magics[s]; - m.mask = sliding_attack(deltas, s, 0) & ~edges; + m.mask = sliding_attack(directions, s, 0) & ~edges; m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); // Set the offset for the attacks table of the square. We have individual @@ -281,7 +282,7 @@ namespace { b = size = 0; do { occupancy[size] = b; - reference[size] = sliding_attack(deltas, s, b); + reference[size] = sliding_attack(directions, s, b); if (HasPext) m.attacks[pext(b, m.mask)] = reference[size]; diff --git a/Engines/Linux32/mcbrain/src/bitboard.h b/Engines/Linux32/mcbrain/src/bitboard.h index c9f199e..c964d1e 100644 --- a/Engines/Linux32/mcbrain/src/bitboard.h +++ b/Engines/Linux32/mcbrain/src/bitboard.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef BITBOARD_H_INCLUDED #define BITBOARD_H_INCLUDED @@ -126,11 +127,10 @@ inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= SquareBB[s]; } -inline bool more_than_one(Bitboard b) { +constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } - /// rank_bb() and file_bb() return a bitboard representing all the squares on /// the given file or rank. @@ -153,8 +153,8 @@ inline Bitboard file_bb(Square s) { /// shift() moves a bitboard one step along direction D. Mainly for pawns -template -inline Bitboard shift(Bitboard b) { +template +constexpr Bitboard shift(Bitboard b) { return D == NORTH ? b << 8 : D == SOUTH ? b >> 8 : D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == NORTH_WEST ? (b & ~FileABB) << 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9 diff --git a/Engines/Linux32/mcbrain/src/endgame.cpp b/Engines/Linux32/mcbrain/src/endgame.cpp index ca17f6a..3d568f5 100644 --- a/Engines/Linux32/mcbrain/src/endgame.cpp +++ b/Engines/Linux32/mcbrain/src/endgame.cpp @@ -1,23 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include #include @@ -524,7 +524,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { Square bsq = pos.square(weakSide); Square psq = pos.square(strongSide); Rank rk = relative_rank(strongSide, psq); - Square push = pawn_push(strongSide); + Direction push = pawn_push(strongSide); // If the pawn is on the 5th rank and the pawn (currently) is on // the same color square as the bishop then there is a chance of diff --git a/Engines/Linux32/mcbrain/src/endgame.h b/Engines/Linux32/mcbrain/src/endgame.h index 3d61207..3bace6f 100644 --- a/Engines/Linux32/mcbrain/src/endgame.h +++ b/Engines/Linux32/mcbrain/src/endgame.h @@ -1,22 +1,24 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef ENDGAME_H_INCLUDED #define ENDGAME_H_INCLUDED diff --git a/Engines/Linux32/mcbrain/src/evaluate.cpp b/Engines/Linux32/mcbrain/src/evaluate.cpp index 7ee3652..e420d3d 100644 --- a/Engines/Linux32/mcbrain/src/evaluate.cpp +++ b/Engines/Linux32/mcbrain/src/evaluate.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include #include @@ -31,12 +32,21 @@ namespace { + const Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB); + const Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; + const Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; + const Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; + + const Bitboard KingFlank[FILE_NB] = { + QueenSide, QueenSide, QueenSide, CenterFiles, CenterFiles, KingSide, KingSide, KingSide + }; + namespace Trace { enum Tracing {NO_TRACE, TRACE}; enum Term { // The first 8 entries are for PieceType - MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL, TERM_NB + MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, INITIATIVE, TOTAL, TERM_NB }; double scores[TERM_NB][COLOR_NB][PHASE_NB]; @@ -54,7 +64,7 @@ namespace { std::ostream& operator<<(std::ostream& os, Term t) { - if (t == MATERIAL || t == IMBALANCE || t == Term(PAWN) || t == TOTAL) + if (t == MATERIAL || t == IMBALANCE || t == Term(PAWN) || t == INITIATIVE || t == TOTAL) os << " --- --- | --- --- | "; else os << std::setw(5) << scores[t][WHITE][MG] << " " @@ -88,6 +98,7 @@ namespace { template void initialize(); template Score evaluate_king(); template Score evaluate_threats(); + int king_distance(Color c, Square s); template Score evaluate_passed_pawns(); template Score evaluate_space(); template Score evaluate_pieces(); @@ -102,7 +113,8 @@ namespace { Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; // attackedBy[color][piece type] is a bitboard representing all squares - // attacked by a given color and piece type (can be also ALL_PIECES). + // attacked by a given color and piece type. Special "piece types" which are + // also calculated are QUEEN_DIAGONAL and ALL_PIECES. Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; // attackedBy2[color] are the squares attacked by 2 pieces of a given color, @@ -162,8 +174,8 @@ namespace { // supported by a pawn. If the minor piece occupies an outpost square // then score is doubled. const Score Outpost[][2] = { - { S(22, 6), S(33, 9) }, // Knight - { S( 9, 2), S(14, 4) } // Bishop + { S(22, 6), S(36,12) }, // Knight + { S( 9, 2), S(15, 5) } // Bishop }; // RookOnFile[semiopen/open] contains bonuses for each rook when there is no @@ -174,23 +186,23 @@ namespace { // which piece type attacks which one. Attacks on lesser pieces which are // pawn-defended are not considered. const Score ThreatByMinor[PIECE_TYPE_NB] = { - S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72, 107), S(48, 118) - }; + S(0, 0), S(0, 31), S(39, 42), S(57, 44), S(68, 112), S(47, 120) + }; //Fauzi Akram const Score ThreatByRook[PIECE_TYPE_NB] = { - S(0, 0), S(0, 25), S(40, 62), S(40, 59), S(0, 34), S(35, 48) - }; + S(0, 0), S(0, 24), S(38, 71), S(38, 61), S(0, 38), S(36, 38) + }; //Fauzi Akram // ThreatByKing[on one/on many] contains bonuses for king attacks on // pawns or pieces which are not pawn-defended. - const Score ThreatByKing[] = { S(3, 62), S(9, 138) }; + const Score ThreatByKing[] = { S(3, 65), S(9, 145) }; //Fauzi Akram // Passed[mg/eg][Rank] contains midgame and endgame bonuses for passed pawns. // We don't use a Score because we process the two components independently. const Value Passed[][RANK_NB] = { - { V(5), V( 5), V(31), V(73), V(166), V(252) }, - { V(7), V(14), V(38), V(73), V(166), V(252) } - }; + { V(0), V(5), V( 5), V(32), V(70), V(172), V(217) }, + { V(0), V(7), V(13), V(42), V(70), V(170), V(269) } + }; //Fauzi Akram // PassedFile[File] contains a bonus according to the file of a passed pawn const Score PassedFile[FILE_NB] = { @@ -198,25 +210,29 @@ namespace { S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10) }; + // Rank factor applied on some bonus for passed pawn on rank 4 or beyond + const int RankFactor[RANK_NB] = {0, 0, 0, 2, 7, 12, 19}; //Fauzi Akram + // KingProtector[PieceType-2] contains a bonus according to distance from king const Score KingProtector[] = { S(-3, -5), S(-4, -3), S(-3, 0), S(-1, 1) }; // Assorted bonuses and penalties used by evaluation - const Score MinorBehindPawn = S( 16, 0); - const Score BishopPawns = S( 8, 12); - const Score RookOnPawn = S( 8, 24); - const Score TrappedRook = S( 92, 0); - const Score WeakQueen = S( 50, 10); - const Score OtherCheck = S( 10, 10); - const Score CloseEnemies = S( 7, 0); - const Score PawnlessFlank = S( 20, 80); - const Score ThreatByHangingPawn = S( 71, 61); - const Score ThreatBySafePawn = S(182,175); - const Score ThreatByRank = S( 16, 3); - const Score Hanging = S( 48, 27); - const Score ThreatByPawnPush = S( 38, 22); - const Score HinderPassedPawn = S( 7, 0); - const Score TrappedBishopA1H1 = S( 50, 50); + const Score MinorBehindPawn = S( 16, 0); + const Score BishopPawns = S( 8, 12); + const Score LongRangedBishop = S( 22, 0); + const Score RookOnPawn = S( 8, 24); + const Score TrappedRook = S( 92, 0); + const Score WeakQueen = S( 50, 10); + const Score CloseEnemies = S( 7, 0); + const Score PawnlessFlank = S( 20, 80); + const Score ThreatBySafePawn = S(175,168); //Fauzi Akram + const Score ThreatByRank = S( 16, 3); + const Score Hanging = S( 52, 30); //Fauzi Akram + const Score WeakUnopposedPawn = S( 5, 25); + const Score ThreatByPawnPush = S( 47, 26); //Fauzi Akram + const Score ThreatByAttackOnQueen = S( 42, 21); //Fauzi Akram + const Score HinderPassedPawn = S( 8, 1); //Fauzi Akram + const Score TrappedBishopA1H1 = S( 50, 50); #undef S #undef V @@ -225,10 +241,10 @@ namespace { const int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 78, 56, 45, 11 }; // Penalties for enemy's safe checks - const int QueenCheck = 780; - const int RookCheck = 880; - const int BishopCheck = 435; - const int KnightCheck = 790; + const int QueenSafeCheck = 780; + const int RookSafeCheck = 880; + const int BishopSafeCheck = 435; + const int KnightSafeCheck = 790; // Threshold for lazy and space evaluation const Value LazyThreshold = Value(1500); @@ -241,9 +257,9 @@ namespace { template template void Evaluation::initialize() { - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Square Down = (Us == WHITE ? SOUTH : NORTH); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Down = (Us == WHITE ? SOUTH : NORTH); const Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB: Rank7BB | Rank6BB); // Find our pawns on the first two ranks, and those which are blocked @@ -284,6 +300,7 @@ namespace { const Color Them = (Us == WHITE ? BLACK : WHITE); const Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB : Rank5BB | Rank4BB | Rank3BB); + const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); const Square* pl = pos.squares(Us); Bitboard b, bb; @@ -292,11 +309,14 @@ namespace { attackedBy[Us][Pt] = 0; + if (Pt == QUEEN) + attackedBy[Us][QUEEN_DIAGONAL] = 0; + while ((s = *pl++) != SQ_NONE) { // Find attacked squares, including x-ray attacks for bishops and rooks - b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(Us, QUEEN)) - : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(Us, ROOK, QUEEN)) + b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) + : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) : pos.attacks_from(s); if (pos.pinned_pieces(Us) & s) @@ -305,6 +325,9 @@ namespace { attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b; attackedBy[Us][ALL_PIECES] |= attackedBy[Us][Pt] |= b; + if (Pt == QUEEN) + attackedBy[Us][QUEEN_DIAGONAL] |= b & PseudoAttacks[BISHOP][s]; + if (b & kingRing[Them]) { kingAttackersCount[Us]++; @@ -324,12 +347,12 @@ namespace { // Bonus for outpost squares bb = OutpostRanks & ~pe->pawn_attacks_span(Them); if (bb & s) - score += Outpost[Pt == BISHOP][!!(attackedBy[Us][PAWN] & s)] * 2; + score += Outpost[Pt == BISHOP][bool(attackedBy[Us][PAWN] & s)] * 2; else { bb &= b & ~pos.pieces(Us); if (bb) - score += Outpost[Pt == BISHOP][!!(attackedBy[Us][PAWN] & bb)]; + score += Outpost[Pt == BISHOP][bool(attackedBy[Us][PAWN] & bb)]; } // Bonus when behind a pawn @@ -337,10 +360,16 @@ namespace { && (pos.pieces(PAWN) & (s + pawn_push(Us)))) score += MinorBehindPawn; - // Penalty for pawns on the same color square as the bishop if (Pt == BISHOP) + { + // Penalty for pawns on the same color square as the bishop score -= BishopPawns * pe->pawns_on_same_color_squares(Us, s); + // Bonus for bishop on a long diagonal which can "see" both center squares + if (more_than_one(Center & (attacks_bb(s, pos.pieces(PAWN)) | s))) + score += LongRangedBishop; + } + // An important Chess960 pattern: A cornered bishop blocked by a friendly // pawn diagonally in front of it is a very serious problem, especially // when that pawn is also blocked. @@ -348,7 +377,7 @@ namespace { && pos.is_chess960() && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1))) { - Square d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); + Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); if (pos.piece_on(s + d) == make_piece(Us, PAWN)) score -= !pos.empty(s + d + pawn_push(Us)) ? TrappedBishopA1H1 * 4 : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? TrappedBishopA1H1 * 2 @@ -364,7 +393,7 @@ namespace { // Bonus when on an open or semi-open file if (pe->semiopen_file(Us, file_of(s))) - score += RookOnFile[!!pe->semiopen_file(Them, file_of(s))]; + score += RookOnFile[bool(pe->semiopen_file(Them, file_of(s)))]; // Penalty when trapped by the king, even more if the king cannot castle else if (mob <= 3) @@ -375,6 +404,9 @@ namespace { && !pe->semiopen_side(Us, file_of(ksq), file_of(s) < file_of(ksq))) score -= (TrappedRook - make_score(mob * 22, 0)) * (1 + !pos.can_castle(Us)); } + // Bonus when rook can see the enemy back rank and has enough mobility. + else if (relative_rank(Us, s) <= RANK_5 && (pos.attacks_from(s) & TRank8BB)) + score += RookOnFile[0]; } if (Pt == QUEEN) @@ -395,119 +427,98 @@ namespace { // evaluate_king() assigns bonuses and penalties to a king of a given color - const Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; - const Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; - const Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; - - const Bitboard KingFlank[FILE_NB] = { - QueenSide, QueenSide, QueenSide, CenterFiles, CenterFiles, KingSide, KingSide, KingSide - }; - template template Score Evaluation::evaluate_king() { - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB - : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB + : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB); const Square ksq = pos.square(Us); - Bitboard kingOnlyDefended, undefended, b, b1, b2, safe, other; - int kingDanger; + Bitboard weak, b, b1, b2, safe, unsafeChecks; // King shelter and enemy pawns storm Score score = pe->king_safety(pos, ksq); + // King tropism: firstly, find squares that opponent attacks in our king flank + File kf = file_of(ksq); + b = attackedBy[Them][ALL_PIECES] & KingFlank[kf] & Camp; + + assert(((Us == WHITE ? b << 4 : b >> 4) & b) == 0); + assert(popcount(Us == WHITE ? b << 4 : b >> 4) == popcount(b)); + + // Secondly, add the squares which are attacked twice in that flank and + // which are not defended by our pawns. + b = (Us == WHITE ? b << 4 : b >> 4) + | (b & attackedBy2[Them] & ~attackedBy[Us][PAWN]); + + int tropism = popcount(b); + // Main king safety evaluation if (kingAttackersCount[Them] > (1 - pos.count(Them))) { - // Find the attacked squares which are defended only by our king... - kingOnlyDefended = attackedBy[Them][ALL_PIECES] - & attackedBy[Us][KING] - & ~attackedBy2[Us]; - - // ... and those which are not defended at all in the larger king ring - undefended = attackedBy[Them][ALL_PIECES] - & ~attackedBy[Us][ALL_PIECES] - & kingRing[Us] - & ~pos.pieces(Them); - - // Initialize the 'kingDanger' variable, which will be transformed - // later into a king danger score. The initial value is based on the - // number and types of the enemy's attacking pieces, the number of - // attacked and weak squares around our king, the absence of queen and - // the quality of the pawn shelter (current 'score' value). - kingDanger = kingAttackersCount[Them] * kingAttackersWeight[Them] - + 102 * kingAdjacentZoneAttacksCount[Them] - + 191 * popcount(kingOnlyDefended | undefended) - + 143 * !!pos.pinned_pieces(Us) - - 848 * !pos.count(Them) - - 9 * mg_value(score) / 8 - + 40; + // Attacked squares defended at most once by our queen or king + weak = attackedBy[Them][ALL_PIECES] + & ~attackedBy2[Us] + & (attackedBy[Us][KING] | attackedBy[Us][QUEEN] | ~attackedBy[Us][ALL_PIECES]); + + int kingDanger = unsafeChecks = 0; // Analyse the safe enemy's checks which are possible on next move safe = ~pos.pieces(Them); - safe &= ~attackedBy[Us][ALL_PIECES] | (kingOnlyDefended & attackedBy2[Them]); + safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]); - b1 = pos.attacks_from< ROOK>(ksq); - b2 = pos.attacks_from(ksq); + b1 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); + b2 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); // Enemy queen safe checks - if ((b1 | b2) & attackedBy[Them][QUEEN] & safe) - kingDanger += QueenCheck; - - // For minors and rooks, also consider the square safe if attacked twice, - // and only defended by our queen. - safe |= attackedBy2[Them] - & ~(attackedBy2[Us] | pos.pieces(Them)) - & attackedBy[Us][QUEEN]; - - // Some other potential checks are also analysed, even from squares - // currently occupied by the opponent own pieces, as long as the square - // is not attacked by our pawns, and is not occupied by a blocked pawn. - other = ~( attackedBy[Us][PAWN] - | (pos.pieces(Them, PAWN) & shift(pos.pieces(PAWN)))); + if ((b1 | b2) & attackedBy[Them][QUEEN] & safe & ~attackedBy[Us][QUEEN]) + kingDanger += QueenSafeCheck; - // Enemy rooks safe and other checks - if (b1 & attackedBy[Them][ROOK] & safe) - kingDanger += RookCheck; + b1 &= attackedBy[Them][ROOK]; + b2 &= attackedBy[Them][BISHOP]; - else if (b1 & attackedBy[Them][ROOK] & other) - score -= OtherCheck; + // Enemy rooks checks + if (b1 & safe) + kingDanger += RookSafeCheck; + else + unsafeChecks |= b1; - // Enemy bishops safe and other checks - if (b2 & attackedBy[Them][BISHOP] & safe) - kingDanger += BishopCheck; + // Enemy bishops checks + if (b2 & safe) + kingDanger += BishopSafeCheck; + else + unsafeChecks |= b2; - else if (b2 & attackedBy[Them][BISHOP] & other) - score -= OtherCheck; - - // Enemy knights safe and other checks + // Enemy knights checks b = pos.attacks_from(ksq) & attackedBy[Them][KNIGHT]; if (b & safe) - kingDanger += KnightCheck; - - else if (b & other) - score -= OtherCheck; - - // Transform the kingDanger units into a Score, and substract it from the evaluation + kingDanger += KnightSafeCheck; + else + unsafeChecks |= b; + + // Unsafe or occupied checking squares will also be considered, as long as + // the square is in the attacker's mobility area. + unsafeChecks &= mobilityArea[Them]; + + kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] + + 102 * kingAdjacentZoneAttacksCount[Them] + + 191 * popcount(kingRing[Us] & weak) + + 143 * popcount(pos.pinned_pieces(Us) | unsafeChecks) + - 848 * !pos.count(Them) + - 9 * mg_value(score) / 8 + + 4 * tropism; + + // Transform the kingDanger units into a Score, and subtract it from the evaluation if (kingDanger > 0) + { + int mobilityDanger = (mg_value(mobility[Them] - mobility[Us])) * 2; + kingDanger = std::max(0, kingDanger + mobilityDanger); score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16); + } } - // King tropism: firstly, find squares that opponent attacks in our king flank - File kf = file_of(ksq); - b = attackedBy[Them][ALL_PIECES] & KingFlank[kf] & Camp; - - assert(((Us == WHITE ? b << 4 : b >> 4) & b) == 0); - assert(popcount(Us == WHITE ? b << 4 : b >> 4) == popcount(b)); - - // Secondly, add the squares which are attacked twice in that flank and - // which are not defended by our pawns. - b = (Us == WHITE ? b << 4 : b >> 4) - | (b & attackedBy2[Them] & ~attackedBy[Us][PAWN]); - - score -= CloseEnemies * popcount(b); + score -= CloseEnemies * tropism; // Penalty when our king is on a pawnless flank if (!(pos.pieces(PAWN) & KingFlank[kf])) @@ -526,11 +537,11 @@ namespace { template template Score Evaluation::evaluate_threats() { - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); - const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); - const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + const Direction Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); Bitboard b, weak, defended, stronglyProtected, safeThreats; Score score = SCORE_ZERO; @@ -546,9 +557,6 @@ namespace { safeThreats = (shift(b) | shift(b)) & weak; score += ThreatBySafePawn * popcount(safeThreats); - - if (weak ^ safeThreats) - score += ThreatByHangingPawn; } // Squares strongly protected by the opponent, either because they attack the @@ -593,6 +601,10 @@ namespace { score += ThreatByKing[more_than_one(b)]; } + // Bonus for opponent unopposed weak pawns + if (pos.pieces(Us, ROOK, QUEEN)) + score += WeakUnopposedPawn * pe->weak_unopposed(Them); + // Find squares where our pawns can push on the next move b = shift(pos.pieces(Us, PAWN)) & ~pos.pieces(); b |= shift(b & TRank3BB) & ~pos.pieces(); @@ -608,12 +620,24 @@ namespace { score += ThreatByPawnPush * popcount(b); + // Add a bonus for safe slider attack threats on opponent queen + safeThreats = ~pos.pieces(Us) & ~attackedBy2[Them] & attackedBy2[Us]; + b = (attackedBy[Us][BISHOP] & attackedBy[Them][QUEEN_DIAGONAL]) + | (attackedBy[Us][ROOK ] & attackedBy[Them][QUEEN] & ~attackedBy[Them][QUEEN_DIAGONAL]); + + score += ThreatByAttackOnQueen * popcount(b & safeThreats); + if (T) Trace::add(THREAT, Us, score); return score; } + // helper used by evaluate_passed_pawns to cap the distance + template + int Evaluation::king_distance(Color c, Square s) { + return std::min(distance(pos.square(c), s), 5); + } // evaluate_passed_pawns() evaluates the passed pawns and candidate passed // pawns of the given color. @@ -621,8 +645,8 @@ namespace { template template Score Evaluation::evaluate_passed_pawns() { - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); Bitboard b, bb, squaresToQueen, defendedSquares, unsafeSquares; Score score = SCORE_ZERO; @@ -638,8 +662,8 @@ namespace { bb = forward_file_bb(Us, s) & (attackedBy[Them][ALL_PIECES] | pos.pieces(Them)); score -= HinderPassedPawn * popcount(bb); - int r = relative_rank(Us, s) - RANK_2; - int rr = r * (r - 1); + int r = relative_rank(Us, s); + int rr = RankFactor[r]; Value mbonus = Passed[MG][r], ebonus = Passed[EG][r]; @@ -648,12 +672,11 @@ namespace { Square blockSq = s + Up; // Adjust bonus based on the king's proximity - ebonus += distance(pos.square(Them), blockSq) * 5 * rr - - distance(pos.square( Us), blockSq) * 2 * rr; + ebonus += (king_distance(Them, blockSq) * 5 - king_distance(Us, blockSq) * 2) * rr; // If blockSq is not the queening square then consider also a second push - if (relative_rank(Us, blockSq) != RANK_8) - ebonus -= distance(pos.square(Us), blockSq + Up) * rr; + if (r != RANK_7) + ebonus -= king_distance(Us, blockSq + Up) * rr; // If the pawn is free to advance, then increase the bonus if (pos.empty(blockSq)) @@ -673,7 +696,7 @@ namespace { // If there aren't any enemy attacks, assign a big bonus. Otherwise // assign a smaller bonus if the block square isn't attacked. - int k = !unsafeSquares ? 18 : !(unsafeSquares & blockSq) ? 8 : 0; + int k = !unsafeSquares ? 20 : !(unsafeSquares & blockSq) ? 9 : 0; //Fauzi Akram // If the path to the queen is fully defended, assign a big bonus. // Otherwise assign a smaller bonus if the block square is defended. @@ -762,6 +785,9 @@ namespace { // that the endgame score will never change sign after the bonus. int v = ((eg > 0) - (eg < 0)) * std::max(initiative, -abs(eg)); + if (T) + Trace::add(INITIATIVE, make_score(0, v)); + return make_score(0, v); } @@ -822,7 +848,7 @@ namespace { // Initialize score by reading the incrementally updated scores included in // the position object (material + piece square tables) and the material // imbalance. Score is computed internally from the white point of view. - Score score = pos.psq_score() + me->imbalance(); + Score score = pos.psq_score() + me->imbalance() + Eval::Contempt; // Probe the pawn hash table pe = Pawns::probe(pos); @@ -880,18 +906,19 @@ namespace { Trace::add(TOTAL, score); } - return (pos.side_to_move() == WHITE ? v : -v) + Eval::Tempo; // Side to move point of view + return pos.side_to_move() == WHITE ? v : -v; // Side to move point of view } } // namespace +Score Eval::Contempt = SCORE_ZERO; /// evaluate() is the evaluator for the outer world. It returns a static evaluation /// of the position from the point of view of the side to move. Value Eval::evaluate(const Position& pos) { - return Evaluation<>(pos).value(); + return Evaluation<>(pos).value() + Eval::Tempo; } /// trace() is like evaluate(), but instead of returning a value, it returns @@ -902,7 +929,7 @@ std::string Eval::trace(const Position& pos) { std::memset(scores, 0, sizeof(scores)); - Value v = Evaluation(pos).value(); + Value v = Evaluation(pos).value() + Eval::Tempo; v = pos.side_to_move() == WHITE ? v : -v; // White's point of view std::stringstream ss; @@ -922,6 +949,7 @@ std::string Eval::trace(const Position& pos) { << " Threats | " << Term(THREAT) << " Passed pawns | " << Term(PASSED) << " Space | " << Term(SPACE) + << " Initiative | " << Term(INITIATIVE) << "----------------+-------------+-------------+-------------\n" << " Total | " << Term(TOTAL); diff --git a/Engines/Linux32/mcbrain/src/evaluate.h b/Engines/Linux32/mcbrain/src/evaluate.h index 95a1f19..8d388fa 100644 --- a/Engines/Linux32/mcbrain/src/evaluate.h +++ b/Engines/Linux32/mcbrain/src/evaluate.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef EVALUATE_H_INCLUDED #define EVALUATE_H_INCLUDED @@ -31,6 +32,8 @@ namespace Eval { const Value Tempo = Value(20); // Must be visible to search +extern Score Contempt; + std::string trace(const Position& pos); Value evaluate(const Position& pos); diff --git a/Engines/Linux32/mcbrain/src/main.cpp b/Engines/Linux32/mcbrain/src/main.cpp index 5c2810e..637dc3b 100644 --- a/Engines/Linux32/mcbrain/src/main.cpp +++ b/Engines/Linux32/mcbrain/src/main.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include @@ -27,7 +28,7 @@ #include "tt.h" #include "uci.h" #include "tbprobe.h" -#include "tzbook.h" +#include "polybook.h" namespace PSQT { void init(); @@ -44,13 +45,14 @@ int main(int argc, char* argv[]) { Bitbases::init(); Search::init(); Pawns::init(); + polybook.init(Options["BookFile"]); Tablebases::init(Options["SyzygyPath"]); TT.resize(Options["Hash"]); - Threads.init(Options["Threads"]); + Threads.set(Options["Threads"]); Search::clear(); // After threads are up UCI::loop(argc, argv); - Threads.exit(); + Threads.set(0); return 0; } diff --git a/Engines/Linux32/mcbrain/src/material.cpp b/Engines/Linux32/mcbrain/src/material.cpp index d67b95c..2b28837 100644 --- a/Engines/Linux32/mcbrain/src/material.cpp +++ b/Engines/Linux32/mcbrain/src/material.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include // For std::min #include @@ -39,7 +40,7 @@ namespace { { 32, 255, -3 }, // Knight OUR PIECES { 0, 104, 4, 0 }, // Bishop { -26, -2, 47, 105, -149 }, // Rook - {-185, 24, 122, 137, -134, 0 } // Queen + {-189, 24, 117, 133, -134, -10 } // Queen }; const int QuadraticTheirs[][PIECE_TYPE_NB] = { @@ -50,18 +51,7 @@ namespace { { 9, 63, 0 }, // Knight OUR PIECES { 59, 65, 42, 0 }, // Bishop { 46, 39, 24, -24, 0 }, // Rook - { 101, 100, -37, 141, 268, 0 } // Queen - }; - - // PawnSet[pawn count] contains a bonus/malus indexed by number of pawns - const int PawnSet[] = { - 24, -32, 107, -51, 117, -9, -126, -21, 31 - }; - - // QueenMinorsImbalance[opp_minor_count] is applied when only one side has a queen. - // It contains a bonus/malus for the side with the queen. - const int QueenMinorsImbalance[13] = { - 31, -8, -15, -25, -5 + { 97, 100, -42, 137, 268, 0 } // Queen }; // Endgame evaluation and scaling functions are accessed directly and not through @@ -100,9 +90,9 @@ namespace { const Color Them = (Us == WHITE ? BLACK : WHITE); - int bonus = PawnSet[pieceCount[Us][PAWN]]; + int bonus = 0; - // Second-degree polynomial material imbalance by Tord Romstad + // Second-degree polynomial material imbalance, by Tord Romstad for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) { if (!pieceCount[Us][pt1]) @@ -117,10 +107,6 @@ namespace { bonus += pieceCount[Us][pt1] * v; } - // Special handling of Queen vs. Minors - if (pieceCount[Us][QUEEN] == 1 && pieceCount[Them][QUEEN] == 0) - bonus += QueenMinorsImbalance[pieceCount[Them][KNIGHT] + pieceCount[Them][BISHOP]]; - return bonus; } diff --git a/Engines/Linux32/mcbrain/src/material.h b/Engines/Linux32/mcbrain/src/material.h index ccf97b7..95d6c84 100644 --- a/Engines/Linux32/mcbrain/src/material.h +++ b/Engines/Linux32/mcbrain/src/material.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef MATERIAL_H_INCLUDED #define MATERIAL_H_INCLUDED diff --git a/Engines/Linux32/mcbrain/src/misc.cpp b/Engines/Linux32/mcbrain/src/misc.cpp index 16dc981..a14d9c4 100644 --- a/Engines/Linux32/mcbrain/src/misc.cpp +++ b/Engines/Linux32/mcbrain/src/misc.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifdef _WIN32 #if _WIN32_WINNT < 0x0601 @@ -51,7 +52,7 @@ namespace { /// Version number. If Version is left empty, then compile date in the format /// DD-MM-YY and show in engine_info. -const string Version = "v2.7"; +const string Version = "9"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We @@ -122,7 +123,7 @@ const string engine_info(bool to_uci) { string month, day, year; stringstream ss, date(__DATE__); // From compiler, format is "Sep 21 2008" - ss << "SF-McBrain " << Version << setfill('0'); + ss << "McBrain " << Version << setfill('0'); if (Version.empty()) { @@ -133,7 +134,7 @@ const string engine_info(bool to_uci) { ss << (Is64Bit ? " 64" : "") << (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : "")) << (to_uci ? "\nid author ": " by ") - << "M. Byrne based on Stockfish"; + << "M. Byrne, Stockfish authors and others"; return ss.str(); } @@ -293,14 +294,6 @@ int get_group(size_t idx) { void bindThisThread(size_t idx) { - // If OS already scheduled us on a different group than 0 then don't overwrite - // the choice, eventually we are one of many one-threaded processes running on - // some Windows NUMA hardware, for instance in fishtest. To make it simple, - // just check if running threads are below a threshold, in this case all this - // NUMA machinery is not needed. - if (Threads.size() < 8) - return; - // Use only local variables to be thread-safe int group = get_group(idx); diff --git a/Engines/Linux32/mcbrain/src/misc.h b/Engines/Linux32/mcbrain/src/misc.h index b63e613..98b5202 100644 --- a/Engines/Linux32/mcbrain/src/misc.h +++ b/Engines/Linux32/mcbrain/src/misc.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef MISC_H_INCLUDED #define MISC_H_INCLUDED diff --git a/Engines/Linux32/mcbrain/src/movegen.cpp b/Engines/Linux32/mcbrain/src/movegen.cpp index 465d616..d06bc86 100644 --- a/Engines/Linux32/mcbrain/src/movegen.cpp +++ b/Engines/Linux32/mcbrain/src/movegen.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include @@ -42,8 +43,8 @@ namespace { assert(!pos.checkers()); - const Square K = Chess960 ? kto > kfrom ? WEST : EAST - : KingSide ? WEST : EAST; + const Direction K = Chess960 ? kto > kfrom ? WEST : EAST + : KingSide ? WEST : EAST; for (Square s = kto; s != kfrom; s += K) if (pos.attackers_to(s) & enemies) @@ -65,7 +66,7 @@ namespace { } - template + template ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) { if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) @@ -94,13 +95,13 @@ namespace { // Compute our parametrized parameters at compile time, named according to // the point of view of white side. - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); - const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); - const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); - const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); + const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Direction Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); Bitboard emptySquares; diff --git a/Engines/Linux32/mcbrain/src/movegen.h b/Engines/Linux32/mcbrain/src/movegen.h index 8256057..666e4ef 100644 --- a/Engines/Linux32/mcbrain/src/movegen.h +++ b/Engines/Linux32/mcbrain/src/movegen.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef MOVEGEN_H_INCLUDED #define MOVEGEN_H_INCLUDED @@ -45,7 +46,7 @@ struct ExtMove { // Inhibit unwanted implicit conversions to Move // with an ambiguity that yields to a compile error. - operator float() const; + operator float() const = delete; }; inline bool operator<(const ExtMove& f, const ExtMove& s) { diff --git a/Engines/Linux32/mcbrain/src/movepick.cpp b/Engines/Linux32/mcbrain/src/movepick.cpp index 1c0de69..f62ca2b 100644 --- a/Engines/Linux32/mcbrain/src/movepick.cpp +++ b/Engines/Linux32/mcbrain/src/movepick.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include @@ -28,8 +29,7 @@ namespace { MAIN_SEARCH, CAPTURES_INIT, GOOD_CAPTURES, KILLERS, COUNTERMOVE, QUIET_INIT, QUIET, BAD_CAPTURES, EVASION, EVASIONS_INIT, ALL_EVASIONS, PROBCUT, PROBCUT_INIT, PROBCUT_CAPTURES, - QSEARCH_WITH_CHECKS, QCAPTURES_1_INIT, QCAPTURES_1, QCHECKS, - QSEARCH_NO_CHECKS, QCAPTURES_2_INIT, QCAPTURES_2, + QSEARCH, QCAPTURES_INIT, QCAPTURES, QCHECKS, QSEARCH_RECAPTURES, QRECAPTURES }; @@ -68,8 +68,8 @@ namespace { /// MovePicker constructor for the main search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const PieceToHistory** ch, Move cm, Move* killers_p) - : pos(p), mainHistory(mh), contHistory(ch), countermove(cm), + const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, Move* killers_p) + : pos(p), mainHistory(mh), captureHistory(cph), contHistory(ch), countermove(cm), killers{killers_p[0], killers_p[1]}, depth(d){ assert(d > DEPTH_ZERO); @@ -80,19 +80,16 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist } /// MovePicker constructor for quiescence search -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, Square s) - : pos(p), mainHistory(mh) { +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const CapturePieceToHistory* cph, Square s) + : pos(p), mainHistory(mh), captureHistory(cph), depth(d) { assert(d <= DEPTH_ZERO); if (pos.checkers()) stage = EVASION; - else if (d > DEPTH_QS_NO_CHECKS) - stage = QSEARCH_WITH_CHECKS; - else if (d > DEPTH_QS_RECAPTURES) - stage = QSEARCH_NO_CHECKS; + stage = QSEARCH; else { @@ -107,8 +104,8 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist /// MovePicker constructor for ProbCut: we generate captures with SEE higher /// than or equal to the given threshold. -MovePicker::MovePicker(const Position& p, Move ttm, Value th) - : pos(p), threshold(th) { +MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) + : pos(p), captureHistory(cph), threshold(th) { assert(!pos.checkers()); @@ -123,7 +120,7 @@ MovePicker::MovePicker(const Position& p, Move ttm, Value th) /// score() assigns a numerical value to each move in a list, used for sorting. /// Captures are ordered by Most Valuable Victim (MVV), preferring captures -/// near our home rank. Quiets are ordered using the histories. +/// with a good history. Quiets are ordered using the histories. template void MovePicker::score() { @@ -132,7 +129,7 @@ void MovePicker::score() { for (auto& m : *this) if (Type == CAPTURES) m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - - Value(200 * relative_rank(pos.side_to_move(), to_sq(m))); + + Value((*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]); else if (Type == QUIETS) m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] @@ -161,8 +158,7 @@ Move MovePicker::next_move(bool skipQuiets) { switch (stage) { - case MAIN_SEARCH: case EVASION: case QSEARCH_WITH_CHECKS: - case QSEARCH_NO_CHECKS: case PROBCUT: + case MAIN_SEARCH: case EVASION: case QSEARCH: case PROBCUT: ++stage; return ttMove; @@ -179,7 +175,7 @@ Move MovePicker::next_move(bool skipQuiets) { move = pick_best(cur++, endMoves); if (move != ttMove) { - if (pos.see_ge(move)) + if (pos.see_ge(move, Value(-55 * (cur-1)->value / 1024))) return move; // Losing capture, move it to the beginning of the array @@ -280,21 +276,21 @@ Move MovePicker::next_move(bool skipQuiets) { } break; - case QCAPTURES_1_INIT: case QCAPTURES_2_INIT: + case QCAPTURES_INIT: cur = moves; endMoves = generate(pos, cur); score(); ++stage; /* fallthrough */ - case QCAPTURES_1: case QCAPTURES_2: + case QCAPTURES: while (cur < endMoves) { move = pick_best(cur++, endMoves); if (move != ttMove) return move; } - if (stage == QCAPTURES_2) + if (depth <= DEPTH_QS_NO_CHECKS) break; cur = moves; endMoves = generate(pos, cur); @@ -313,14 +309,13 @@ Move MovePicker::next_move(bool skipQuiets) { case QSEARCH_RECAPTURES: cur = moves; endMoves = generate(pos, cur); - score(); ++stage; /* fallthrough */ case QRECAPTURES: while (cur < endMoves) { - move = pick_best(cur++, endMoves); + move = *cur++; if (to_sq(move) == recaptureSquare) return move; } diff --git a/Engines/Linux32/mcbrain/src/movepick.h b/Engines/Linux32/mcbrain/src/movepick.h index 66fc1bd..26e2a14 100644 --- a/Engines/Linux32/mcbrain/src/movepick.h +++ b/Engines/Linux32/mcbrain/src/movepick.h @@ -1,27 +1,29 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef MOVEPICK_H_INCLUDED #define MOVEPICK_H_INCLUDED #include +#include #include "movegen.h" #include "position.h" @@ -39,7 +41,7 @@ struct StatBoards : public std::array, Size1> { void update(T& entry, int bonus, const int D) { assert(abs(bonus) <= D); // Ensure range is [-32 * D, 32 * D] - assert(abs(32 * D) < INT16_MAX); // Ensure we don't overflow + assert(abs(32 * D) < (std::numeric_limits::max)()); // Ensure we don't overflow entry += bonus * 32 - entry * abs(bonus) / D; @@ -47,6 +49,26 @@ struct StatBoards : public std::array, Size1> { } }; +/// StatCubes is a generic 3-dimensional array used to store various statistics +template +struct StatCubes : public std::array, Size2>, Size1> { + + void fill(const T& v) { + T* p = &(*this)[0][0][0]; + std::fill(p, p + sizeof(*this) / sizeof(*p), v); + } + + void update(T& entry, int bonus, const int D, const int W) { + + assert(abs(bonus) <= D); // Ensure range is [-W * D, W * D] + assert(abs(W * D) < (std::numeric_limits::max)()); // Ensure we don't overflow + + entry += bonus * W - entry * abs(bonus) / D; + + assert(abs(entry) <= W * D); + } +}; + /// ButterflyBoards are 2 tables (one for each color) indexed by the move's from /// and to squares, see chessprogramming.wikispaces.com/Butterfly+Boards typedef StatBoards ButterflyBoards; @@ -54,6 +76,9 @@ typedef StatBoards ButterflyBoards; /// PieceToBoards are addressed by a move's [piece][to] information typedef StatBoards PieceToBoards; +/// CapturePieceToBoards are addressed by a move's [piece][to][captured piece type] information +typedef StatCubes CapturePieceToBoards; + /// ButterflyHistory records how often quiet moves have been successful or /// unsuccessful during the current search, and is used for reduction and move /// ordering decisions. It uses ButterflyBoards as backing store. @@ -72,6 +97,14 @@ struct PieceToHistory : public PieceToBoards { } }; +/// CapturePieceToHistory is like PieceToHistory, but is based on CapturePieceToBoards +struct CapturePieceToHistory : public CapturePieceToBoards { + + void update(Piece pc, Square to, PieceType captured, int bonus) { + StatCubes::update((*this)[pc][to][captured], bonus, 324, 2); + } +}; + /// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous /// move, see chessprogramming.wikispaces.com/Countermove+Heuristic typedef StatBoards CounterMoveHistory; @@ -93,9 +126,9 @@ class MovePicker { public: MovePicker(const MovePicker&) = delete; MovePicker& operator=(const MovePicker&) = delete; - MovePicker(const Position&, Move, Value); - MovePicker(const Position&, Move, Depth, const ButterflyHistory*, Square); - MovePicker(const Position&, Move, Depth, const ButterflyHistory*, const PieceToHistory**, Move, Move*); + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + MovePicker(const Position&, Move, Depth, const ButterflyHistory*, const CapturePieceToHistory*, Square); + MovePicker(const Position&, Move, Depth, const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, Move, Move*); Move next_move(bool skipQuiets = false); private: @@ -105,8 +138,9 @@ class MovePicker { const Position& pos; const ButterflyHistory* mainHistory; + const CapturePieceToHistory* captureHistory; const PieceToHistory** contHistory; - Move ttMove, countermove, killers[2]; + Move ttMove, countermove, killers[4]; ExtMove *cur, *endMoves, *endBadCaptures; int stage; Square recaptureSquare; diff --git a/Engines/Linux32/mcbrain/src/pawns.cpp b/Engines/Linux32/mcbrain/src/pawns.cpp index 98811e7..3e5c2ed 100644 --- a/Engines/Linux32/mcbrain/src/pawns.cpp +++ b/Engines/Linux32/mcbrain/src/pawns.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include #include @@ -31,11 +32,11 @@ namespace { #define V Value #define S(mg, eg) make_score(mg, eg) - // Isolated pawn penalty by opposed flag - const Score Isolated[] = { S(27, 30), S(13, 18) }; + // Isolated pawn penalty + const Score Isolated = S(13, 18); - // Backward pawn penalty by opposed flag - const Score Backward[] = { S(40, 26), S(24, 12) }; + // Backward pawn penalty + const Score Backward = S(24, 12); // Connected pawn bonus by opposed, phalanx, #support and rank Score Connected[2][2][3][RANK_NB]; @@ -43,38 +44,36 @@ namespace { // Doubled pawn penalty const Score Doubled = S(18, 38); - // Lever bonus by rank - const Score Lever[RANK_NB] = { - S( 0, 0), S( 0, 0), S(0, 0), S(0, 0), - S(17, 16), S(33, 32), S(0, 0), S(0, 0) - }; - - // Weakness of our pawn shelter in front of the king by [distance from edge][rank]. + // Weakness of our pawn shelter in front of the king by [isKingFile][distance from edge][rank]. // RANK_1 = 0 is used for files where we have no pawns or our pawn is behind our king. - const Value ShelterWeakness[][RANK_NB] = { - { V(100), V(20), V(10), V(46), V(82), V( 86), V( 98) }, - { V(116), V( 4), V(28), V(87), V(94), V(108), V(104) }, - { V(109), V( 1), V(59), V(87), V(62), V( 91), V(116) }, - { V( 75), V(12), V(43), V(59), V(90), V( 84), V(112) } + const Value ShelterWeakness[][int(FILE_NB) / 2][RANK_NB] = { + { { V( 97), V(17), V( 9), V(44), V( 84), V( 87), V( 99) }, // Not On King file + { V(106), V( 6), V(33), V(86), V( 87), V(104), V(112) }, + { V(101), V( 2), V(65), V(98), V( 58), V( 89), V(115) }, + { V( 73), V( 7), V(54), V(73), V( 84), V( 83), V(111) } }, + { { V(104), V(20), V( 6), V(27), V( 86), V( 93), V( 82) }, // On King file + { V(123), V( 9), V(34), V(96), V(112), V( 88), V( 75) }, + { V(120), V(25), V(65), V(91), V( 66), V( 78), V(117) }, + { V( 81), V( 2), V(47), V(63), V( 94), V( 93), V(104) } } }; // Danger of enemy pawns moving toward our king by [type][distance from edge][rank]. - // For the unopposed and unblocked cases, RANK_1 = 0 is used when opponent has no pawn - // on the given file, or their pawn is behind our king. + // For the unopposed and unblocked cases, RANK_1 = 0 is used when opponent has + // no pawn on the given file, or their pawn is behind our king. const Value StormDanger[][4][RANK_NB] = { - { { V( 0), V(-290), V(-274), V(57), V(41) }, //BlockedByKing + { { V( 0), V(-290), V(-274), V(57), V(41) }, // BlockedByKing { V( 0), V( 60), V( 144), V(39), V(13) }, { V( 0), V( 65), V( 141), V(41), V(34) }, { V( 0), V( 53), V( 127), V(56), V(14) } }, - { { V( 4), V( 73), V( 132), V(46), V(31) }, //Unopposed + { { V( 4), V( 73), V( 132), V(46), V(31) }, // Unopposed { V( 1), V( 64), V( 143), V(26), V(13) }, { V( 1), V( 47), V( 110), V(44), V(24) }, { V( 0), V( 72), V( 127), V(50), V(31) } }, - { { V( 0), V( 0), V( 79), V(23), V( 1) }, //BlockedByPawn + { { V( 0), V( 0), V( 79), V(23), V( 1) }, // BlockedByPawn { V( 0), V( 0), V( 148), V(27), V( 2) }, { V( 0), V( 0), V( 161), V(16), V( 1) }, { V( 0), V( 0), V( 171), V(22), V(15) } }, - { { V(22), V( 45), V( 104), V(62), V( 6) }, //Unblocked + { { V(22), V( 45), V( 104), V(62), V( 6) }, // Unblocked { V(31), V( 30), V( 99), V(39), V(19) }, { V(23), V( 29), V( 96), V(41), V(15) }, { V(21), V( 23), V( 116), V(41), V(15) } } @@ -90,10 +89,10 @@ namespace { template Score evaluate(const Position& pos, Pawns::Entry* e) { - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); - const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Direction Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); Bitboard b, neighbours, stoppers, doubled, supported, phalanx; Bitboard lever, leverPush; @@ -105,7 +104,7 @@ namespace { Bitboard ourPawns = pos.pieces( Us, PAWN); Bitboard theirPawns = pos.pieces(Them, PAWN); - e->passedPawns[Us] = e->pawnAttacksSpan[Us] = 0; + e->passedPawns[Us] = e->pawnAttacksSpan[Us] = e->weakUnopposed[Us] = 0; e->semiopenFiles[Us] = 0xFF; e->kingSquares[Us] = SQ_NONE; e->pawnAttacks[Us] = shift(ourPawns) | shift(ourPawns); @@ -160,7 +159,7 @@ namespace { e->passedPawns[Us] |= s; else if ( stoppers == SquareBB[s + Up] - && relative_rank(Us, s) >= RANK_4) + && relative_rank(Us, s) >= RANK_5) { b = shift(supported) & ~theirPawns; while (b) @@ -170,19 +169,16 @@ namespace { // Score this pawn if (supported | phalanx) - score += Connected[opposed][!!phalanx][popcount(supported)][relative_rank(Us, s)]; + score += Connected[opposed][bool(phalanx)][popcount(supported)][relative_rank(Us, s)]; else if (!neighbours) - score -= Isolated[opposed]; + score -= Isolated, e->weakUnopposed[Us] += !opposed; else if (backward) - score -= Backward[opposed]; + score -= Backward, e->weakUnopposed[Us] += !opposed; if (doubled && !supported) score -= Doubled; - - if (lever) - score += Lever[relative_rank(Us, s)]; } return score; @@ -250,7 +246,7 @@ Value Entry::shelter_storm(const Position& pos, Square ksq) { Value safety = MaxSafetyBonus; File center = std::max(FILE_B, std::min(FILE_G, file_of(ksq))); - for (File f = center - File(1); f <= center + File(1); ++f) + for (File f = File(center - 1); f <= File(center + 1); ++f) { b = ourPawns & file_bb(f); Rank rkUs = b ? relative_rank(Us, backmost_sq(Us, b)) : RANK_1; @@ -258,8 +254,8 @@ Value Entry::shelter_storm(const Position& pos, Square ksq) { b = theirPawns & file_bb(f); Rank rkThem = b ? relative_rank(Us, frontmost_sq(Them, b)) : RANK_1; - int d = std::min(f, FILE_H - f); - safety -= ShelterWeakness[d][rkUs] + int d = std::min(f, ~f); + safety -= ShelterWeakness[f == file_of(ksq)][d][rkUs] + StormDanger [f == file_of(ksq) && rkThem == relative_rank(Us, ksq) + 1 ? BlockedByKing : rkUs == RANK_1 ? Unopposed : diff --git a/Engines/Linux32/mcbrain/src/pawns.h b/Engines/Linux32/mcbrain/src/pawns.h index 15b0b77..e834e18 100644 --- a/Engines/Linux32/mcbrain/src/pawns.h +++ b/Engines/Linux32/mcbrain/src/pawns.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-218 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef PAWNS_H_INCLUDED #define PAWNS_H_INCLUDED @@ -37,6 +38,7 @@ struct Entry { Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } Bitboard passed_pawns(Color c) const { return passedPawns[c]; } Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; } + int weak_unopposed(Color c) const { return weakUnopposed[c]; } int pawn_asymmetry() const { return asymmetry; } int open_files() const { return openFiles; } @@ -49,7 +51,7 @@ struct Entry { } int pawns_on_same_color_squares(Color c, Square s) const { - return pawnsOnSquares[c][!!(DarkSquares & s)]; + return pawnsOnSquares[c][bool(DarkSquares & s)]; } template @@ -71,6 +73,7 @@ struct Entry { Bitboard pawnAttacksSpan[COLOR_NB]; Square kingSquares[COLOR_NB]; Score kingSafety[COLOR_NB]; + int weakUnopposed[COLOR_NB]; int castlingRights[COLOR_NB]; int semiopenFiles[COLOR_NB]; int pawnsOnSquares[COLOR_NB][COLOR_NB]; // [color][light/dark squares] diff --git a/Engines/Linux32/mcbrain/src/polybook.cpp b/Engines/Linux32/mcbrain/src/polybook.cpp new file mode 100644 index 0000000..9aaacb4 --- /dev/null +++ b/Engines/Linux32/mcbrain/src/polybook.cpp @@ -0,0 +1,737 @@ +/* + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +// polybook.cpp was written by Thomas Ziproth +/* +BrainFish, a UCI chess playing engine derived from Stockfish +Copyright (C) 2016-2017 Thomas Zipproth + +BrainFish is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BrainFish is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "polybook.h" +#include "uci.h" +#include "movegen.h" +#include "thread.h" +#include +#include "misc.h" +#include + +PolyBook polybook; // global PolyBook + +using namespace std; + +// Random numbers from PolyGlot, used to compute book hash keys +const union { + uint64_t PolyGlotRandoms[781]; + struct { + uint64_t psq[12][64]; // [piece][square] + uint64_t castle[4]; // [castle right] + uint64_t enpassant[8]; // [file] + uint64_t turn; + } Zobrist; +} PG = { { + 0x9D39247E33776D41ULL, 0x2AF7398005AAA5C7ULL, 0x44DB015024623547ULL, + 0x9C15F73E62A76AE2ULL, 0x75834465489C0C89ULL, 0x3290AC3A203001BFULL, + 0x0FBBAD1F61042279ULL, 0xE83A908FF2FB60CAULL, 0x0D7E765D58755C10ULL, + 0x1A083822CEAFE02DULL, 0x9605D5F0E25EC3B0ULL, 0xD021FF5CD13A2ED5ULL, + 0x40BDF15D4A672E32ULL, 0x011355146FD56395ULL, 0x5DB4832046F3D9E5ULL, + 0x239F8B2D7FF719CCULL, 0x05D1A1AE85B49AA1ULL, 0x679F848F6E8FC971ULL, + 0x7449BBFF801FED0BULL, 0x7D11CDB1C3B7ADF0ULL, 0x82C7709E781EB7CCULL, + 0xF3218F1C9510786CULL, 0x331478F3AF51BBE6ULL, 0x4BB38DE5E7219443ULL, + 0xAA649C6EBCFD50FCULL, 0x8DBD98A352AFD40BULL, 0x87D2074B81D79217ULL, + 0x19F3C751D3E92AE1ULL, 0xB4AB30F062B19ABFULL, 0x7B0500AC42047AC4ULL, + 0xC9452CA81A09D85DULL, 0x24AA6C514DA27500ULL, 0x4C9F34427501B447ULL, + 0x14A68FD73C910841ULL, 0xA71B9B83461CBD93ULL, 0x03488B95B0F1850FULL, + 0x637B2B34FF93C040ULL, 0x09D1BC9A3DD90A94ULL, 0x3575668334A1DD3BULL, + 0x735E2B97A4C45A23ULL, 0x18727070F1BD400BULL, 0x1FCBACD259BF02E7ULL, + 0xD310A7C2CE9B6555ULL, 0xBF983FE0FE5D8244ULL, 0x9F74D14F7454A824ULL, + 0x51EBDC4AB9BA3035ULL, 0x5C82C505DB9AB0FAULL, 0xFCF7FE8A3430B241ULL, + 0x3253A729B9BA3DDEULL, 0x8C74C368081B3075ULL, 0xB9BC6C87167C33E7ULL, + 0x7EF48F2B83024E20ULL, 0x11D505D4C351BD7FULL, 0x6568FCA92C76A243ULL, + 0x4DE0B0F40F32A7B8ULL, 0x96D693460CC37E5DULL, 0x42E240CB63689F2FULL, + 0x6D2BDCDAE2919661ULL, 0x42880B0236E4D951ULL, 0x5F0F4A5898171BB6ULL, + 0x39F890F579F92F88ULL, 0x93C5B5F47356388BULL, 0x63DC359D8D231B78ULL, + 0xEC16CA8AEA98AD76ULL, 0x5355F900C2A82DC7ULL, 0x07FB9F855A997142ULL, + 0x5093417AA8A7ED5EULL, 0x7BCBC38DA25A7F3CULL, 0x19FC8A768CF4B6D4ULL, + 0x637A7780DECFC0D9ULL, 0x8249A47AEE0E41F7ULL, 0x79AD695501E7D1E8ULL, + 0x14ACBAF4777D5776ULL, 0xF145B6BECCDEA195ULL, 0xDABF2AC8201752FCULL, + 0x24C3C94DF9C8D3F6ULL, 0xBB6E2924F03912EAULL, 0x0CE26C0B95C980D9ULL, + 0xA49CD132BFBF7CC4ULL, 0xE99D662AF4243939ULL, 0x27E6AD7891165C3FULL, + 0x8535F040B9744FF1ULL, 0x54B3F4FA5F40D873ULL, 0x72B12C32127FED2BULL, + 0xEE954D3C7B411F47ULL, 0x9A85AC909A24EAA1ULL, 0x70AC4CD9F04F21F5ULL, + 0xF9B89D3E99A075C2ULL, 0x87B3E2B2B5C907B1ULL, 0xA366E5B8C54F48B8ULL, + 0xAE4A9346CC3F7CF2ULL, 0x1920C04D47267BBDULL, 0x87BF02C6B49E2AE9ULL, + 0x092237AC237F3859ULL, 0xFF07F64EF8ED14D0ULL, 0x8DE8DCA9F03CC54EULL, + 0x9C1633264DB49C89ULL, 0xB3F22C3D0B0B38EDULL, 0x390E5FB44D01144BULL, + 0x5BFEA5B4712768E9ULL, 0x1E1032911FA78984ULL, 0x9A74ACB964E78CB3ULL, + 0x4F80F7A035DAFB04ULL, 0x6304D09A0B3738C4ULL, 0x2171E64683023A08ULL, + 0x5B9B63EB9CEFF80CULL, 0x506AACF489889342ULL, 0x1881AFC9A3A701D6ULL, + 0x6503080440750644ULL, 0xDFD395339CDBF4A7ULL, 0xEF927DBCF00C20F2ULL, + 0x7B32F7D1E03680ECULL, 0xB9FD7620E7316243ULL, 0x05A7E8A57DB91B77ULL, + 0xB5889C6E15630A75ULL, 0x4A750A09CE9573F7ULL, 0xCF464CEC899A2F8AULL, + 0xF538639CE705B824ULL, 0x3C79A0FF5580EF7FULL, 0xEDE6C87F8477609DULL, + 0x799E81F05BC93F31ULL, 0x86536B8CF3428A8CULL, 0x97D7374C60087B73ULL, + 0xA246637CFF328532ULL, 0x043FCAE60CC0EBA0ULL, 0x920E449535DD359EULL, + 0x70EB093B15B290CCULL, 0x73A1921916591CBDULL, 0x56436C9FE1A1AA8DULL, + 0xEFAC4B70633B8F81ULL, 0xBB215798D45DF7AFULL, 0x45F20042F24F1768ULL, + 0x930F80F4E8EB7462ULL, 0xFF6712FFCFD75EA1ULL, 0xAE623FD67468AA70ULL, + 0xDD2C5BC84BC8D8FCULL, 0x7EED120D54CF2DD9ULL, 0x22FE545401165F1CULL, + 0xC91800E98FB99929ULL, 0x808BD68E6AC10365ULL, 0xDEC468145B7605F6ULL, + 0x1BEDE3A3AEF53302ULL, 0x43539603D6C55602ULL, 0xAA969B5C691CCB7AULL, + 0xA87832D392EFEE56ULL, 0x65942C7B3C7E11AEULL, 0xDED2D633CAD004F6ULL, + 0x21F08570F420E565ULL, 0xB415938D7DA94E3CULL, 0x91B859E59ECB6350ULL, + 0x10CFF333E0ED804AULL, 0x28AED140BE0BB7DDULL, 0xC5CC1D89724FA456ULL, + 0x5648F680F11A2741ULL, 0x2D255069F0B7DAB3ULL, 0x9BC5A38EF729ABD4ULL, + 0xEF2F054308F6A2BCULL, 0xAF2042F5CC5C2858ULL, 0x480412BAB7F5BE2AULL, + 0xAEF3AF4A563DFE43ULL, 0x19AFE59AE451497FULL, 0x52593803DFF1E840ULL, + 0xF4F076E65F2CE6F0ULL, 0x11379625747D5AF3ULL, 0xBCE5D2248682C115ULL, + 0x9DA4243DE836994FULL, 0x066F70B33FE09017ULL, 0x4DC4DE189B671A1CULL, + 0x51039AB7712457C3ULL, 0xC07A3F80C31FB4B4ULL, 0xB46EE9C5E64A6E7CULL, + 0xB3819A42ABE61C87ULL, 0x21A007933A522A20ULL, 0x2DF16F761598AA4FULL, + 0x763C4A1371B368FDULL, 0xF793C46702E086A0ULL, 0xD7288E012AEB8D31ULL, + 0xDE336A2A4BC1C44BULL, 0x0BF692B38D079F23ULL, 0x2C604A7A177326B3ULL, + 0x4850E73E03EB6064ULL, 0xCFC447F1E53C8E1BULL, 0xB05CA3F564268D99ULL, + 0x9AE182C8BC9474E8ULL, 0xA4FC4BD4FC5558CAULL, 0xE755178D58FC4E76ULL, + 0x69B97DB1A4C03DFEULL, 0xF9B5B7C4ACC67C96ULL, 0xFC6A82D64B8655FBULL, + 0x9C684CB6C4D24417ULL, 0x8EC97D2917456ED0ULL, 0x6703DF9D2924E97EULL, + 0xC547F57E42A7444EULL, 0x78E37644E7CAD29EULL, 0xFE9A44E9362F05FAULL, + 0x08BD35CC38336615ULL, 0x9315E5EB3A129ACEULL, 0x94061B871E04DF75ULL, + 0xDF1D9F9D784BA010ULL, 0x3BBA57B68871B59DULL, 0xD2B7ADEEDED1F73FULL, + 0xF7A255D83BC373F8ULL, 0xD7F4F2448C0CEB81ULL, 0xD95BE88CD210FFA7ULL, + 0x336F52F8FF4728E7ULL, 0xA74049DAC312AC71ULL, 0xA2F61BB6E437FDB5ULL, + 0x4F2A5CB07F6A35B3ULL, 0x87D380BDA5BF7859ULL, 0x16B9F7E06C453A21ULL, + 0x7BA2484C8A0FD54EULL, 0xF3A678CAD9A2E38CULL, 0x39B0BF7DDE437BA2ULL, + 0xFCAF55C1BF8A4424ULL, 0x18FCF680573FA594ULL, 0x4C0563B89F495AC3ULL, + 0x40E087931A00930DULL, 0x8CFFA9412EB642C1ULL, 0x68CA39053261169FULL, + 0x7A1EE967D27579E2ULL, 0x9D1D60E5076F5B6FULL, 0x3810E399B6F65BA2ULL, + 0x32095B6D4AB5F9B1ULL, 0x35CAB62109DD038AULL, 0xA90B24499FCFAFB1ULL, + 0x77A225A07CC2C6BDULL, 0x513E5E634C70E331ULL, 0x4361C0CA3F692F12ULL, + 0xD941ACA44B20A45BULL, 0x528F7C8602C5807BULL, 0x52AB92BEB9613989ULL, + 0x9D1DFA2EFC557F73ULL, 0x722FF175F572C348ULL, 0x1D1260A51107FE97ULL, + 0x7A249A57EC0C9BA2ULL, 0x04208FE9E8F7F2D6ULL, 0x5A110C6058B920A0ULL, + 0x0CD9A497658A5698ULL, 0x56FD23C8F9715A4CULL, 0x284C847B9D887AAEULL, + 0x04FEABFBBDB619CBULL, 0x742E1E651C60BA83ULL, 0x9A9632E65904AD3CULL, + 0x881B82A13B51B9E2ULL, 0x506E6744CD974924ULL, 0xB0183DB56FFC6A79ULL, + 0x0ED9B915C66ED37EULL, 0x5E11E86D5873D484ULL, 0xF678647E3519AC6EULL, + 0x1B85D488D0F20CC5ULL, 0xDAB9FE6525D89021ULL, 0x0D151D86ADB73615ULL, + 0xA865A54EDCC0F019ULL, 0x93C42566AEF98FFBULL, 0x99E7AFEABE000731ULL, + 0x48CBFF086DDF285AULL, 0x7F9B6AF1EBF78BAFULL, 0x58627E1A149BBA21ULL, + 0x2CD16E2ABD791E33ULL, 0xD363EFF5F0977996ULL, 0x0CE2A38C344A6EEDULL, + 0x1A804AADB9CFA741ULL, 0x907F30421D78C5DEULL, 0x501F65EDB3034D07ULL, + 0x37624AE5A48FA6E9ULL, 0x957BAF61700CFF4EULL, 0x3A6C27934E31188AULL, + 0xD49503536ABCA345ULL, 0x088E049589C432E0ULL, 0xF943AEE7FEBF21B8ULL, + 0x6C3B8E3E336139D3ULL, 0x364F6FFA464EE52EULL, 0xD60F6DCEDC314222ULL, + 0x56963B0DCA418FC0ULL, 0x16F50EDF91E513AFULL, 0xEF1955914B609F93ULL, + 0x565601C0364E3228ULL, 0xECB53939887E8175ULL, 0xBAC7A9A18531294BULL, + 0xB344C470397BBA52ULL, 0x65D34954DAF3CEBDULL, 0xB4B81B3FA97511E2ULL, + 0xB422061193D6F6A7ULL, 0x071582401C38434DULL, 0x7A13F18BBEDC4FF5ULL, + 0xBC4097B116C524D2ULL, 0x59B97885E2F2EA28ULL, 0x99170A5DC3115544ULL, + 0x6F423357E7C6A9F9ULL, 0x325928EE6E6F8794ULL, 0xD0E4366228B03343ULL, + 0x565C31F7DE89EA27ULL, 0x30F5611484119414ULL, 0xD873DB391292ED4FULL, + 0x7BD94E1D8E17DEBCULL, 0xC7D9F16864A76E94ULL, 0x947AE053EE56E63CULL, + 0xC8C93882F9475F5FULL, 0x3A9BF55BA91F81CAULL, 0xD9A11FBB3D9808E4ULL, + 0x0FD22063EDC29FCAULL, 0xB3F256D8ACA0B0B9ULL, 0xB03031A8B4516E84ULL, + 0x35DD37D5871448AFULL, 0xE9F6082B05542E4EULL, 0xEBFAFA33D7254B59ULL, + 0x9255ABB50D532280ULL, 0xB9AB4CE57F2D34F3ULL, 0x693501D628297551ULL, + 0xC62C58F97DD949BFULL, 0xCD454F8F19C5126AULL, 0xBBE83F4ECC2BDECBULL, + 0xDC842B7E2819E230ULL, 0xBA89142E007503B8ULL, 0xA3BC941D0A5061CBULL, + 0xE9F6760E32CD8021ULL, 0x09C7E552BC76492FULL, 0x852F54934DA55CC9ULL, + 0x8107FCCF064FCF56ULL, 0x098954D51FFF6580ULL, 0x23B70EDB1955C4BFULL, + 0xC330DE426430F69DULL, 0x4715ED43E8A45C0AULL, 0xA8D7E4DAB780A08DULL, + 0x0572B974F03CE0BBULL, 0xB57D2E985E1419C7ULL, 0xE8D9ECBE2CF3D73FULL, + 0x2FE4B17170E59750ULL, 0x11317BA87905E790ULL, 0x7FBF21EC8A1F45ECULL, + 0x1725CABFCB045B00ULL, 0x964E915CD5E2B207ULL, 0x3E2B8BCBF016D66DULL, + 0xBE7444E39328A0ACULL, 0xF85B2B4FBCDE44B7ULL, 0x49353FEA39BA63B1ULL, + 0x1DD01AAFCD53486AULL, 0x1FCA8A92FD719F85ULL, 0xFC7C95D827357AFAULL, + 0x18A6A990C8B35EBDULL, 0xCCCB7005C6B9C28DULL, 0x3BDBB92C43B17F26ULL, + 0xAA70B5B4F89695A2ULL, 0xE94C39A54A98307FULL, 0xB7A0B174CFF6F36EULL, + 0xD4DBA84729AF48ADULL, 0x2E18BC1AD9704A68ULL, 0x2DE0966DAF2F8B1CULL, + 0xB9C11D5B1E43A07EULL, 0x64972D68DEE33360ULL, 0x94628D38D0C20584ULL, + 0xDBC0D2B6AB90A559ULL, 0xD2733C4335C6A72FULL, 0x7E75D99D94A70F4DULL, + 0x6CED1983376FA72BULL, 0x97FCAACBF030BC24ULL, 0x7B77497B32503B12ULL, + 0x8547EDDFB81CCB94ULL, 0x79999CDFF70902CBULL, 0xCFFE1939438E9B24ULL, + 0x829626E3892D95D7ULL, 0x92FAE24291F2B3F1ULL, 0x63E22C147B9C3403ULL, + 0xC678B6D860284A1CULL, 0x5873888850659AE7ULL, 0x0981DCD296A8736DULL, + 0x9F65789A6509A440ULL, 0x9FF38FED72E9052FULL, 0xE479EE5B9930578CULL, + 0xE7F28ECD2D49EECDULL, 0x56C074A581EA17FEULL, 0x5544F7D774B14AEFULL, + 0x7B3F0195FC6F290FULL, 0x12153635B2C0CF57ULL, 0x7F5126DBBA5E0CA7ULL, + 0x7A76956C3EAFB413ULL, 0x3D5774A11D31AB39ULL, 0x8A1B083821F40CB4ULL, + 0x7B4A38E32537DF62ULL, 0x950113646D1D6E03ULL, 0x4DA8979A0041E8A9ULL, + 0x3BC36E078F7515D7ULL, 0x5D0A12F27AD310D1ULL, 0x7F9D1A2E1EBE1327ULL, + 0xDA3A361B1C5157B1ULL, 0xDCDD7D20903D0C25ULL, 0x36833336D068F707ULL, + 0xCE68341F79893389ULL, 0xAB9090168DD05F34ULL, 0x43954B3252DC25E5ULL, + 0xB438C2B67F98E5E9ULL, 0x10DCD78E3851A492ULL, 0xDBC27AB5447822BFULL, + 0x9B3CDB65F82CA382ULL, 0xB67B7896167B4C84ULL, 0xBFCED1B0048EAC50ULL, + 0xA9119B60369FFEBDULL, 0x1FFF7AC80904BF45ULL, 0xAC12FB171817EEE7ULL, + 0xAF08DA9177DDA93DULL, 0x1B0CAB936E65C744ULL, 0xB559EB1D04E5E932ULL, + 0xC37B45B3F8D6F2BAULL, 0xC3A9DC228CAAC9E9ULL, 0xF3B8B6675A6507FFULL, + 0x9FC477DE4ED681DAULL, 0x67378D8ECCEF96CBULL, 0x6DD856D94D259236ULL, + 0xA319CE15B0B4DB31ULL, 0x073973751F12DD5EULL, 0x8A8E849EB32781A5ULL, + 0xE1925C71285279F5ULL, 0x74C04BF1790C0EFEULL, 0x4DDA48153C94938AULL, + 0x9D266D6A1CC0542CULL, 0x7440FB816508C4FEULL, 0x13328503DF48229FULL, + 0xD6BF7BAEE43CAC40ULL, 0x4838D65F6EF6748FULL, 0x1E152328F3318DEAULL, + 0x8F8419A348F296BFULL, 0x72C8834A5957B511ULL, 0xD7A023A73260B45CULL, + 0x94EBC8ABCFB56DAEULL, 0x9FC10D0F989993E0ULL, 0xDE68A2355B93CAE6ULL, + 0xA44CFE79AE538BBEULL, 0x9D1D84FCCE371425ULL, 0x51D2B1AB2DDFB636ULL, + 0x2FD7E4B9E72CD38CULL, 0x65CA5B96B7552210ULL, 0xDD69A0D8AB3B546DULL, + 0x604D51B25FBF70E2ULL, 0x73AA8A564FB7AC9EULL, 0x1A8C1E992B941148ULL, + 0xAAC40A2703D9BEA0ULL, 0x764DBEAE7FA4F3A6ULL, 0x1E99B96E70A9BE8BULL, + 0x2C5E9DEB57EF4743ULL, 0x3A938FEE32D29981ULL, 0x26E6DB8FFDF5ADFEULL, + 0x469356C504EC9F9DULL, 0xC8763C5B08D1908CULL, 0x3F6C6AF859D80055ULL, + 0x7F7CC39420A3A545ULL, 0x9BFB227EBDF4C5CEULL, 0x89039D79D6FC5C5CULL, + 0x8FE88B57305E2AB6ULL, 0xA09E8C8C35AB96DEULL, 0xFA7E393983325753ULL, + 0xD6B6D0ECC617C699ULL, 0xDFEA21EA9E7557E3ULL, 0xB67C1FA481680AF8ULL, + 0xCA1E3785A9E724E5ULL, 0x1CFC8BED0D681639ULL, 0xD18D8549D140CAEAULL, + 0x4ED0FE7E9DC91335ULL, 0xE4DBF0634473F5D2ULL, 0x1761F93A44D5AEFEULL, + 0x53898E4C3910DA55ULL, 0x734DE8181F6EC39AULL, 0x2680B122BAA28D97ULL, + 0x298AF231C85BAFABULL, 0x7983EED3740847D5ULL, 0x66C1A2A1A60CD889ULL, + 0x9E17E49642A3E4C1ULL, 0xEDB454E7BADC0805ULL, 0x50B704CAB602C329ULL, + 0x4CC317FB9CDDD023ULL, 0x66B4835D9EAFEA22ULL, 0x219B97E26FFC81BDULL, + 0x261E4E4C0A333A9DULL, 0x1FE2CCA76517DB90ULL, 0xD7504DFA8816EDBBULL, + 0xB9571FA04DC089C8ULL, 0x1DDC0325259B27DEULL, 0xCF3F4688801EB9AAULL, + 0xF4F5D05C10CAB243ULL, 0x38B6525C21A42B0EULL, 0x36F60E2BA4FA6800ULL, + 0xEB3593803173E0CEULL, 0x9C4CD6257C5A3603ULL, 0xAF0C317D32ADAA8AULL, + 0x258E5A80C7204C4BULL, 0x8B889D624D44885DULL, 0xF4D14597E660F855ULL, + 0xD4347F66EC8941C3ULL, 0xE699ED85B0DFB40DULL, 0x2472F6207C2D0484ULL, + 0xC2A1E7B5B459AEB5ULL, 0xAB4F6451CC1D45ECULL, 0x63767572AE3D6174ULL, + 0xA59E0BD101731A28ULL, 0x116D0016CB948F09ULL, 0x2CF9C8CA052F6E9FULL, + 0x0B090A7560A968E3ULL, 0xABEEDDB2DDE06FF1ULL, 0x58EFC10B06A2068DULL, + 0xC6E57A78FBD986E0ULL, 0x2EAB8CA63CE802D7ULL, 0x14A195640116F336ULL, + 0x7C0828DD624EC390ULL, 0xD74BBE77E6116AC7ULL, 0x804456AF10F5FB53ULL, + 0xEBE9EA2ADF4321C7ULL, 0x03219A39EE587A30ULL, 0x49787FEF17AF9924ULL, + 0xA1E9300CD8520548ULL, 0x5B45E522E4B1B4EFULL, 0xB49C3B3995091A36ULL, + 0xD4490AD526F14431ULL, 0x12A8F216AF9418C2ULL, 0x001F837CC7350524ULL, + 0x1877B51E57A764D5ULL, 0xA2853B80F17F58EEULL, 0x993E1DE72D36D310ULL, + 0xB3598080CE64A656ULL, 0x252F59CF0D9F04BBULL, 0xD23C8E176D113600ULL, + 0x1BDA0492E7E4586EULL, 0x21E0BD5026C619BFULL, 0x3B097ADAF088F94EULL, + 0x8D14DEDB30BE846EULL, 0xF95CFFA23AF5F6F4ULL, 0x3871700761B3F743ULL, + 0xCA672B91E9E4FA16ULL, 0x64C8E531BFF53B55ULL, 0x241260ED4AD1E87DULL, + 0x106C09B972D2E822ULL, 0x7FBA195410E5CA30ULL, 0x7884D9BC6CB569D8ULL, + 0x0647DFEDCD894A29ULL, 0x63573FF03E224774ULL, 0x4FC8E9560F91B123ULL, + 0x1DB956E450275779ULL, 0xB8D91274B9E9D4FBULL, 0xA2EBEE47E2FBFCE1ULL, + 0xD9F1F30CCD97FB09ULL, 0xEFED53D75FD64E6BULL, 0x2E6D02C36017F67FULL, + 0xA9AA4D20DB084E9BULL, 0xB64BE8D8B25396C1ULL, 0x70CB6AF7C2D5BCF0ULL, + 0x98F076A4F7A2322EULL, 0xBF84470805E69B5FULL, 0x94C3251F06F90CF3ULL, + 0x3E003E616A6591E9ULL, 0xB925A6CD0421AFF3ULL, 0x61BDD1307C66E300ULL, + 0xBF8D5108E27E0D48ULL, 0x240AB57A8B888B20ULL, 0xFC87614BAF287E07ULL, + 0xEF02CDD06FFDB432ULL, 0xA1082C0466DF6C0AULL, 0x8215E577001332C8ULL, + 0xD39BB9C3A48DB6CFULL, 0x2738259634305C14ULL, 0x61CF4F94C97DF93DULL, + 0x1B6BACA2AE4E125BULL, 0x758F450C88572E0BULL, 0x959F587D507A8359ULL, + 0xB063E962E045F54DULL, 0x60E8ED72C0DFF5D1ULL, 0x7B64978555326F9FULL, + 0xFD080D236DA814BAULL, 0x8C90FD9B083F4558ULL, 0x106F72FE81E2C590ULL, + 0x7976033A39F7D952ULL, 0xA4EC0132764CA04BULL, 0x733EA705FAE4FA77ULL, + 0xB4D8F77BC3E56167ULL, 0x9E21F4F903B33FD9ULL, 0x9D765E419FB69F6DULL, + 0xD30C088BA61EA5EFULL, 0x5D94337FBFAF7F5BULL, 0x1A4E4822EB4D7A59ULL, + 0x6FFE73E81B637FB3ULL, 0xDDF957BC36D8B9CAULL, 0x64D0E29EEA8838B3ULL, + 0x08DD9BDFD96B9F63ULL, 0x087E79E5A57D1D13ULL, 0xE328E230E3E2B3FBULL, + 0x1C2559E30F0946BEULL, 0x720BF5F26F4D2EAAULL, 0xB0774D261CC609DBULL, + 0x443F64EC5A371195ULL, 0x4112CF68649A260EULL, 0xD813F2FAB7F5C5CAULL, + 0x660D3257380841EEULL, 0x59AC2C7873F910A3ULL, 0xE846963877671A17ULL, + 0x93B633ABFA3469F8ULL, 0xC0C0F5A60EF4CDCFULL, 0xCAF21ECD4377B28CULL, + 0x57277707199B8175ULL, 0x506C11B9D90E8B1DULL, 0xD83CC2687A19255FULL, + 0x4A29C6465A314CD1ULL, 0xED2DF21216235097ULL, 0xB5635C95FF7296E2ULL, + 0x22AF003AB672E811ULL, 0x52E762596BF68235ULL, 0x9AEBA33AC6ECC6B0ULL, + 0x944F6DE09134DFB6ULL, 0x6C47BEC883A7DE39ULL, 0x6AD047C430A12104ULL, + 0xA5B1CFDBA0AB4067ULL, 0x7C45D833AFF07862ULL, 0x5092EF950A16DA0BULL, + 0x9338E69C052B8E7BULL, 0x455A4B4CFE30E3F5ULL, 0x6B02E63195AD0CF8ULL, + 0x6B17B224BAD6BF27ULL, 0xD1E0CCD25BB9C169ULL, 0xDE0C89A556B9AE70ULL, + 0x50065E535A213CF6ULL, 0x9C1169FA2777B874ULL, 0x78EDEFD694AF1EEDULL, + 0x6DC93D9526A50E68ULL, 0xEE97F453F06791EDULL, 0x32AB0EDB696703D3ULL, + 0x3A6853C7E70757A7ULL, 0x31865CED6120F37DULL, 0x67FEF95D92607890ULL, + 0x1F2B1D1F15F6DC9CULL, 0xB69E38A8965C6B65ULL, 0xAA9119FF184CCCF4ULL, + 0xF43C732873F24C13ULL, 0xFB4A3D794A9A80D2ULL, 0x3550C2321FD6109CULL, + 0x371F77E76BB8417EULL, 0x6BFA9AAE5EC05779ULL, 0xCD04F3FF001A4778ULL, + 0xE3273522064480CAULL, 0x9F91508BFFCFC14AULL, 0x049A7F41061A9E60ULL, + 0xFCB6BE43A9F2FE9BULL, 0x08DE8A1C7797DA9BULL, 0x8F9887E6078735A1ULL, + 0xB5B4071DBFC73A66ULL, 0x230E343DFBA08D33ULL, 0x43ED7F5A0FAE657DULL, + 0x3A88A0FBBCB05C63ULL, 0x21874B8B4D2DBC4FULL, 0x1BDEA12E35F6A8C9ULL, + 0x53C065C6C8E63528ULL, 0xE34A1D250E7A8D6BULL, 0xD6B04D3B7651DD7EULL, + 0x5E90277E7CB39E2DULL, 0x2C046F22062DC67DULL, 0xB10BB459132D0A26ULL, + 0x3FA9DDFB67E2F199ULL, 0x0E09B88E1914F7AFULL, 0x10E8B35AF3EEAB37ULL, + 0x9EEDECA8E272B933ULL, 0xD4C718BC4AE8AE5FULL, 0x81536D601170FC20ULL, + 0x91B534F885818A06ULL, 0xEC8177F83F900978ULL, 0x190E714FADA5156EULL, + 0xB592BF39B0364963ULL, 0x89C350C893AE7DC1ULL, 0xAC042E70F8B383F2ULL, + 0xB49B52E587A1EE60ULL, 0xFB152FE3FF26DA89ULL, 0x3E666E6F69AE2C15ULL, + 0x3B544EBE544C19F9ULL, 0xE805A1E290CF2456ULL, 0x24B33C9D7ED25117ULL, + 0xE74733427B72F0C1ULL, 0x0A804D18B7097475ULL, 0x57E3306D881EDB4FULL, + 0x4AE7D6A36EB5DBCBULL, 0x2D8D5432157064C8ULL, 0xD1E649DE1E7F268BULL, + 0x8A328A1CEDFE552CULL, 0x07A3AEC79624C7DAULL, 0x84547DDC3E203C94ULL, + 0x990A98FD5071D263ULL, 0x1A4FF12616EEFC89ULL, 0xF6F7FD1431714200ULL, + 0x30C05B1BA332F41CULL, 0x8D2636B81555A786ULL, 0x46C9FEB55D120902ULL, + 0xCCEC0A73B49C9921ULL, 0x4E9D2827355FC492ULL, 0x19EBB029435DCB0FULL, + 0x4659D2B743848A2CULL, 0x963EF2C96B33BE31ULL, 0x74F85198B05A2E7DULL, + 0x5A0F544DD2B1FB18ULL, 0x03727073C2E134B1ULL, 0xC7F6AA2DE59AEA61ULL, + 0x352787BAA0D7C22FULL, 0x9853EAB63B5E0B35ULL, 0xABBDCDD7ED5C0860ULL, + 0xCF05DAF5AC8D77B0ULL, 0x49CAD48CEBF4A71EULL, 0x7A4C10EC2158C4A6ULL, + 0xD9E92AA246BF719EULL, 0x13AE978D09FE5557ULL, 0x730499AF921549FFULL, + 0x4E4B705B92903BA4ULL, 0xFF577222C14F0A3AULL, 0x55B6344CF97AAFAEULL, + 0xB862225B055B6960ULL, 0xCAC09AFBDDD2CDB4ULL, 0xDAF8E9829FE96B5FULL, + 0xB5FDFC5D3132C498ULL, 0x310CB380DB6F7503ULL, 0xE87FBB46217A360EULL, + 0x2102AE466EBB1148ULL, 0xF8549E1A3AA5E00DULL, 0x07A69AFDCC42261AULL, + 0xC4C118BFE78FEAAEULL, 0xF9F4892ED96BD438ULL, 0x1AF3DBE25D8F45DAULL, + 0xF5B4B0B0D2DEEEB4ULL, 0x962ACEEFA82E1C84ULL, 0x046E3ECAAF453CE9ULL, + 0xF05D129681949A4CULL, 0x964781CE734B3C84ULL, 0x9C2ED44081CE5FBDULL, + 0x522E23F3925E319EULL, 0x177E00F9FC32F791ULL, 0x2BC60A63A6F3B3F2ULL, + 0x222BBFAE61725606ULL, 0x486289DDCC3D6780ULL, 0x7DC7785B8EFDFC80ULL, + 0x8AF38731C02BA980ULL, 0x1FAB64EA29A2DDF7ULL, 0xE4D9429322CD065AULL, + 0x9DA058C67844F20CULL, 0x24C0E332B70019B0ULL, 0x233003B5A6CFE6ADULL, + 0xD586BD01C5C217F6ULL, 0x5E5637885F29BC2BULL, 0x7EBA726D8C94094BULL, + 0x0A56A5F0BFE39272ULL, 0xD79476A84EE20D06ULL, 0x9E4C1269BAA4BF37ULL, + 0x17EFEE45B0DEE640ULL, 0x1D95B0A5FCF90BC6ULL, 0x93CBE0B699C2585DULL, + 0x65FA4F227A2B6D79ULL, 0xD5F9E858292504D5ULL, 0xC2B5A03F71471A6FULL, + 0x59300222B4561E00ULL, 0xCE2F8642CA0712DCULL, 0x7CA9723FBB2E8988ULL, + 0x2785338347F2BA08ULL, 0xC61BB3A141E50E8CULL, 0x150F361DAB9DEC26ULL, + 0x9F6A419D382595F4ULL, 0x64A53DC924FE7AC9ULL, 0x142DE49FFF7A7C3DULL, + 0x0C335248857FA9E7ULL, 0x0A9C32D5EAE45305ULL, 0xE6C42178C4BBB92EULL, + 0x71F1CE2490D20B07ULL, 0xF1BCC3D275AFE51AULL, 0xE728E8C83C334074ULL, + 0x96FBF83A12884624ULL, 0x81A1549FD6573DA5ULL, 0x5FA7867CAF35E149ULL, + 0x56986E2EF3ED091BULL, 0x917F1DD5F8886C61ULL, 0xD20D8C88C8FFE65FULL, + 0x31D71DCE64B2C310ULL, 0xF165B587DF898190ULL, 0xA57E6339DD2CF3A0ULL, + 0x1EF6E6DBB1961EC9ULL, 0x70CC73D90BC26E24ULL, 0xE21A6B35DF0C3AD7ULL, + 0x003A93D8B2806962ULL, 0x1C99DED33CB890A1ULL, 0xCF3145DE0ADD4289ULL, + 0xD0E4427A5514FB72ULL, 0x77C621CC9FB3A483ULL, 0x67A34DAC4356550BULL, + 0xF8D626AAAF278509ULL + } }; + + +PolyBook::PolyBook() +{ + keycount = 0; + polyhash = NULL; + + use_best_book_move = true; + max_book_depth = 255; + book_depth_count = 0; + + last_position = 0; + akt_position = 0; + last_anz_pieces = 0; + akt_anz_pieces = 0; + search_counter = 0; + + do_search = true; + enabled = false; +} + + +PolyBook::~PolyBook() +{ + if (polyhash != NULL) + delete[]polyhash; +} + + +void PolyBook::init(const std::string& bookfile) +{ + if (bookfile.length() == 0) return; + const char *fnam = bookfile.c_str(); + + if (strcmp(fnam, "") == 0) + { + enabled = false; + return; + } + + FILE *fpt = fopen(fnam, "rb"); + if (fpt == NULL) + { + sync_cout << "info string Could not open " << bookfile << sync_endl; + enabled = false; + return; + } + + if (polyhash != NULL) + { + free(polyhash); + polyhash = NULL; + } + + fseek(fpt, 0L, SEEK_END); + int filesize = ftell(fpt); + fseek(fpt, 0L, SEEK_SET); + + keycount = filesize / 16; + polyhash = new PolyHash[keycount]; + + fread(polyhash, 1, filesize, fpt); + fclose(fpt); + + for (int i = 0; i= max_book_depth) + return m1; + + Key key = polyglot_key(pos); + + int n = find_first_key(key); + + if (n < 1) + { + search_counter++; + if (search_counter > 4) + { + // stop searching after 4 times not in the book till position changes + // according to check_do_search() + do_search = false; + search_counter = 0; + book_depth_count = 0; + } + + return m1; + } + + book_depth_count++; + + int idx1; + if (use_best_book_move) + idx1 = index_best; + else + idx1 = index_rand; + + m1 = pg_move_to_sf_move(pos, polyhash[idx1].move); + + if (!pos.is_draw(64)) return m1; + if (n == 1) return m1; + + // special case draw position and 2 moves available + + if (!check_draw(m1, pos)) + return m1; + + int idx2 = index_first; + if (idx1 == idx2) + idx2 = index_first + 1; + Move m2 = pg_move_to_sf_move(pos, polyhash[idx2].move); + + if (!check_draw(m2, pos)) + return m2; + + return MOVE_NONE; +} + + +Key PolyBook::polyglot_key(const Position & pos) +{ + Key key = 0; + Bitboard b = pos.pieces(); + + while (b) + { + Square s = pop_lsb(&b); + Piece p = pos.piece_on(s); + + // PolyGlot pieces are: BP = 0, WP = 1, BN = 2, ... BK = 10, WK = 11 + key ^= PG.Zobrist.psq[2 * (type_of(p) - 1) + (color_of(p) == WHITE)][s]; + } + + b = pos.can_castle(ANY_CASTLING); + + while (b) + key ^= PG.Zobrist.castle[pop_lsb(&b)]; + + if (pos.ep_square() != SQ_NONE) + key ^= PG.Zobrist.enpassant[file_of(pos.ep_square())]; + + if (pos.side_to_move() == WHITE) + key ^= PG.Zobrist.turn; + + return key; +} + +// A PolyGlot book move is encoded as follows: +// +// bit 0- 5: destination square (from 0 to 63) +// bit 6-11: origin square (from 0 to 63) +// bit 12-14: promotion piece (from KNIGHT == 1 to QUEEN == 4) +// +// Castling moves follow "king captures rook" representation. So in case book +// move is a promotion we have to convert to our representation, in all the +// other cases we can directly compare with a Move after having masked out +// the special Move's flags (bit 14-15) that are not supported by PolyGlot. +// +// SF: +// bit 0- 5: destination square (from 0 to 63) +// bit 6-11: origin square (from 0 to 63) +// bit 12-13: promotion piece type - 2 (from KNIGHT-2 to QUEEN-2) +// bit 14-15: special move flag: promotion (1), en passant (2), castling (3) +Move PolyBook::pg_move_to_sf_move(const Position & pos, unsigned short pg_move) +{ + Move move = Move(pg_move); + + int pt = (move >> 12) & 7; + if (pt) + return make(from_sq(move), to_sq(move), PieceType(pt + 1)); + + // Add 'special move' flags and verify it is legal + for (const auto& m : MoveList(pos)) + { + if (move == (m.move & (~(3 << 14)))) // compare with MoveType (bit 14-15) masked out + return m; + } + + return MOVE_NONE; +} + + +int PolyBook::find_first_key(uint64_t key) +{ + index_first = -1; + index_count = 0; + index_weight_count = 0; + index_best = -1; + index_rand = -1; + + int start = 0; + int end = keycount; + + for (;;) + { + int mid = (end + start) / 2; + + if (polyhash[mid].key < key) + start = mid; + else + { + if (polyhash[mid].key > key) + end = mid; + else + { + start = max(mid - 4, 0); + end = min(mid + 4, keycount); + } + } + + if (end - start < 9) + break; + } + + for (int i = start; i < end; i++) + { + if (key == polyhash[i].key) + { + index_first = i; + while ((index_first>0) && (key == polyhash[index_first - 1].key)) + index_first--; + return get_key_data(); + } + } + + return -1; +} + + +int PolyBook::get_key_data() +{ + int best_weight = polyhash[index_first].weight; + index_weight_count = best_weight; + uint64_t key = polyhash[index_first].key; + + index_count = 1; + index_best = index_first; + + for (int i = index_first + 1; i best_weight) + { + best_weight = polyhash[i].weight; + index_best = i; + } + } + + int rand_pos = (rand64() % index_weight_count); + int weight_count = 0; + index_rand = index_best; + + for (int i = index_first; i < index_first + index_count; i++) + { + if ((rand_pos >= weight_count) && (rand_pos < weight_count + polyhash[i].weight)) + { + index_rand = i; + break; + } + weight_count += polyhash[i].weight; + } + + return index_count; +} + + +bool PolyBook::check_do_search(const Position & pos) +{ + akt_position = pos.pieces(); + akt_anz_pieces = popcount(akt_position); + + bool pos_changed = false; + + Bitboard b = akt_position ^ last_position; + int n2 = popcount(b); + + if (n2 > 6) pos_changed = true; + if (akt_position == last_position) pos_changed = true; + if (akt_anz_pieces > last_anz_pieces) pos_changed = true; + if (akt_anz_pieces < last_anz_pieces - 2) pos_changed = true; + if (pos.key() == 0xB4D30CD15A43432D) pos_changed = true; + + // reset do_search and book depth counter if + // postion changed more than one move can do or in initial position + if (pos_changed) + { + book_depth_count = 0; + do_search = true; + } + + last_position = akt_position; + last_anz_pieces = akt_anz_pieces; + + return do_search; +} + + +bool PolyBook::check_draw(Move m, Position & pos) +{ + StateInfo st; + + pos.do_move(m, st, pos.gives_check(m)); + bool draw = pos.is_draw(64); + pos.undo_move(m); + + return draw; +} + + +void PolyBook::byteswap_polyhash(PolyHash *ph) +{ + if (is_little_endian()) + { + ph->key = swap_uint64(ph->key); + ph->move = swap_uint16(ph->move); + ph->weight = swap_uint16(ph->weight); + ph->learn = swap_uint32(ph->learn); + } +} + + +uint64_t PolyBook::rand64() +{ + sr ^= sr >> 12, sr ^= sr << 25, sr ^= sr >> 27; + return sr * 2685821657736338717LL; +} + + +bool PolyBook::is_little_endian() +{ + int num = 1; + return (*(uint8_t *)&num == 1); +} + + +uint64_t PolyBook::swap_uint64(uint64_t d) +{ + uint64_t a; + uint8_t *dst = (uint8_t *)&a; + uint8_t *src = (uint8_t *)&d; + + dst[0] = src[7]; + dst[1] = src[6]; + dst[2] = src[5]; + dst[3] = src[4]; + dst[4] = src[3]; + dst[5] = src[2]; + dst[6] = src[1]; + dst[7] = src[0]; + + return a; +} + + +uint32_t PolyBook::swap_uint32(uint32_t d) +{ + uint32_t a; + uint8_t *dst = (uint8_t *)&a; + uint8_t *src = (uint8_t *)&d; + + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; + + return a; +} + + +uint16_t PolyBook::swap_uint16(uint16_t d) +{ + uint16_t a; + uint8_t *dst = (uint8_t *)&a; + uint8_t *src = (uint8_t *)&d; + + dst[0] = src[1]; + dst[1] = src[0]; + + return a; +} + diff --git a/Engines/Linux32/mcbrain/src/polybook.h b/Engines/Linux32/mcbrain/src/polybook.h new file mode 100644 index 0000000..8696eda --- /dev/null +++ b/Engines/Linux32/mcbrain/src/polybook.h @@ -0,0 +1,113 @@ +/* + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +//polybook.h was wriiten by Thomas Zipproth +/* +polybook.h was written by Thomas Zipproth +BrainFish, a UCI chess playing engine derived from Stockfish +Copyright (C) 2016-2018 Thomas Zipproth + +BrainFish is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +BrainFish is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef POLYBOOK_H_INCLUDED +#define POLYBOOK_H_INCLUDED + +#include "bitboard.h" +#include "position.h" +#include "string.h" + +typedef struct { + uint64_t key; + uint16_t move; + uint16_t weight; + uint32_t learn; +} PolyHash; + +class PolyBook +{ +public: + + PolyBook(); + ~PolyBook(); + + void init(const std::string& bookfile); + void set_best_book_move(bool best_book_move); + void set_book_depth(int book_depth); + + Move probe(Position& pos); + +private: + + Key polyglot_key(const Position& pos); + Move pg_move_to_sf_move(const Position & pos, unsigned short pg_move); + + int find_first_key(uint64_t key); + int get_key_data(); + + bool check_do_search(const Position & pos); + bool check_draw(Move m, Position& pos); + + void byteswap_polyhash(PolyHash *ph); + uint64_t rand64(); + + bool is_little_endian(); + uint64_t swap_uint64(uint64_t d); + uint32_t swap_uint32(uint32_t d); + uint16_t swap_uint16(uint16_t d); + + int keycount; + PolyHash *polyhash; + + bool use_best_book_move; + int max_book_depth; + int book_depth_count; + + int index_first; + int index_count; + int index_best; + int index_rand; + int index_weight_count; + + uint64_t sr; + + Bitboard last_position; + Bitboard akt_position; + int last_anz_pieces; + int akt_anz_pieces; + int search_counter; + + bool enabled, do_search; +}; + +extern PolyBook polybook; + +#endif // #ifndef POLYBOOK_H_INCLUDED diff --git a/Engines/Linux32/mcbrain/src/position.cpp b/Engines/Linux32/mcbrain/src/position.cpp index aa43740..acef66b 100644 --- a/Engines/Linux32/mcbrain/src/position.cpp +++ b/Engines/Linux32/mcbrain/src/position.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include #include @@ -211,10 +212,10 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th while ((ss >> token) && !isspace(token)) { if (isdigit(token)) - sq += Square(token - '0'); // Advance the given number of files + sq += (token - '0') * EAST; // Advance the given number of files else if (token == '/') - sq -= Square(16); + sq += 2 * SOUTH; else if ((idx = PieceToChar.find(token)) != string::npos) { @@ -272,7 +273,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th // 5-6. Halfmove clock and fullmove number ss >> std::skipws >> st->rule50 >> gamePly; - // Convert from fullmove starting from 1 to ply starting from 0, + // Convert from fullmove starting from 1 to gamePly starting from 0, // handle also common incorrect FEN with fullmove = 0. gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); @@ -787,7 +788,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if ( (int(to) ^ int(from)) == 16 && (attacks_from(to - pawn_push(us), us) & pieces(them, PAWN))) { - st->epSquare = (from + to) / 2; + st->epSquare = to - pawn_push(us); k ^= Zobrist::enpassant[file_of(st->epSquare)]; } @@ -1003,22 +1004,22 @@ bool Position::see_ge(Move m, Value threshold) const { Value balance; // Values of the pieces taken by us minus opponent's ones Bitboard occupied, stmAttackers; - balance = PieceValue[MG][piece_on(to)]; - occupied = 0; + // The opponent may be able to recapture so this is the best result + // we can hope for. + balance = PieceValue[MG][piece_on(to)] - threshold; - if (balance < threshold) + if (balance < VALUE_ZERO) return false; - if (nextVictim == KING) - return true; - + // Now assume the worst possible result: that the opponent can + // capture our piece for free. balance -= PieceValue[MG][nextVictim]; - if (balance >= threshold) + if (balance >= VALUE_ZERO) // Always true if nextVictim == KING return true; - bool relativeStm = true; // True if the opponent is to move - occupied ^= pieces() ^ from ^ to; + bool opponentToMove = true; + occupied = pieces() ^ from ^ to; // Find all attackers to the destination square, with the moving piece removed, // but possibly an X-ray attacker added behind it. @@ -1026,6 +1027,12 @@ bool Position::see_ge(Move m, Value threshold) const { while (true) { + // The balance is negative only because we assumed we could win + // the last piece for free. We are truly winning only if we can + // win the last piece _cheaply enough_. Test if we can actually + // do this otherwise "give up". + assert(balance < VALUE_ZERO); + stmAttackers = attackers & pieces(stm); // Don't allow pinned pieces to attack pieces except the king as long all @@ -1033,25 +1040,40 @@ bool Position::see_ge(Move m, Value threshold) const { if (!(st->pinnersForKing[stm] & ~occupied)) stmAttackers &= ~st->blockersForKing[stm]; + // If we have no more attackers we must give up if (!stmAttackers) - return relativeStm; + break; // Locate and remove the next least valuable attacker nextVictim = min_attacker(byTypeBB, to, stmAttackers, occupied, attackers); if (nextVictim == KING) - return relativeStm == bool(attackers & pieces(~stm)); - - balance += relativeStm ? PieceValue[MG][nextVictim] - : -PieceValue[MG][nextVictim]; + { + // Our only attacker is the king. If the opponent still has + // attackers we must give up. Otherwise we make the move and + // (having no more attackers) the opponent must give up. + if (!(attackers & pieces(~stm))) + opponentToMove = !opponentToMove; + break; + } - relativeStm = !relativeStm; + // Assume the opponent can win the next piece for free and switch sides + balance += PieceValue[MG][nextVictim]; + opponentToMove = !opponentToMove; - if (relativeStm == (balance >= threshold)) - return relativeStm; + // If balance is negative after receiving a free piece then give up + if (balance < VALUE_ZERO) + break; + // Complete the process of switching sides. The first line swaps + // all negative numbers with non-negative numbers. The compiler + // probably knows that it is just the bitwise negation ~balance. + balance = -balance-1; stm = ~stm; } + + // If the opponent gave up we win, otherwise we lose. + return opponentToMove; } @@ -1075,18 +1097,16 @@ bool Position::is_draw(int ply) const { { stp = stp->previous->previous; - // At root position ply is 1, so return a draw score if a position - // repeats once earlier but strictly after the root, or repeats twice - // before or at the root. + // Return a draw score if a position repeats once earlier but strictly + // after the root, or repeats twice before or at the root. if ( stp->key == st->key - && ++cnt + (ply - 1 > i) == 2) + && ++cnt + (ply > i) == 2) return true; } return false; } - /// Position::flip() flips position with the white and black sides reversed. This /// is only useful for debugging e.g. for finding evaluation symmetry bugs. diff --git a/Engines/Linux32/mcbrain/src/position.h b/Engines/Linux32/mcbrain/src/position.h index fa812ef..c809549 100644 --- a/Engines/Linux32/mcbrain/src/position.h +++ b/Engines/Linux32/mcbrain/src/position.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef POSITION_H_INCLUDED #define POSITION_H_INCLUDED diff --git a/Engines/Linux32/mcbrain/src/psqt.cpp b/Engines/Linux32/mcbrain/src/psqt.cpp index 0d16b1c..55a2e72 100644 --- a/Engines/Linux32/mcbrain/src/psqt.cpp +++ b/Engines/Linux32/mcbrain/src/psqt.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include @@ -116,7 +117,7 @@ void init() { for (Square s = SQ_A1; s <= SQ_H8; ++s) { - File f = std::min(file_of(s), FILE_H - file_of(s)); + File f = std::min(file_of(s), ~file_of(s)); psq[ pc][ s] = v + Bonus[pc][rank_of(s)][f]; psq[~pc][~s] = -psq[pc][s]; } diff --git a/Engines/Linux32/mcbrain/src/search.cpp b/Engines/Linux32/mcbrain/src/search.cpp index 7805768..92753c0 100644 --- a/Engines/Linux32/mcbrain/src/search.cpp +++ b/Engines/Linux32/mcbrain/src/search.cpp @@ -1,35 +1,38 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include #include #include #include // For std::memset +#include //for sleep //MichaelB7 #include #include -#include +#include // ELO MichaelB7 #include "evaluate.h" #include "misc.h" #include "movegen.h" #include "movepick.h" +#include "polybook.h" // Cerebellum #include "position.h" #include "search.h" #include "timeman.h" @@ -37,7 +40,6 @@ #include "tt.h" #include "uci.h" #include "tbprobe.h" -#include "tzbook.h" namespace Search { @@ -69,19 +71,16 @@ namespace { const int skipPhase[] = { 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7 }; // Razoring and futility margin based on depth - // razor_margin[0] is unused as long as depth >= ONE_PLY in search - const int razor_margin[] = { 0, 570, 603, 554 }; - Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); } + const int razor_margin = 600; + //Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); } + Value futility_margin(Depth d) { return Value(16 * d^2 / ONE_PLY + 200 * d / ONE_PLY);} // idea from JD Watson, code modfied by MichaelB7 // Futility and reductions lookup tables, initialized at startup int FutilityMoveCounts[2][16]; // [improving][depth] - int Reductions[2][2][64][64]; // [pv][improving][depth][moveNumber] - - // Threshold used for countermoves based pruning - const int CounterMovePruneThreshold = 0; + int Reductions[2][2][128][96]; // [pv][improving][depth][moveNumber] //MichaelB7 template Depth reduction(bool i, Depth d, int mn) { - return Reductions[PvNode][i][std::min(d / ONE_PLY, 63)][std::min(mn, 63)] * ONE_PLY; + return Reductions[PvNode][i][std::min(d / ONE_PLY, 127)][std::min(mn, 95)] * ONE_PLY; //MichaelB7 } // History and stats update bonus, based on depth @@ -92,61 +91,18 @@ namespace { // Skill structure is used to implement strength limit struct Skill { - Skill(int l) : level(l) {} + explicit Skill(int l) : level(l) {} bool enabled() const { return level < 20; } bool time_to_pick(Depth depth) const { return depth / ONE_PLY == 1 + level; } - Move best_move(size_t multiPV) { return best ? best : pick_best(multiPV); } Move pick_best(size_t multiPV); int level; Move best = MOVE_NONE; }; - - // EasyMoveManager structure is used to detect an 'easy move'. When the PV is stable - // across multiple search iterations, we can quickly return the best move. - struct EasyMoveManager { - - void clear() { - stableCnt = 0; - expectedPosKey = 0; - pv[0] = pv[1] = pv[2] = MOVE_NONE; - } - - Move get(Key key) const { - return expectedPosKey == key ? pv[2] : MOVE_NONE; - } - - void update(Position& pos, const std::vector& newPv) { - - assert(newPv.size() >= 3); - - // Keep track of how many times in a row the 3rd ply remains stable - stableCnt = (newPv[2] == pv[2]) ? stableCnt + 1 : 0; - - if (!std::equal(newPv.begin(), newPv.begin() + 3, pv)) - { - std::copy(newPv.begin(), newPv.begin() + 3, pv); - - StateInfo st[2]; - pos.do_move(newPv[0], st[0]); - pos.do_move(newPv[1], st[1]); - expectedPosKey = pos.key(); - pos.undo_move(newPv[1]); - pos.undo_move(newPv[0]); - } - } - - Key expectedPosKey; - int stableCnt; - Move pv[3]; - }; - - EasyMoveManager EasyMove; - Value DrawValue[COLOR_NB]; int tactical; - bool bruteForce, limitStrength, noNULL; + bool bookEnabled, bruteForce, limitStrength, noNULL; template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning); @@ -159,6 +115,8 @@ namespace { void update_pv(Move* pv, Move move, Move* childPv); void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); void update_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietsCnt, int bonus); + void update_capture_stats(const Position& pos, Move move, Move* captures, int captureCnt, int bonus); + bool pv_is_draw(Position& pos); // perft() is our utility to verify move generation. All the leaf nodes up // to the given depth are generated and counted, and the sum is returned. @@ -194,10 +152,10 @@ namespace { void Search::init() { for (int imp = 0; imp <= 1; ++imp) - for (int d = 1; d < 64; ++d) - for (int mc = 1; mc < 64; ++mc) + for (int d = 1; d < 128; ++d) // MichaelB7 + for (int mc = 1; mc < 96; ++mc) // MichaelB7 { - double r = 0.2 * d * (1.0 - exp(-9.0 / d)) * log(mc); + double r = log(d) * log(mc) / 1.89 ; Reductions[NonPV][imp][d][mc] = int(std::round(r)); Reductions[PV][imp][d][mc] = std::max(Reductions[NonPV][imp][d][mc] - 1, 0); @@ -223,21 +181,7 @@ void Search::clear() { Time.availableNodes = 0; TT.clear(); - - for (Thread* th : Threads) - { - th->counterMoves.fill(MOVE_NONE); - th->mainHistory.fill(0); - - for (auto& to : th->contHistory) - for (auto& h : to) - h.fill(0); - - th->contHistory[NO_PIECE][0].fill(CounterMovePruneThreshold - 1); - } - - Threads.main()->callsCnt = 0; - Threads.main()->previousScore = VALUE_INFINITE; + Threads.clear(); } @@ -257,20 +201,24 @@ void MainThread::search() { limitStrength = Options["UCI_LimitStrength"]; noNULL = Options["No_Null_Moves"]; tactical = Options["Tactical"]; + bookEnabled = Options["Book_Enabled"]; Color us = rootPos.side_to_move(); Time.init(Limits, us, rootPos.game_ply()); TT.new_search(); - if (Options["Respect"]){ - int respect = Options["Respect"] * PawnValueEg / 100; // From centipawns - DrawValue[ us] = VALUE_DRAW + Value(respect); - DrawValue[~us] = VALUE_DRAW - Value(respect);} - - else if (Options["Respect White POV"]) { - int respectPOV = Options["Respect White POV"] * PawnValueEg / 100; // From centipawns - DrawValue[WHITE] = VALUE_DRAW + Value(respectPOV); - DrawValue[BLACK] = VALUE_DRAW - Value(respectPOV);} + int contempt = Options["Contempt"] * PawnValueEg / 100; // From centipawns + + // In analysis mode, adjust contempt in accordance with user preference + if (Limits.infinite || Options["UCI_AnalyseMode"]) + contempt = Options["Analysis Contempt"] == "Off" ? 0 + : Options["Analysis Contempt"] == "White" && us == BLACK ? -contempt + : Options["Analysis Contempt"] == "Black" && us == WHITE ? -contempt + : contempt; // contempt remains with the side to move + + // Eval::Contempt is from white's point of view + Eval::Contempt = (us == WHITE ? make_score(contempt, contempt / 2) + : -make_score(contempt, contempt / 2)); if (rootMoves.empty()) { @@ -280,26 +228,22 @@ void MainThread::search() { << sync_endl; } else - { - Move bookMove = MOVE_NONE; - - if (!Limits.infinite && !Limits.mate) - bookMove = tzbook.probe2(rootPos); - - if (bookMove && std::count(rootMoves.begin(), rootMoves.end(), bookMove)) - { - std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), bookMove)); - for (Thread* th : Threads) - if (th != this) - std::swap(th->rootMoves[0], *std::find(th->rootMoves.begin(), th->rootMoves.end(), bookMove)); - } - else - { + { + Move bookMove = MOVE_NONE; + + if (!Limits.infinite && !Limits.mate) + bookMove = polybook.probe(rootPos); + + if (bookEnabled && bookMove && std::count(rootMoves.begin(), rootMoves.end(), bookMove)) + { + for (Thread* th : Threads) + std::swap(th->rootMoves[0], *std::find(th->rootMoves.begin(), th->rootMoves.end(), bookMove)); + } + else + { if (limitStrength) { - int uci_elo = (Options["UCI_ELO"]); - std::mt19937 gen(now()); std::uniform_int_distribution dis(-33, 33); int rand = dis(gen); @@ -311,18 +255,13 @@ void MainThread::search() { Limits.nodes *= std::max(1,Time.optimum()/1000 ); std::this_thread::sleep_for (std::chrono::seconds(Time.optimum()/1000) * (1 - Limits.nodes/724000)); } - for (Thread* th : Threads) - if (th != this) - th->start_searching(); - - Thread::search(); // Let's start searching! - } - } - - // When playing in 'nodes as time' mode, subtract the searched nodes from - // the available ones before exiting. - if (Limits.npmsec) - Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); + for (Thread* th : Threads) + if (th != this) + th->start_searching(); + + Thread::search(); // Let's start searching! + } + } // When we reach the maximum depth, we can arrive here without a raise of // Threads.stop. However, if we are pondering or in an infinite search, @@ -343,10 +282,14 @@ void MainThread::search() { if (th != this) th->wait_for_search_finished(); + // When playing in 'nodes as time' mode, subtract the searched nodes from + // the available ones before exiting. + if (Limits.npmsec) + Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); + // Check if there are threads with a better score than main thread Thread* bestThread = this; - if ( !this->easyMovePlayed - && Options["MultiPV"] == 1 + if ( Options["MultiPV"] == 1 && !Limits.depth && !Skill(Options["Skill Level"]).enabled() && rootMoves[0].pv[0] != MOVE_NONE) @@ -384,28 +327,30 @@ void MainThread::search() { void Thread::search() { - Stack stack[MAX_PLY+7], *ss = stack+4; // To allow referencing (ss-4) and (ss+2) - Value bestValue, alpha, beta, delta; - Move easyMove = MOVE_NONE; + Stack stack[MAX_PLY+7], *ss = stack+4; // To reference from (ss-4) to (ss+2) + Value bestValue, alpha, beta, delta1, delta2; + Move lastBestMove = MOVE_NONE; + Depth lastBestMoveDepth = DEPTH_ZERO; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); + double timeReduction = 1.0; std::memset(ss-4, 0, 7 * sizeof(Stack)); for (int i = 4; i > 0; i--) (ss-i)->contHistory = &this->contHistory[NO_PIECE][0]; // Use as sentinel - bestValue = delta = alpha = -VALUE_INFINITE; + bestValue = delta1 = delta2 = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; if (mainThread) { - easyMove = EasyMove.get(rootPos.key()); - EasyMove.clear(); - mainThread->easyMovePlayed = mainThread->failedLow = false; + mainThread->failedLow = false; mainThread->bestMoveChanges = 0; } - size_t multiPV = Options["MultiPV"]; + multiPV = Options["MultiPV"]; Skill skill(Options["Skill Level"]); + if (tactical) multiPV = pow(2, tactical); + // When playing with strength handicap enable MultiPV search that we will // use behind the scenes to retrieve a set of possible moves. @@ -413,19 +358,25 @@ void Thread::search() { multiPV = std::max(multiPV, (size_t)4); multiPV = std::min(multiPV, rootMoves.size()); + + // UCI options specify 256 as the max MultiPv option + int consecutiveEarlyExits[257] = {0}; // Iterative deepening loop until requested to stop or the target depth is reached while ( (rootDepth += ONE_PLY) < DEPTH_MAX && !Threads.stop && !(Limits.depth && mainThread && rootDepth / ONE_PLY > Limits.depth)) { - // Distribute search depths across the threads - if (idx) - { - int i = (idx - 1) % 20; - if (((rootDepth / ONE_PLY + rootPos.game_ply() + skipPhase[i]) / skipSize[i]) % 2) - continue; - } + // Distribute search depths across the threads + if (idx) + { + int i = (idx - 1) % 20; + if (idx == Threads.size() - 1 && !ss->excludedMove && rootDepth < 14 * ONE_PLY && rootMoves.size() > 1 && rootPos.see_ge(rootMoves[0].pv[0], VALUE_ZERO + 1)) + ss->excludedMove = rootMoves[0].pv[0]; //lazy_or_stubborn by pb00068 + else + if (((rootDepth / ONE_PLY + rootPos.game_ply() + skipPhase[i]) / skipSize[i]) % 2) + continue; + } // Age out PV variability metric if (mainThread) @@ -445,10 +396,11 @@ void Thread::search() { // Reset aspiration window starting size if (rootDepth >= 5 * ONE_PLY) { - Value prevScore = rootMoves[PVIdx].previousScore; - delta = std::max(Value(int(8.0 + 0.1 * abs(prevScore))), Value(18)); - alpha = std::max(rootMoves[PVIdx].previousScore - delta,-VALUE_INFINITE); - beta = std::min(rootMoves[PVIdx].previousScore + delta, VALUE_INFINITE); + Value prevScore = rootMoves[PVIdx].previousScore; + delta1 = (prevScore < 0) ? Value(int(8.0 + 0.1 * abs(prevScore))) : Value(18); + delta2 = (prevScore > 0) ? Value(int(8.0 + 0.1 * abs(prevScore))) : Value(18); + alpha = std::max(prevScore - delta1,-VALUE_INFINITE); + beta = std::min(prevScore + delta2, VALUE_INFINITE); } // Start with a small aspiration window and, in the case of a fail @@ -464,7 +416,8 @@ void Thread::search() { // and we want to keep the same order for all the moves except the // new PV that goes to the front. Note that in case of MultiPV // search the already searched PV lines are preserved. - std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end()); + if (PVIdx + 1 == multiPV) + std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end()); // If search has been stopped, we break immediately. Sorting and // writing PV back to TT is safe because RootMoves is still @@ -485,7 +438,7 @@ void Thread::search() { if (bestValue <= alpha) { beta = (alpha + beta) / 2; - alpha = std::max(bestValue - delta, -VALUE_INFINITE); + alpha = std::max(bestValue - delta1, -VALUE_INFINITE); if (mainThread) { @@ -493,14 +446,20 @@ void Thread::search() { Threads.stopOnPonderhit = false; } } - else if (bestValue >= beta) - beta = std::min(bestValue + delta, VALUE_INFINITE); + + else if ( bestValue >= beta) + beta = std::min(bestValue + delta2, VALUE_INFINITE); //Ivan Ivec + else + { + delta1 += delta1 / 4 + 5; //Ivan Ivec + delta2 += delta2 / 4 + 5; //Ivan Ivec + + assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + + consecutiveEarlyExits[PVIdx] = (bestValue >= beta) ? consecutiveEarlyExits[PVIdx] + 1 : 0; break; - - delta += delta / 4 + 5; - - assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + } } // Sort the PV lines searched so far and update the GUI @@ -514,7 +473,12 @@ void Thread::search() { if (!Threads.stop) completedDepth = rootDepth; - // Has any of the threads found a "mate in x"? + if (rootMoves[0].pv[0] != lastBestMove) { + lastBestMove = rootMoves[0].pv[0]; + lastBestMoveDepth = rootDepth; + } + + // Have we found a "mate in x"? if ( Limits.mate && bestValue >= VALUE_MATE_IN_MAX_PLY && VALUE_MATE - bestValue <= 2 * Limits.mate) @@ -522,14 +486,14 @@ void Thread::search() { if (!mainThread) continue; - +//BEGINS MichaelB7 if (Options["FastPlay"]) - { - if ( Time.elapsed() > Time.optimum() / 256 - && ( abs(bestValue) > 12300 || abs(bestValue) >= VALUE_MATE_IN_MAX_PLY )) - Threads.stop = true; - } - + { + if ( Time.elapsed() > Time.optimum() / 256 + && ( abs(bestValue) > 12300 || abs(bestValue) >= VALUE_MATE_IN_MAX_PLY )) + Threads.stop = true; + } +//ENDS MichaelB7 // If skill level is enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(rootDepth)) skill.pick_best(multiPV); @@ -540,21 +504,29 @@ void Thread::search() { if (!Threads.stop && !Threads.stopOnPonderhit) { // Stop the search if only one legal move is available, or if all - // of the available time has been used, or if we matched an easyMove - // from the previous search and just did a fast verification. + // of the available time has been used const int F[] = { mainThread->failedLow, bestValue - mainThread->previousScore }; - int improvingFactor = std::max(229, std::min(715, 357 + 119 * F[0] - 6 * F[1])); - double unstablePvFactor = 1 + mainThread->bestMoveChanges; - bool doEasyMove = rootMoves[0].pv[0] == easyMove - && mainThread->bestMoveChanges < 0.03 - && Time.elapsed() > Time.optimum() * 5 / 44; + Color us = rootPos.side_to_move(); + bool thinkHard = bestValue == VALUE_DRAW + && Limits.time[us] - Time.elapsed() > Limits.time[~us] + && ::pv_is_draw(rootPos); + + double unstablePvFactor = 1 + mainThread->bestMoveChanges + thinkHard; + + // if the bestMove is stable over several iterations, reduce time for this move, + // the longer the move has been stable, the more. + // Use part of the gained time from a previous stable move for the current move. + timeReduction = 1; + for (int i : {3, 4, 5}) + if (lastBestMoveDepth * i < completedDepth && !thinkHard) + timeReduction *= 1.3; + unstablePvFactor *= std::pow(mainThread->previousTimeReduction, 0.51) / timeReduction; if ( rootMoves.size() == 1 - || Time.elapsed() > Time.optimum() * unstablePvFactor * improvingFactor / 628 - || (mainThread->easyMovePlayed = doEasyMove, doEasyMove)) + || Time.elapsed() > Time.optimum() * unstablePvFactor * improvingFactor / 628) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". @@ -564,26 +536,18 @@ void Thread::search() { Threads.stop = true; } } - - if (rootMoves[0].pv.size() >= 3) - EasyMove.update(rootPos, rootMoves[0].pv); - else - EasyMove.clear(); } } if (!mainThread) return; - // Clear any candidate easy move that wasn't stable for the last search - // iterations; the second condition prevents consecutive fast moves. - if (EasyMove.stableCnt < 6 || mainThread->easyMovePlayed) - EasyMove.clear(); + mainThread->previousTimeReduction = timeReduction; // If skill level is enabled, swap best PV line with the sub-optimal one if (skill.enabled()) - std::swap(rootMoves[0], *std::find(rootMoves.begin(), - rootMoves.end(), skill.best_move(multiPV))); + std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(multiPV))); } @@ -595,7 +559,7 @@ namespace { Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning) { const bool PvNode = NT == PV; - const bool rootNode = PvNode && (ss-1)->ply == 0; + const bool rootNode = PvNode && ss->ply == 0; assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); @@ -603,7 +567,7 @@ namespace { assert(!(PvNode && cutNode)); assert(depth / ONE_PLY * ONE_PLY == depth); - Move pv[MAX_PLY+1], quietsSearched[64]; + Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; StateInfo st; TTEntry* tte; Key posKey; @@ -611,19 +575,19 @@ namespace { Depth extension, newDepth; Value bestValue, value, ttValue, eval, maxValue; bool ttHit, inCheck, givesCheck, singularExtensionNode, improving; - bool captureOrPromotion, doFullDepthSearch, moveCountPruning, skipQuiets, ttCapture; + bool captureOrPromotion, doFullDepthSearch, moveCountPruning, skipQuiets, ttCapture, goodCap, pvExact; Piece movedPiece; - int moveCount, quietCount; + int moveCount, captureCount, quietCount; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); inCheck = pos.checkers(); - moveCount = quietCount = ss->moveCount = 0; + moveCount = captureCount = quietCount = ss->moveCount = 0; ss->statScore = 0; bestValue = -VALUE_INFINITE; maxValue = VALUE_INFINITE; - ss->ply = (ss-1)->ply + 1; + (ss + 1)->ply = (ss)->ply + 1; ss->forcedMove = 0; ss->forcingTree = ((ss - 2)->forcingTree && (ss - 2)->forcedMove) // Recursive forcing tree. || (ss->ply == 2 && (ss - 1)->moveCount > 1) // Offensive forcing tree @@ -633,16 +597,15 @@ namespace { if (thisThread == Threads.main()) static_cast(thisThread)->check_time(); - // Used to send selDepth info to GUI - if (PvNode && thisThread->selDepth < ss->ply) - thisThread->selDepth = ss->ply; + // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) + if (PvNode && thisThread->selDepth < ss->ply + 1) + thisThread->selDepth = ss->ply + 1; if (!rootNode) { // Step 2. Check for aborted search and immediate draw if (Threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) - : DrawValue[pos.side_to_move()]; + return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) : VALUE_DRAW; // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply+1), but if alpha is already bigger because @@ -658,16 +621,17 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); + (ss+1)->ply = ss->ply + 1; ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; ss->contHistory = &thisThread->contHistory[NO_PIECE][0]; - (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + (ss+2)->killers[0] = (ss+2)->killers[1] = (ss+2)->killers[2] = (ss+2)->killers[3] = MOVE_NONE; Square prevSq = to_sq((ss-1)->currentMove); // Step 4. Transposition table lookup. We don't want the score of a partial // search to overwrite a previous full search TT value, so we use a different // position key in case of an excluded move. excludedMove = ss->excludedMove; - posKey = pos.key() ^ Key(excludedMove); + posKey = pos.key() ^ Key(excludedMove << 16); // isn't a very good hash tte = TT.probe(posKey, ttHit); ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0] @@ -688,7 +652,16 @@ namespace { { if (!pos.capture_or_promotion(ttMove)) update_stats(pos, ss, ttMove, nullptr, 0, stat_bonus(depth)); - + else update_capture_stats(pos, ttMove, nullptr, 0, stat_bonus(depth)); + if(type_of(ttMove) == PROMOTION || !pos.see_ge(ttMove, VALUE_ZERO)) + { + if (ss->killers[2] != ttMove) + { + ss->killers[3] = ss->killers[2]; + ss->killers[2] = ttMove; + } + } + // Extra penalty for a quiet TT move in previous ply when it gets refuted if ((ss-1)->moveCount == 1 && !pos.captured_piece()) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); @@ -715,7 +688,7 @@ namespace { && !pos.can_castle(ANY_CASTLING)) { TB::ProbeState err; - TB::WDLScore v = Tablebases::probe_wdl(pos, &err); + TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); if (err != TB::ProbeState::FAIL) { @@ -723,36 +696,30 @@ namespace { int drawScore = TB::UseRule50 ? 1 : 0; - value = v < -drawScore ? -VALUE_MATE + MAX_PLY + ss->ply - : v > drawScore ? VALUE_MATE - MAX_PLY - ss->ply - : VALUE_DRAW + 2 * v * drawScore; + value = wdl < -drawScore ? -VALUE_MATE + MAX_PLY + ss->ply + 1 + : wdl > drawScore ? VALUE_MATE - MAX_PLY - ss->ply - 1 + : VALUE_DRAW + 2 * wdl * drawScore; + + Bound b = wdl < -drawScore ? BOUND_UPPER + : wdl > drawScore ? BOUND_LOWER : BOUND_EXACT; + + if ( b == BOUND_EXACT + || (b == BOUND_LOWER ? value >= beta : value <= alpha)) + { + tte->save(posKey, value_to_tt(value, ss->ply), b, + std::min(DEPTH_MAX - ONE_PLY, depth + 6 * ONE_PLY), + MOVE_NONE, VALUE_NONE, TT.generation()); - if (abs(v) <= drawScore) { - tte->save(posKey, value_to_tt(value, ss->ply), BOUND_EXACT, - depth, MOVE_NONE, VALUE_NONE, TT.generation()); return value; } - else if (v < -drawScore) { - if (alpha >= value) { - tte->save(posKey, value_to_tt(value, ss->ply), - BOUND_UPPER, depth, MOVE_NONE, VALUE_NONE, - TT.generation()); - return value; - } - maxValue = value; - } - else { - if (beta <= value) { - tte->save(posKey, value_to_tt(value, ss->ply), - BOUND_LOWER, depth, MOVE_NONE, VALUE_NONE, - TT.generation()); - return value; - } - bestValue = value; - if (PvNode && bestValue > alpha) - alpha = bestValue; - } + if (PvNode) + { + if (b == BOUND_LOWER) + bestValue = value, alpha = std::max(alpha, bestValue); + else + maxValue = value; + } } } } @@ -785,18 +752,18 @@ namespace { ss->staticEval, TT.generation()); } - if (skipEarlyPruning) + if (skipEarlyPruning || !pos.non_pawn_material(pos.side_to_move())) goto moves_loop; // Step 6. Razoring (skipped when in check) if ( !bruteForce && !PvNode && depth < 4 * ONE_PLY - && eval + razor_margin[depth / ONE_PLY] <= alpha) + && eval + razor_margin <= alpha) { if (depth <= ONE_PLY) return qsearch(pos, ss, alpha, alpha+1); - Value ralpha = alpha - razor_margin[depth / ONE_PLY]; + Value ralpha = alpha - razor_margin; Value v = qsearch(pos, ss, ralpha, ralpha+1); if (v <= ralpha) return v; @@ -806,22 +773,23 @@ namespace { if ( !bruteForce && !rootNode && depth < 7 * ONE_PLY && eval - futility_margin(depth) >= beta - && eval < VALUE_KNOWN_WIN // Do not return unproven wins - && pos.non_pawn_material(pos.side_to_move())) + && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; // Step 8. Null move search with verification search (is omitted in PV nodes) - if ( !noNULL && !PvNode + if ( !PvNode && eval >= beta - && (ss->staticEval >= beta - 35 * (depth / ONE_PLY - 6) || depth >= 13 * ONE_PLY) - && thisThread->selDepth + 3 * ONE_PLY > thisThread->rootDepth - && pos.non_pawn_material(pos.side_to_move()) > (depth > 12 * ONE_PLY) * BishopValueMg) + && ss->staticEval >= beta - int(320 * log(depth / ONE_PLY)) + 500 //Corchess/IIvec + && (((ss-2)->currentMove != MOVE_NULL) || (ss-2)->staticEval >= beta - 36 * depth / ONE_PLY + 225) + && thisThread->selDepth + 3 > thisThread->rootDepth / ONE_PLY // idea from Corchess/IIvec + && (ss->ply >= thisThread->nmp_ply || ss->ply % 2 != thisThread->pair) + && !(MoveList(pos).size() < 4)) //MichaelB7 { assert(eval - beta >= 0); // Null move dynamic reduction based on depth and value - Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min((eval - beta) / PawnValueMg, 3)) * ONE_PLY; + Depth R = (int(2.6 * log(depth / ONE_PLY)) + std::min((eval - beta) / Value(170), 3)) * ONE_PLY; ss->currentMove = MOVE_NULL; ss->contHistory = &thisThread->contHistory[NO_PIECE][0]; @@ -841,8 +809,16 @@ namespace { return nullValue; // Do verification search when searching for mate + R += ONE_PLY; + // disable null move pruning for side to move for the first part of the remaining search tree + int nmp_ply = thisThread->nmp_ply; + int pair = thisThread->pair; + thisThread->nmp_ply = ss->ply + 3 * (depth-R) / 4; + thisThread->pair = (ss->ply % 2) == 0; Value v = depth-R < ONE_PLY ? qsearch(pos, ss, beta-1, beta) : search(pos, ss, beta-1, beta, depth-R, false, true); + thisThread->pair = pair; + thisThread->nmp_ply = nmp_ply; if (v >= beta) return nullValue; @@ -852,7 +828,8 @@ namespace { // Step 9. ProbCut (skipped when in check) // If we have a good enough capture and a reduced search returns a value // much above beta, we can (almost) safely prune the previous move. - if ( !bruteForce && !PvNode + if ( !bruteForce + && !PvNode && depth >= 5 * ONE_PLY && abs(beta) < VALUE_MATE_IN_MAX_PLY) { @@ -860,7 +837,7 @@ namespace { assert(is_ok((ss-1)->currentMove)); - MovePicker mp(pos, ttMove, rbeta - ss->staticEval); + MovePicker mp(pos, ttMove, rbeta - ss->staticEval, &thisThread->captureHistory); while ((move = mp.next_move()) != MOVE_NONE) if (pos.legal(move)) @@ -894,7 +871,7 @@ namespace { const PieceToHistory* contHist[] = { (ss-1)->contHistory, (ss-2)->contHistory, nullptr, (ss-4)->contHistory }; Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; - MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, contHist, countermove, ss->killers); + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, countermove, ss->killers); value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc improving = ss->staticEval >= (ss-2)->staticEval /* || ss->staticEval == VALUE_NONE Already implicit in the previous condition */ @@ -909,6 +886,8 @@ namespace { && tte->depth() >= depth - 3 * ONE_PLY; skipQuiets = false; ttCapture = false; + goodCap = false; + pvExact = PvNode && ttHit && tte->bound() == BOUND_EXACT; // Step 11. Loop through moves // Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs @@ -919,12 +898,20 @@ namespace { if (move == excludedMove) continue; - // At root obey the "searchmoves" option and skip moves not listed in Root - // Move List. As a consequence any illegal move is also skipped. In MultiPV - // mode we also skip PV moves which have been already searched. - if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx, - thisThread->rootMoves.end(), move)) - continue; + if (rootNode) + { + // At root obey the "searchmoves" option and skip moves not listed in Root + // Move List. As a consequence any illegal move is also skipped. + if (!std::count(thisThread->rootMoves.begin() + thisThread->PVIdx, + thisThread->rootMoves.end(), move)) + continue; + + // In MultiPV mode we not only skip PV moves which have already been searched, + // but also any other move except we have reached the last PV line. + if ( thisThread->PVIdx + 1 < thisThread->multiPV + && move != ttMove) + continue; + } ss->moveCount = ++moveCount; @@ -941,7 +928,7 @@ namespace { movedPiece = pos.moved_piece(move); givesCheck = type_of(move) == NORMAL && !pos.discovered_check_candidates() - ? pos.check_squares(type_of(pos.piece_on(from_sq(move)))) & to_sq(move) + ? pos.check_squares(type_of(movedPiece)) & to_sq(move) : pos.gives_check(move); moveCountPruning = depth < 16 * ONE_PLY @@ -956,6 +943,7 @@ namespace { // ttValue minus a margin then we will extend the ttMove. if ( singularExtensionNode && move == ttMove + && !extension && pos.legal(move)) { Value rBeta = std::max(ttValue - 2 * depth / ONE_PLY, -VALUE_MATE); @@ -965,22 +953,22 @@ namespace { ss->excludedMove = MOVE_NONE; if (value < rBeta) - extension = ONE_PLY; + extension = ONE_PLY; } - else if ( givesCheck - && !moveCountPruning - && pos.see_ge(move)) - extension = ONE_PLY; + else if (!moveCountPruning) + { + if ( givesCheck + && (pos.see_ge(move) || PvNode )) + extension = ONE_PLY; + + else if ( PvNode + && depth < 13 * ONE_PLY + && ( givesCheck || pos.advanced_pawn_push(move) + || ( ss->forcingTree && (ss)->newDepth - depth > 1))) + extension = ONE_PLY; + } - else if ( pos.advanced_pawn_push(move) - && !moveCountPruning - && pos.non_pawn_material(pos.side_to_move()) <= RookValueEg) - extension = ONE_PLY; - else if ( ss->forcingTree && (ss - 1)->newDepth - depth > 1 - && !moveCountPruning) - extension = ONE_PLY; - // Calculate new depth for this move newDepth = depth - ONE_PLY + extension; @@ -1001,8 +989,7 @@ namespace { } // Reduced depth of the next LMR search - int mch = std::max(1, moveCount - (ss-1)->moveCount / 16); - int lmrDepth = std::max(newDepth - reduction(improving, depth, mch), DEPTH_ZERO) / ONE_PLY; + int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), DEPTH_ZERO) / ONE_PLY; // Countermoves based pruning if ( lmrDepth < 3 @@ -1013,7 +1000,7 @@ namespace { // Futility pruning: parent node if ( lmrDepth < 7 && !inCheck - && ss->staticEval + 256 + 200 * lmrDepth <= alpha) + && ss->staticEval + 256 + futility_margin(lmrDepth * ONE_PLY) <= alpha)// idea from JD Watson, modifed by MichaelB7 continue; // Prune moves with negative SEE @@ -1039,11 +1026,13 @@ namespace { if (move == ttMove && captureOrPromotion) ttCapture = true; + else if (to_sq(move) == to_sq((ss - 1)->currentMove)) + goodCap = true; // Update the current move (this must be done after singular extension search) ss->currentMove = move; ss->contHistory = &thisThread->contHistory[movedPiece][to_sq(move)]; - ss->forcedMove = (extension || inCheck || captureOrPromotion) && -(ss - 1)->staticEval < alpha - PawnValueEg / 2 && ss->staticEval > alpha - PawnValueEg / 2; + ss->forcedMove = (extension || inCheck || captureOrPromotion) && (ss)->staticEval < alpha - PawnValueEg / 2 && ss->staticEval > alpha - PawnValueEg / 2; // Step 14. Make the move pos.do_move(move, st, givesCheck); @@ -1052,18 +1041,29 @@ namespace { // re-searched at full depth. if ( !bruteForce && depth >= 3 * ONE_PLY && moveCount > 1 - && (!captureOrPromotion || moveCountPruning)) + && (!captureOrPromotion || moveCountPruning) + && (ss+2)->killers[0] != move + && (ss+2)->killers[1] != move) { - int mch = std::max(1, moveCount - (ss-1)->moveCount / 16); - Depth r = reduction(improving, depth, mch); + Depth r = reduction(improving, depth, moveCount); if (captureOrPromotion) r -= r ? ONE_PLY : DEPTH_ZERO; else { + // Decrease reduction if opponent's move count is high + if ((ss-1)->moveCount > 13 ) + r -= ONE_PLY; + + // Decrease reduction for exact PV nodes + if (pvExact) + r -= ONE_PLY; + // Increase reduction if ttMove is a capture - if (ttCapture) + if (ttCapture || (!pvExact && depth > 13 * ONE_PLY)) r += ONE_PLY; + else if(goodCap && !inCheck && !givesCheck) + r += ONE_PLY; // Increase reduction for cut nodes if (cutNode) @@ -1079,18 +1079,18 @@ namespace { ss->statScore = thisThread->mainHistory[~pos.side_to_move()][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] - 4000; // Decrease/increase reduction by comparing opponent's stat score - if (ss->statScore > 0 && ss->statScore > (ss-1)->statScore) + if (ss->statScore >= 0 && (ss-1)->statScore < 0) r -= ONE_PLY; - else if (ss->statScore < 0 && ss->statScore < (ss-1)->statScore ) + else if ((ss-1)->statScore >= 0 && ss->statScore < 0) r += ONE_PLY; // Decrease/increase reduction for moves with a good/bad history - r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->statScore / 32768) * ONE_PLY); + r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->statScore / 20000) * ONE_PLY); } Depth d = std::max(newDepth - r, ONE_PLY); @@ -1188,6 +1188,8 @@ namespace { if (!captureOrPromotion && move != bestMove && quietCount < 64) quietsSearched[quietCount++] = move; + else if (captureOrPromotion && move != bestMove && captureCount < 32) + capturesSearched[captureCount++] = move; } // The following condition would detect a stop only after move loop has been @@ -1207,12 +1209,22 @@ namespace { if (!moveCount) bestValue = excludedMove ? alpha - : inCheck ? mated_in(ss->ply) : DrawValue[pos.side_to_move()]; + : inCheck ? mated_in(ss->ply) : VALUE_DRAW; else if (bestMove) { // Quiet best move: update move sorting heuristics if (!pos.capture_or_promotion(bestMove)) update_stats(pos, ss, bestMove, quietsSearched, quietCount, stat_bonus(depth)); + else update_capture_stats(pos, bestMove, capturesSearched, captureCount, stat_bonus(depth)); + if(type_of(bestMove) == PROMOTION || quietCount > 3) + { + if (ss->killers[2] != bestMove) + { + ss->killers[3] = ss->killers[2]; + ss->killers[2] = bestMove; + } + } + // Extra penalty for a quiet TT move in previous ply when it gets refuted if ((ss-1)->moveCount == 1 && !pos.captured_piece()) @@ -1224,8 +1236,8 @@ namespace { && is_ok((ss-1)->currentMove)) update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth)); - if (PvNode && bestValue > maxValue) - bestValue = maxValue; + if (PvNode) + bestValue = std::min(bestValue, maxValue); if (!excludedMove) tte->save(posKey, value_to_tt(bestValue, ss->ply), @@ -1247,7 +1259,7 @@ namespace { const bool PvNode = NT == PV; - assert(InCheck == !!pos.checkers()); + assert(InCheck == bool(pos.checkers())); assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(depth <= DEPTH_ZERO); @@ -1271,13 +1283,12 @@ namespace { } ss->currentMove = bestMove = MOVE_NONE; - ss->ply = (ss-1)->ply + 1; + (ss+1)->ply = ss->ply + 1; moveCount = 0; // Check for an instant draw or if the maximum ply has been reached if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) - return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) - : DrawValue[pos.side_to_move()]; + return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1286,7 +1297,6 @@ namespace { // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. ttDepth = InCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; - // Transposition table lookup posKey = pos.key(); tte = TT.probe(posKey, ttHit); @@ -1329,7 +1339,7 @@ namespace { if (bestValue >= beta) { if (!ttHit) - tte->save(pos.key(), value_to_tt(bestValue, ss->ply), BOUND_LOWER, + tte->save(posKey, value_to_tt(bestValue, ss->ply), BOUND_LOWER, DEPTH_NONE, MOVE_NONE, ss->staticEval, TT.generation()); return bestValue; @@ -1345,7 +1355,7 @@ namespace { // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will // be generated. - MovePicker mp(pos, ttMove, depth, &pos.this_thread()->mainHistory, to_sq((ss-1)->currentMove)); + MovePicker mp(pos, ttMove, depth, &pos.this_thread()->mainHistory, &pos.this_thread()->captureHistory, to_sq((ss-1)->currentMove)); // Loop through the moves until no moves remain or a beta cutoff occurs while ((move = mp.next_move()) != MOVE_NONE) @@ -1353,7 +1363,7 @@ namespace { assert(is_ok(move)); givesCheck = type_of(move) == NORMAL && !pos.discovered_check_candidates() - ? pos.check_squares(type_of(pos.piece_on(from_sq(move)))) & to_sq(move) + ? pos.check_squares(type_of(pos.moved_piece(move))) & to_sq(move) : pos.gives_check(move); moveCount++; @@ -1389,7 +1399,6 @@ namespace { // Don't search moves with negative SEE values if ( (!InCheck || evasionPrunable) - && type_of(move) != PROMOTION && !pos.see_ge(move)) continue; @@ -1500,6 +1509,26 @@ namespace { } + // update_capture_stats() updates move sorting heuristics when a new capture best move is found + + void update_capture_stats(const Position& pos, Move move, + Move* captures, int captureCnt, int bonus) { + + CapturePieceToHistory& captureHistory = pos.this_thread()->captureHistory; + Piece moved_piece = pos.moved_piece(move); + PieceType captured = type_of(pos.piece_on(to_sq(move))); + captureHistory.update(moved_piece, to_sq(move), captured, bonus); + + // Decrease all the other played capture moves + for (int i = 0; i < captureCnt; ++i) + { + moved_piece = pos.moved_piece(captures[i]); + captured = type_of(pos.piece_on(to_sq(captures[i]))); + captureHistory.update(moved_piece, to_sq(captures[i]), captured, -bonus); + } + } + + // update_stats() updates move sorting heuristics when a new quiet best move is found void update_stats(const Position& pos, Stack* ss, Move move, @@ -1531,6 +1560,24 @@ namespace { } + // Is the PV leading to a draw position? Assumes all pv moves are legal + bool pv_is_draw(Position& pos) { + + StateInfo st[MAX_PLY]; + auto& pv = pos.this_thread()->rootMoves[0].pv; + + for (size_t i = 0; i < pv.size(); ++i) + pos.do_move(pv[i], st[i]); + + bool isDraw = pos.is_draw(pv.size()); + + for (size_t i = pv.size(); i > 0; --i) + pos.undo_move(pv[i-1]); + + return isDraw; + } + + // When playing with strength handicap, choose best move among a set of RootMoves // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. @@ -1554,7 +1601,7 @@ namespace { int push = ( weakness * int(topScore - rootMoves[i].score) + delta * (rng.rand() % weakness)) / 128; - if (rootMoves[i].score + push > maxScore) + if (rootMoves[i].score + push >= maxScore) { maxScore = rootMoves[i].score + push; best = rootMoves[i].pv[0]; @@ -1593,7 +1640,7 @@ namespace { if (Threads.ponder) return; - if ( (Limits.use_time_management() && elapsed > Time.maximum()) + if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10) || (Limits.movetime && elapsed >= Limits.movetime) || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) Threads.stop = true; @@ -1606,12 +1653,19 @@ namespace { string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { std::stringstream ss; + int temp = 0; int elapsed = Time.elapsed() + 1; const RootMoves& rootMoves = pos.this_thread()->rootMoves; size_t PVIdx = pos.this_thread()->PVIdx; - size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); + size_t multiPV = pos.this_thread()->multiPV; uint64_t nodesSearched = Threads.nodes_searched(); uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); + + if (tactical) + { + temp = multiPV; + multiPV = 1; + } for (size_t i = 0; i < multiPV; ++i) { @@ -1651,11 +1705,10 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { for (Move m : rootMoves[i].pv) ss << " " << UCI::move(m, pos.is_chess960()); } - + if (tactical) multiPV = temp; return ss.str(); } - /// RootMove::extract_ponder_from_tt() is called in case we have no ponder move /// before exiting the search, for instance, in case we stop the search during a /// fail high at root. We try hard to have a ponder move to return to the GUI, @@ -1702,6 +1755,10 @@ void Tablebases::filter_root_moves(Position& pos, Search::RootMoves& rootMoves) if (Cardinality < popcount(pos.pieces()) || pos.can_castle(ANY_CASTLING)) return; + // Don't filter any moves if the user requested analysis on multiple + if (Options["MultiPV"] != 1) + return; + // If the current root position is in the tablebases, then RootMoves // contains only moves that preserve the draw or the win. RootInTB = root_probe(pos, rootMoves, TB::Score); @@ -1723,8 +1780,9 @@ void Tablebases::filter_root_moves(Position& pos, Search::RootMoves& rootMoves) TB::Score = TB::Score > VALUE_DRAW ? VALUE_MATE - MAX_PLY - 1 : TB::Score < VALUE_DRAW ? -VALUE_MATE + MAX_PLY + 1 : VALUE_DRAW; - // Since root_probe() and root_probe_wdl() dirty the root move scores, - // we reset them to -VALUE_INFINITE - for (size_t i = 0; i < rootMoves.size(); ++i) - rootMoves[i].score = -VALUE_INFINITE; + + // Since root_probe() and root_probe_wdl() dirty the root move scores, + // we reset them to -VALUE_INFINITE + for (RootMove& rm : rootMoves) + rm.score = -VALUE_INFINITE; } diff --git a/Engines/Linux32/mcbrain/src/search.h b/Engines/Linux32/mcbrain/src/search.h index 5fe6d32..f1ac439 100644 --- a/Engines/Linux32/mcbrain/src/search.h +++ b/Engines/Linux32/mcbrain/src/search.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED @@ -31,6 +32,10 @@ class Position; namespace Search { +/// Threshold used for countermoves based pruning +const int CounterMovePruneThreshold = 0; + + /// Stack struct keeps track of the information we need to remember from nodes /// shallower and deeper in the tree during the search. Each search thread has /// its own array of Stack objects, indexed by the current ply. @@ -41,14 +46,13 @@ struct Stack { int ply; Move currentMove; Move excludedMove; - Move killers[2]; + Move killers[4]; Value staticEval; int statScore; int moveCount; Depth newDepth; uint8_t forcedMove; uint8_t forcingTree; - }; diff --git a/Engines/Linux32/mcbrain/src/tbprobe.cpp b/Engines/Linux32/mcbrain/src/tbprobe.cpp index 90a016b..09e031c 100644 --- a/Engines/Linux32/mcbrain/src/tbprobe.cpp +++ b/Engines/Linux32/mcbrain/src/tbprobe.cpp @@ -1,21 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (c) 2013 Ronald de Man - Copyright (C) 2016-2017 Marco Costalba, Lucas Braesch - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include #include @@ -133,16 +135,16 @@ struct Atomic { std::atomic_bool ready; }; -// We define types for the different parts of the WLDEntry and DTZEntry with +// We define types for the different parts of the WDLEntry and DTZEntry with // corresponding specializations for pieces or pawns. -struct WLDEntryPiece { +struct WDLEntryPiece { PairsData* precomp; }; struct WDLEntryPawn { uint8_t pawnCount[2]; // [Lead color / other color] - WLDEntryPiece file[2][4]; // [wtm / btm][FILE_A..FILE_D] + WDLEntryPiece file[2][4]; // [wtm / btm][FILE_A..FILE_D] }; struct DTZEntryPiece { @@ -172,7 +174,7 @@ struct WDLEntry : public TBEntry { WDLEntry(const std::string& code); ~WDLEntry(); union { - WLDEntryPiece pieceTable[2]; // [wtm / btm] + WDLEntryPiece pieceTable[2]; // [wtm / btm] WDLEntryPawn pawnTable; }; }; @@ -341,6 +343,10 @@ class TBFile : public std::ifstream { #ifndef _WIN32 struct stat statbuf; int fd = ::open(fname.c_str(), O_RDONLY); + + if (fd == -1) + return *baseAddress = nullptr, nullptr; + fstat(fd, &statbuf); *mapping = statbuf.st_size; *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); @@ -353,6 +359,10 @@ class TBFile : public std::ifstream { #else HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (fd == INVALID_HANDLE_VALUE) + return *baseAddress = nullptr, nullptr; + DWORD size_high; DWORD size_low = GetFileSize(fd, &size_high); HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr); @@ -380,8 +390,7 @@ class TBFile : public std::ifstream { || *data++ != *TB_MAGIC) { std::cerr << "Corrupted table in file " << fname << std::endl; unmap(*baseAddress, *mapping); - *baseAddress = nullptr; - return nullptr; + return *baseAddress = nullptr, nullptr; } return data; @@ -482,7 +491,7 @@ void HashTable::insert(const std::vector& pieces) { TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK - if (!file.is_open()) + if (!file.is_open()) // Only WDL file is checked return; file.close(); @@ -1385,7 +1394,7 @@ void Tablebases::init(const std::string& paths) { } } - sync_cout << "info string Found " << EntryTable.size() << " tablebases" << sync_endl; + sync_cout << "Info string found: " << EntryTable.size() << " tablebases" << sync_endl; } // Probe the WDL table for a particular position. diff --git a/Engines/Linux32/mcbrain/src/tbprobe.h b/Engines/Linux32/mcbrain/src/tbprobe.h index 0c16662..97ec7b1 100644 --- a/Engines/Linux32/mcbrain/src/tbprobe.h +++ b/Engines/Linux32/mcbrain/src/tbprobe.h @@ -1,21 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (c) 2013 Ronald de Man - Copyright (C) 2016-2017 Marco Costalba, Lucas Braesch - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef TBPROBE_H #define TBPROBE_H diff --git a/Engines/Linux32/mcbrain/src/thread.cpp b/Engines/Linux32/mcbrain/src/thread.cpp index 6b24521..47a21c1 100644 --- a/Engines/Linux32/mcbrain/src/thread.cpp +++ b/Engines/Linux32/mcbrain/src/thread.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017*2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include // For std::count #include @@ -24,6 +25,7 @@ #include "movegen.h" #include "search.h" #include "thread.h" +#include "uci.h" #include "tbprobe.h" ThreadPool Threads; // Global object @@ -51,6 +53,21 @@ Thread::~Thread() { } +/// Thread::clear() reset histories, usually before a new game + +void Thread::clear() { + + counterMoves.fill(MOVE_NONE); + mainHistory.fill(0); + captureHistory.fill(0); + + for (auto& to : contHistory) + for (auto& h : to) + h.fill(0); + + contHistory[NO_PIECE][0].fill(Search::CounterMovePruneThreshold - 1); +} + /// Thread::start_searching() wakes up the thread that will start the search void Thread::start_searching() { @@ -76,7 +93,13 @@ void Thread::wait_for_search_finished() { void Thread::idle_loop() { - WinProcGroup::bindThisThread(idx); + // If OS already scheduled us on a different group than 0 then don't overwrite + // the choice, eventually we are one of many one-threaded processes running on + // some Windows NUMA hardware, for instance in fishtest. To make it simple, + // just check if running threads are below a threshold, in this case all this + // NUMA machinery is not needed. + if (Options["Threads"] >= 8) + WinProcGroup::bindThisThread(idx); while (true) { @@ -94,42 +117,40 @@ void Thread::idle_loop() { } } +/// ThreadPool::set() creates/destroys threads to match the requested number. +/// Created and launced threads wil go immediately to sleep in idle_loop. +/// Upon resizing, threads are recreated to allow for binding if necessary. -/// ThreadPool::init() creates and launches the threads that will go -/// immediately to sleep in idle_loop. We cannot use the c'tor because -/// Threads is a static object and we need a fully initialized engine at -/// this point due to allocation of Endgames in the Thread constructor. - -void ThreadPool::init(size_t requested) { - - push_back(new MainThread(0)); - set(requested); -} +void ThreadPool::set(size_t requested) { + if (size() > 0) { // destroy any existing thread(s) + main()->wait_for_search_finished(); -/// ThreadPool::exit() terminates threads before the program exits. Cannot be -/// done in the destructor because threads must be terminated before deleting -/// any static object, so before main() returns. + while (size() > 0) + delete back(), pop_back(); + } -void ThreadPool::exit() { + if (requested > 0) { // create new thread(s) + push_back(new MainThread(0)); - main()->wait_for_search_finished(); - set(0); + while (size() < requested) + push_back(new Thread(size())); + clear(); + } } +/// ThreadPool::clear() sets threadPool data to initial values. -/// ThreadPool::set() creates/destroys threads to match the requested number - -void ThreadPool::set(size_t requested) { +void ThreadPool::clear() { - while (size() < requested) - push_back(new Thread(size())); + for (Thread* th : *this) + th->clear(); - while (size() > requested) - delete back(), pop_back(); + main()->callsCnt = 0; + main()->previousScore = VALUE_INFINITE; + main()->previousTimeReduction = 1; } - /// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and /// returns immediately. Main thread will wake up other threads and start the search. @@ -165,12 +186,14 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, // is shared by threads but is accessed in read-only mode. StateInfo tmp = setupStates->back(); - for (Thread* th : Threads) + for (Thread* th : *this) { th->nodes = th->tbHits = 0; th->rootDepth = th->completedDepth = DEPTH_ZERO; th->rootMoves = rootMoves; th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th); + th->nmp_ply = 0; + th->pair = -1; } setupStates->back() = tmp; diff --git a/Engines/Linux32/mcbrain/src/thread.h b/Engines/Linux32/mcbrain/src/thread.h index 8c0a666..25a6511 100644 --- a/Engines/Linux32/mcbrain/src/thread.h +++ b/Engines/Linux32/mcbrain/src/thread.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef THREAD_H_INCLUDED #define THREAD_H_INCLUDED @@ -52,6 +53,7 @@ class Thread { explicit Thread(size_t); virtual ~Thread(); virtual void search(); + void clear(); void idle_loop(); void start_searching(); void wait_for_search_finished(); @@ -59,8 +61,8 @@ class Thread { Pawns::Table pawnsTable; Material::Table materialTable; Endgames endgames; - size_t PVIdx; - int selDepth; + size_t PVIdx, multiPV; + int selDepth, nmp_ply, pair; std::atomic nodes, tbHits; Position rootPos; @@ -68,6 +70,7 @@ class Thread { Depth rootDepth, completedDepth; CounterMoveHistory counterMoves; ButterflyHistory mainHistory; + CapturePieceToHistory captureHistory; ContinuationHistory contHistory; }; @@ -81,8 +84,8 @@ struct MainThread : public Thread { void search() override; void check_time(); - bool easyMovePlayed, failedLow; - double bestMoveChanges; + bool failedLow; + double bestMoveChanges, previousTimeReduction; Value previousScore; int callsCnt; }; @@ -97,11 +100,19 @@ struct ThreadPool : public std::vector { void init(size_t); // No constructor and destructor, threads rely on globals that should void exit(); // be initialized and valid during the whole thread lifetime. void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); + void clear(); void set(size_t); MainThread* main() const { return static_cast(front()); } uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } + + Depth lowestDepth(){ + Depth lowest = DEPTH_MAX; + for (Thread* th: *this) + lowest = std::min(lowest, th->rootDepth); + return lowest; + } std::atomic_bool stop, ponder, stopOnPonderhit; diff --git a/Engines/Linux32/mcbrain/src/thread_win32.h b/Engines/Linux32/mcbrain/src/thread_win32.h index 917563a..d69e817 100644 --- a/Engines/Linux32/mcbrain/src/thread_win32.h +++ b/Engines/Linux32/mcbrain/src/thread_win32.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef THREAD_WIN32_H_INCLUDED #define THREAD_WIN32_H_INCLUDED diff --git a/Engines/Linux32/mcbrain/src/timeman.cpp b/Engines/Linux32/mcbrain/src/timeman.cpp index 330709b..6a854c9 100644 --- a/Engines/Linux32/mcbrain/src/timeman.cpp +++ b/Engines/Linux32/mcbrain/src/timeman.cpp @@ -1,24 +1,27 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include +#include +#include #include "search.h" #include "timeman.h" @@ -30,43 +33,41 @@ namespace { enum TimeType { OptimumTime, MaxTime }; - int remaining(int myTime, int myInc, int moveOverhead, int movesToGo, - int moveNum, bool ponder, TimeType type) { + const int MoveHorizon = 50; // Plan time management at most this many moves ahead + const double MaxRatio = 7.09; // When in trouble, we can step over reserved time with this ratio + const double StealRatio = 0.35; // However we must not steal time from remaining moves over this ratio + - if (myTime <= 0) - return 0; + // move_importance() is a skew-logistic function based on naive statistical + // analysis of "how many games are still undecided after n half-moves". Game + // is considered "undecided" as long as neither side has >275cp advantage. + // Data was extracted from the CCRL game database with some simple filtering criteria. - double ratio; // Which ratio of myTime we are going to use + double move_importance(int ply) { - // Usage of increment follows quadratic distribution with the maximum at move 25 - double inc = myInc * std::max(55.0, 120 - 0.12 * (moveNum - 25) * (moveNum - 25)); + const double XScale = 7.64; + const double XShift = 58.4; + const double Skew = 0.183; - // In moves-to-go we distribute time according to a quadratic function with - // the maximum around move 20 for 40 moves in y time case. - if (movesToGo) - { - ratio = (type == OptimumTime ? 1.0 : 6.0) / std::min(50, movesToGo); + return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero + } + + template + int remaining(int myTime, int movesToGo, int ply, int slowMover) { - if (moveNum <= 40) - ratio *= 1.1 - 0.001 * (moveNum - 20) * (moveNum - 20); - else - ratio *= 1.5; + const double TMaxRatio = (T == OptimumTime ? 1 : MaxRatio); + const double TStealRatio = (T == OptimumTime ? 0 : StealRatio); - ratio *= 1 + inc / (myTime * 8.5); - } - // Otherwise we increase usage of remaining time as the game goes on - else - { - double k = 1 + 20 * moveNum / (500.0 + moveNum); - ratio = (type == OptimumTime ? 0.017 : 0.07) * (k + inc / myTime); - } + double moveImportance = (move_importance(ply) * slowMover) / 100; + double otherMovesImportance = 0; - int time = int(std::min(1.0, ratio) * std::max(0, myTime - moveOverhead)); + for (int i = 1; i < movesToGo; ++i) + otherMovesImportance += move_importance(ply + 2 * i); - if (type == OptimumTime && ponder) - time *= 1.25; + double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance); + double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance); - return time; + return int(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast } } // namespace @@ -81,11 +82,12 @@ namespace { /// inc > 0 && movestogo == 0 means: x basetime + z increment /// inc > 0 && movestogo != 0 means: x moves in y minutes + z increment -void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) -{ - int moveOverhead = Options["Move Overhead"]; - int npmsec = Options["nodestime"]; - bool ponder = Options["Ponder"]; +void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { + + int minThinkingTime = Options["Minimum Thinking Time"]; + int moveOverhead = Options["Move Overhead"]; + int slowMover = Options["Slow Mover"]; + int npmsec = Options["nodestime"]; // If we have to play in 'nodes as time' mode, then convert from time // to nodes, and use resulting values in time management formulas. @@ -102,11 +104,30 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) limits.npmsec = npmsec; } - int moveNum = (ply + 1) / 2; - startTime = limits.startTime; - optimumTime = remaining(limits.time[us], limits.inc[us], moveOverhead, - limits.movestogo, moveNum, ponder, OptimumTime); - maximumTime = remaining(limits.time[us], limits.inc[us], moveOverhead, - limits.movestogo, moveNum, ponder, MaxTime); + optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime); + + const int MaxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon; + + // We calculate optimum time usage for different hypothetical "moves to go"-values + // and choose the minimum of calculated search time values. Usually the greatest + // hypMTG gives the minimum values. + for (int hypMTG = 1; hypMTG <= MaxMTG; ++hypMTG) + { + // Calculate thinking time for hypothetical "moves to go"-value + int hypMyTime = limits.time[us] + + limits.inc[us] * (hypMTG - 1) + - moveOverhead * (2 + std::min(hypMTG, 40)); + + hypMyTime = std::max(hypMyTime, 0); + + int t1 = minThinkingTime + remaining(hypMyTime, hypMTG, ply, slowMover); + int t2 = minThinkingTime + remaining(hypMyTime, hypMTG, ply, slowMover); + + optimumTime = std::min(t1, optimumTime); + maximumTime = std::min(t2, maximumTime); + } + + if (Options["Ponder"]) + optimumTime += optimumTime / 4; } diff --git a/Engines/Linux32/mcbrain/src/timeman.h b/Engines/Linux32/mcbrain/src/timeman.h index c22c8c2..4d3172b 100644 --- a/Engines/Linux32/mcbrain/src/timeman.h +++ b/Engines/Linux32/mcbrain/src/timeman.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef TIMEMAN_H_INCLUDED #define TIMEMAN_H_INCLUDED diff --git a/Engines/Linux32/mcbrain/src/tt.cpp b/Engines/Linux32/mcbrain/src/tt.cpp index f283d0c..1aed0f3 100644 --- a/Engines/Linux32/mcbrain/src/tt.cpp +++ b/Engines/Linux32/mcbrain/src/tt.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include // For std::memset #include @@ -33,7 +34,7 @@ TranspositionTable TT; // Our global transposition table void TranspositionTable::resize(size_t mbSize) { - size_t newClusterCount = size_t(1) << msb((mbSize * 1024 * 1024) / sizeof(Cluster)); + size_t newClusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); if (newClusterCount == clusterCount) return; @@ -41,7 +42,7 @@ void TranspositionTable::resize(size_t mbSize) { clusterCount = newClusterCount; free(mem); - mem = calloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1, 1); + mem = malloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1); if (!mem) { @@ -51,6 +52,7 @@ void TranspositionTable::resize(size_t mbSize) { } table = (Cluster*)((uintptr_t(mem) + CacheLineSize - 1) & ~(CacheLineSize - 1)); + clear(); } diff --git a/Engines/Linux32/mcbrain/src/tt.h b/Engines/Linux32/mcbrain/src/tt.h index 24045ed..e0dc386 100644 --- a/Engines/Linux32/mcbrain/src/tt.h +++ b/Engines/Linux32/mcbrain/src/tt.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef TT_H_INCLUDED #define TT_H_INCLUDED @@ -104,9 +105,9 @@ class TranspositionTable { void resize(size_t mbSize); void clear(); - // The lowest order bits of the key are used to get the index of the cluster + // The 32 lowest order bits of the key are used to get the index of the cluster TTEntry* first_entry(const Key key) const { - return &table[(size_t)key & (clusterCount - 1)].entry[0]; + return &table[(uint32_t(key) * uint64_t(clusterCount)) >> 32].entry[0]; } private: diff --git a/Engines/Linux32/mcbrain/src/types.h b/Engines/Linux32/mcbrain/src/types.h index acca874..7b4318e 100644 --- a/Engines/Linux32/mcbrain/src/types.h +++ b/Engines/Linux32/mcbrain/src/types.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef TYPES_H_INCLUDED #define TYPES_H_INCLUDED @@ -146,7 +147,7 @@ enum CastlingRight { }; template struct MakeCastling { - static const CastlingRight + static constexpr CastlingRight right = C == WHITE ? S == QUEEN_SIDE ? WHITE_OOO : WHITE_OO : S == QUEEN_SIDE ? BLACK_OOO : BLACK_OO; }; @@ -187,7 +188,7 @@ enum Value : int { KnightValueMg = 764, KnightValueEg = 848, BishopValueMg = 826, BishopValueEg = 891, RookValueMg = 1282, RookValueEg = 1373, - QueenValueMg = 2526, QueenValueEg = 2646, + QueenValueMg = 2560, QueenValueEg = 2680, MidgameLimit = 15258, EndgameLimit = 3915 }; @@ -195,6 +196,7 @@ enum Value : int { enum PieceType { NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, ALL_PIECES = 0, + QUEEN_DIAGONAL = 7, PIECE_TYPE_NB = 8 }; @@ -222,7 +224,7 @@ enum Depth : int { static_assert(!(ONE_PLY & (ONE_PLY - 1)), "ONE_PLY is not a power of 2"); -enum Square { +enum Square : int { SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, @@ -233,8 +235,10 @@ enum Square { SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, SQ_NONE, - SQUARE_NB = 64, + SQUARE_NB = 64 +}; +enum Direction : int { NORTH = 8, EAST = 1, SOUTH = -NORTH, @@ -261,7 +265,7 @@ enum Rank : int { /// care to avoid left-shifting a signed int to avoid undefined behavior. enum Score : int { SCORE_ZERO }; -inline Score make_score(int mg, int eg) { +constexpr Score make_score(int mg, int eg) { return Score((int)((unsigned int)eg << 16) + mg); } @@ -269,58 +273,68 @@ inline Score make_score(int mg, int eg) { /// according to the standard a simple cast to short is implementation defined /// and so is a right shift of a signed integer. inline Value eg_value(Score s) { - union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) }; return Value(eg.s); } inline Value mg_value(Score s) { - union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) }; return Value(mg.s); } -#define ENABLE_BASE_OPERATORS_ON(T) \ -inline T operator+(T d1, T d2) { return T(int(d1) + int(d2)); } \ -inline T operator-(T d1, T d2) { return T(int(d1) - int(d2)); } \ -inline T operator-(T d) { return T(-int(d)); } \ -inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; } \ -inline T& operator-=(T& d1, T d2) { return d1 = d1 - d2; } \ - -#define ENABLE_FULL_OPERATORS_ON(T) \ -ENABLE_BASE_OPERATORS_ON(T) \ -inline T operator*(int i, T d) { return T(i * int(d)); } \ -inline T operator*(T d, int i) { return T(int(d) * i); } \ -inline T& operator++(T& d) { return d = T(int(d) + 1); } \ -inline T& operator--(T& d) { return d = T(int(d) - 1); } \ -inline T operator/(T d, int i) { return T(int(d) / i); } \ -inline int operator/(T d1, T d2) { return int(d1) / int(d2); } \ -inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ +#define ENABLE_BASE_OPERATORS_ON(T) \ +constexpr T operator+(T d1, T d2) { return T(int(d1) + int(d2)); } \ +constexpr T operator-(T d1, T d2) { return T(int(d1) - int(d2)); } \ +constexpr T operator-(T d) { return T(-int(d)); } \ +inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; } \ +inline T& operator-=(T& d1, T d2) { return d1 = d1 - d2; } + +#define ENABLE_INCR_OPERATORS_ON(T) \ +inline T& operator++(T& d) { return d = T(int(d) + 1); } \ +inline T& operator--(T& d) { return d = T(int(d) - 1); } + +#define ENABLE_FULL_OPERATORS_ON(T) \ +ENABLE_BASE_OPERATORS_ON(T) \ +ENABLE_INCR_OPERATORS_ON(T) \ +constexpr T operator*(int i, T d) { return T(i * int(d)); } \ +constexpr T operator*(T d, int i) { return T(int(d) * i); } \ +constexpr T operator/(T d, int i) { return T(int(d) / i); } \ +constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ +inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } ENABLE_FULL_OPERATORS_ON(Value) -ENABLE_FULL_OPERATORS_ON(PieceType) -ENABLE_FULL_OPERATORS_ON(Piece) -ENABLE_FULL_OPERATORS_ON(Color) ENABLE_FULL_OPERATORS_ON(Depth) -ENABLE_FULL_OPERATORS_ON(Square) -ENABLE_FULL_OPERATORS_ON(File) -ENABLE_FULL_OPERATORS_ON(Rank) +ENABLE_FULL_OPERATORS_ON(Direction) + +ENABLE_INCR_OPERATORS_ON(PieceType) +ENABLE_INCR_OPERATORS_ON(Piece) +ENABLE_INCR_OPERATORS_ON(Color) +ENABLE_INCR_OPERATORS_ON(Square) +ENABLE_INCR_OPERATORS_ON(File) +ENABLE_INCR_OPERATORS_ON(Rank) ENABLE_BASE_OPERATORS_ON(Score) #undef ENABLE_FULL_OPERATORS_ON +#undef ENABLE_INCR_OPERATORS_ON #undef ENABLE_BASE_OPERATORS_ON /// Additional operators to add integers to a Value -inline Value operator+(Value v, int i) { return Value(int(v) + i); } -inline Value operator-(Value v, int i) { return Value(int(v) - i); } +constexpr Value operator+(Value v, int i) { return Value(int(v) + i); } +constexpr Value operator-(Value v, int i) { return Value(int(v) - i); } inline Value& operator+=(Value& v, int i) { return v = v + i; } inline Value& operator-=(Value& v, int i) { return v = v - i; } +/// Additional operators to add a Direction to a Square +inline Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } +inline Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } +inline Square& operator+=(Square &s, Direction d) { return s = s + d; } +inline Square& operator-=(Square &s, Direction d) { return s = s - d; } + /// Only declared but not defined. We don't want to multiply two scores due to /// a very high risk of overflow. So user should explicitly convert to integer. -inline Score operator*(Score s1, Score s2); +Score operator*(Score, Score) = delete; /// Division of a Score must be handled separately for each term inline Score operator/(Score s, int i) { @@ -339,39 +353,43 @@ inline Score operator*(Score s, int i) { return result; } -inline Color operator~(Color c) { +constexpr Color operator~(Color c) { return Color(c ^ BLACK); // Toggle color } -inline Square operator~(Square s) { +constexpr Square operator~(Square s) { return Square(s ^ SQ_A8); // Vertical flip SQ_A1 -> SQ_A8 } -inline Piece operator~(Piece pc) { +constexpr File operator~(File f) { + return File(f ^ FILE_H); // Horizontal flip FILE_A -> FILE_H +} + +constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT } -inline CastlingRight operator|(Color c, CastlingSide s) { +constexpr CastlingRight operator|(Color c, CastlingSide s) { return CastlingRight(WHITE_OO << ((s == QUEEN_SIDE) + 2 * c)); } -inline Value mate_in(int ply) { +constexpr Value mate_in(int ply) { return VALUE_MATE - ply; } -inline Value mated_in(int ply) { +constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; } -inline Square make_square(File f, Rank r) { +constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); } -inline Piece make_piece(Color c, PieceType pt) { +constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); } -inline PieceType type_of(Piece pc) { +constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } @@ -380,27 +398,27 @@ inline Color color_of(Piece pc) { return Color(pc >> 3); } -inline bool is_ok(Square s) { +constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } -inline File file_of(Square s) { +constexpr File file_of(Square s) { return File(s & 7); } -inline Rank rank_of(Square s) { +constexpr Rank rank_of(Square s) { return Rank(s >> 3); } -inline Square relative_square(Color c, Square s) { +constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); } -inline Rank relative_rank(Color c, Rank r) { +constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); } -inline Rank relative_rank(Color c, Square s) { +constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); } @@ -409,27 +427,27 @@ inline bool opposite_colors(Square s1, Square s2) { return ((s >> 3) ^ s) & 1; } -inline Square pawn_push(Color c) { +constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } -inline Square from_sq(Move m) { +constexpr Square from_sq(Move m) { return Square((m >> 6) & 0x3F); } -inline Square to_sq(Move m) { +constexpr Square to_sq(Move m) { return Square(m & 0x3F); } -inline int from_to(Move m) { +constexpr int from_to(Move m) { return m & 0xFFF; } -inline MoveType type_of(Move m) { +constexpr MoveType type_of(Move m) { return MoveType(m & (3 << 14)); } -inline PieceType promotion_type(Move m) { +constexpr PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + KNIGHT); } @@ -438,11 +456,11 @@ inline Move make_move(Square from, Square to) { } template -inline Move make(Square from, Square to, PieceType pt = KNIGHT) { +constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } -inline bool is_ok(Move m) { +constexpr bool is_ok(Move m) { return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE } diff --git a/Engines/Linux32/mcbrain/src/tzbook.cpp b/Engines/Linux32/mcbrain/src/tzbook.cpp deleted file mode 100644 index 4052256..0000000 --- a/Engines/Linux32/mcbrain/src/tzbook.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "tzbook.h" -#include "uci.h" -#include "movegen.h" -#include "thread.h" -#include -#include "misc.h" -#include - -TZBook tzbook; // global TZBook - -using namespace std; - -int qsort_compare_int(const void* a, const void* b) -{ - const int int_a = *((const int*)a); - const int int_b = *((const int*)b); - - if (int_a == int_b) return 0; - else if (int_a < int_b) return -1; - else return 1; -} - -TZBook::TZBook() -{ - keycount = 0; - book_move2_probability = 0; - last_position = 0; - akt_position = 0; - last_anz_pieces = 0; - akt_anz_pieces = 0; - search_counter = 0; - tzhash2 = NULL; - do_search = true; - enabled = false; -} - -TZBook::~TZBook() -{ -} - - -void TZBook::init(const std::string& path) -{ - if (path.length() == 0) return; - - const char *p = path.c_str(); - if (strcmp(p, "") == 0) - return; - - FILE *fpt = fopen(p, "rb"); - if (fpt == NULL) - { - sync_cout << "info string Could not open " << path << sync_endl; - return; - } - - if (tzhash2 != NULL) - { - free(tzhash2); - tzhash2 = NULL; - } - - fseek(fpt, 0L, SEEK_END); - int filesize = ftell(fpt); - fseek(fpt, 0L, SEEK_SET); - - keycount = filesize / 8; - tzhash2 = new TZHash2[keycount]; - - fread(tzhash2, 1, filesize, fpt); - fclose(fpt); - - sync_cout << "info string Book loaded: " << path << sync_endl; - - srand((int)time(NULL)); - enabled = true; -} - - -void TZBook::set_book_move2_probability(int book_move2_prob) -{ - book_move2_probability = book_move2_prob; -} - - -Move TZBook::probe2(Position& pos) -{ - Move m = MOVE_NONE; - if (!enabled) return m; - - akt_position = pos.pieces(); - akt_anz_pieces = popcount(akt_position); - - if (do_search == false) - { - Bitboard b = akt_position ^ last_position; - int n2 = popcount(b); - - if (n2 > 4) do_search = true; - if (akt_anz_pieces > last_anz_pieces) do_search = true; - if (akt_anz_pieces < last_anz_pieces - 2) do_search = true; - } - - last_position = akt_position; - last_anz_pieces = akt_anz_pieces; - - if (do_search) - { - TZHash2 *tz = probe2(pos.key()); - if (tz == NULL) - { - search_counter++; - if (search_counter > 2) - { - do_search = false; - search_counter = 0; - } - } - else - { - if (pos.is_draw(64)) - m = get_move_from_draw_position(pos, tz); - else - m = get_move(pos, tz); - } - } - - return m; -} - - -TZHash2 *TZBook::probe2(Key key) -{ - uint32_t key1 = key >> 32; - unsigned short key2 = key >> 16 & 0xFFFF; - - int start = 0; - int end = keycount; - - for (;;) - { - int mid = (end + start) / 2; - - if (tzhash2[mid].key1 < key1) - start = mid; - else - { - if (tzhash2[mid].key1 > key1) - end = mid; - else - { - start = max(mid - 4, 0); - end = min(mid + 4, keycount); - } - } - - if (end - start < 9) - break; - } - - for (int i = start; i < end; i++) - if ((key1 == tzhash2[i].key1) && (key2 == tzhash2[i].key2)) - return &(tzhash2[i]); - - return NULL; -} - -Move TZBook::movenumber_to_move(Position& pos, int n) -{ - const ExtMove *m = MoveList(pos).begin(); - size_t size = MoveList(pos).size(); - Move *mv = new Move[size]; - for (unsigned int i = 0; i < size; i++) - mv[i] = m[i].move; - - qsort(mv, size, sizeof(mv[0]), qsort_compare_int); - - return mv[n]; -} - -bool TZBook::check_draw(Move m, Position& pos) -{ - StateInfo st; - - pos.do_move(m, st, pos.gives_check(m)); - bool draw = pos.is_draw(64); - pos.undo_move(m); - - return draw; -} - - -Move TZBook::get_move_from_draw_position(Position& pos, TZHash2 *tz) -{ - Move m = movenumber_to_move(pos, tz->move_number); - if (!check_draw(m, pos)) - return m; - - if (tz->move_number2 == 255) - return m; - - m = movenumber_to_move(pos, tz->move_number2); - if (!check_draw(m, pos)) - return m; - - return MOVE_NONE; -} - - -Move TZBook::get_move(Position& pos, TZHash2 *tz) -{ - Move m1 = movenumber_to_move(pos, tz->move_number); - if ((book_move2_probability == 0) || (tz->move_number2 == 255)) - return m1; - - Move m2 = movenumber_to_move(pos, tz->move_number2); - if ((book_move2_probability == 100) || (rand() % 100 < book_move2_probability)) - return m2; - - return m1; -} \ No newline at end of file diff --git a/Engines/Linux32/mcbrain/src/tzbook.h b/Engines/Linux32/mcbrain/src/tzbook.h deleted file mode 100644 index 744618a..0000000 --- a/Engines/Linux32/mcbrain/src/tzbook.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef TZBOOK_H_INCLUDED -#define TZBOOK_H_INCLUDED - -#include "bitboard.h" -#include "position.h" -#include "string.h" - -struct TZHash2 -{ - uint32_t key1; - uint16_t key2; - unsigned char move_number; - unsigned char move_number2; -}; - -class TZBook -{ - public: - - Bitboard last_position; - Bitboard akt_position; - int last_anz_pieces; - int akt_anz_pieces; - int search_counter; - - bool enabled, do_search; - int book_move2_probability; - - TZBook(); - ~TZBook(); - - void init(const std::string& path); - void set_book_move2_probability(int book_move2_prob); - - Move probe2(Position& pos); - TZHash2 *probe2(Key key); - -private: - - int keycount; - TZHash2 *tzhash2; - bool check_draw(Move m, Position& pos); - Move get_move_from_draw_position(Position& pos, TZHash2 *tz); - Move get_move(Position& pos, TZHash2 *tz); - Move movenumber_to_move(Position& pos, int n); -}; - -extern TZBook tzbook; - -#endif // #ifndef TZBOOK_H_INCLUDED \ No newline at end of file diff --git a/Engines/Linux32/mcbrain/src/uci.cpp b/Engines/Linux32/mcbrain/src/uci.cpp index 0b9c680..40efb6d 100644 --- a/Engines/Linux32/mcbrain/src/uci.cpp +++ b/Engines/Linux32/mcbrain/src/uci.cpp @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include #include @@ -103,7 +104,33 @@ namespace { else sync_cout << "No such option: " << name << sync_endl; } - + + // set() is called by typing "s" from the terminal when the user wants to use abbreviated + // non-UCI comamnds and avoid the uci option protocol "setoption name (option name) value (xxx) ", + // e.g., instead of typing "setoption name threads value 8" to set cores to 8 at the terminal, + // the user simply types "s threads 8" - restricted to option names that do not contain + // any white spaces - see ucioption.cpp. The argument can take white spaces e.g., + // "s syzygypath /endgame tablebases/syzygy" will work + void set(istringstream& is) { + string token, name, value; + + // Read option name (no white spaces in option name) + is >> token; + name = token; + + // Read option value (can contain white spaces) + while (is >> token) + value += string(" ", value.empty() ? 0 : 1) + token; + + // provide user confirmation + if (Options.count(name)) { + Options[name] = value; + sync_cout << "Confirmation: "<< name << " set to " << value << sync_endl; + + } + else + sync_cout << "No such option: " << name << sync_endl; + } // go() is called when engine receives the "go" UCI command. The function sets // the thinking time and other parameters from the input string, then starts @@ -132,6 +159,7 @@ namespace { else if (token == "nodes") is >> limits.nodes; else if (token == "movetime") is >> limits.movetime; else if (token == "mate") is >> limits.mate; + else if (token == "perft") is >> limits.perft; else if (token == "infinite") limits.infinite = 1; else if (token == "i") limits.infinite = 1; else if (token == "ponder") ponderMode = true;; @@ -167,6 +195,7 @@ namespace { nodes += Threads.nodes_searched(); } else if (token == "setoption") setoption(is); + else if (token == "s") set(is); else if (token == "position") position(pos, is, states); else if (token == "ucinewgame") Search::clear(); } @@ -222,6 +251,7 @@ void UCI::loop(int argc, char* argv[]) { if ( token == "quit" || token == "q" || token == "stop" + || token == "?" || (token == "ponderhit" && Threads.stopOnPonderhit)) Threads.stop = true; @@ -235,19 +265,21 @@ void UCI::loop(int argc, char* argv[]) { else if (token == "setoption") setoption(is); else if (token == "so") setoption(is); + else if (token == "set") set(is); + else if (token == "s") set(is); else if (token == "go") go(pos, is, states); else if (token == "g") go(pos, is, states); else if (token == "q") cmd = "quit"; else if (token == "position") { position(pos, is, states); - if (Options["Clean Search"] == 1) + if (Options["Clean_Search"] == true) Search::clear(); } else if (token == "p") { position(pos, is, states); - if (Options["Clean Search"] == 1) + if (Options["Clean_Search"] == true) Search::clear(); } else if (token == "ucinewgame") Search::clear(); @@ -256,7 +288,7 @@ void UCI::loop(int argc, char* argv[]) { // Additional custom non-UCI commands, mainly for debugging else if (token == "flip") pos.flip(); else if (token == "bench") bench(pos, is, states); - else if (token == "b") bench(pos, is, states); + else if (token == "b") bench(pos, is, states); else if (token == "d") sync_cout << pos << sync_endl; else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; else diff --git a/Engines/Linux32/mcbrain/src/uci.h b/Engines/Linux32/mcbrain/src/uci.h index e6b31c5..713468b 100644 --- a/Engines/Linux32/mcbrain/src/uci.h +++ b/Engines/Linux32/mcbrain/src/uci.h @@ -1,22 +1,23 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #ifndef UCI_H_INCLUDED #define UCI_H_INCLUDED @@ -50,11 +51,13 @@ class Option { Option(bool v, OnChange = nullptr); Option(const char* v, OnChange = nullptr); Option(int v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char *cur, OnChange = nullptr); Option& operator=(const std::string&); void operator<<(const Option&); operator int() const; operator std::string() const; + bool operator==(const char*); private: friend std::ostream& operator<<(std::ostream&, const OptionsMap&); diff --git a/Engines/Linux32/mcbrain/src/ucioption.cpp b/Engines/Linux32/mcbrain/src/ucioption.cpp index abc5fba..3c31396 100644 --- a/Engines/Linux32/mcbrain/src/ucioption.cpp +++ b/Engines/Linux32/mcbrain/src/ucioption.cpp @@ -1,32 +1,33 @@ /* - Stockfish, a UCI chess playing engine derived from Glaurung 2.1 - Copyright (C) 2004-2008 Tord Romstad (Glaurung author) - Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2017 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad - - Stockfish is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Stockfish is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ #include #include #include #include "misc.h" +#include "polybook.h" #include "search.h" #include "thread.h" #include "tt.h" -#include "tzbook.h" #include "uci.h" #include "tbprobe.h" @@ -42,8 +43,9 @@ void on_hash_size(const Option& o) { TT.resize(o); } void on_logger(const Option& o) { start_logger(o); } void on_threads(const Option& o) { Threads.set(o); } void on_tb_path(const Option& o) { Tablebases::init(o); } -void on_brainbook_path(const Option& o) { tzbook.init(o); } -void on_book_move2_prob(const Option& o) { tzbook.set_book_move2_probability(o); } +void on_book_file(const Option& o) { polybook.init(o); } +void on_best_book_move(const Option& o) { polybook.set_best_book_move(o); } +void on_book_depth(const Option& o) { polybook.set_book_depth(o); } /// Our case insensitive less() function as required by UCI protocol @@ -58,35 +60,46 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const void init(OptionsMap& o) { - const int MaxHashMB = Is64Bit ? 1024 * 1024 : 2048; + // at most 2^32 clusters. + const int MaxHashMB = Is64Bit ? 131072 : 2048; + o["Debug Log File"] << Option("", on_logger); + o["Contempt"] << Option(20, -100, 100); + o["Analysis Contempt"] << Option("Off var Off var White var Black var Both", "Off"); + o["UCI_AnalyseMode"] << Option(false); + o["Tactical"] << Option(0, 0, 8); + o["Threads"] << Option(1, 1, 512, on_threads); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Ponder"] << Option(false); o["Threads"] << Option(1, 1, 512, on_threads); - - o["Clear Hash"] << Option(on_clear_hash); - o["Clean Search"] << Option(false); + o["Clear_Hash"] << Option(on_clear_hash); + o["Clean_Search"] << Option(false); o["BruteForce"] << Option(false); o["FastPlay"] << Option(false); o["No_Null_Moves"] << Option(false); + o["UCI_Chess960"] << Option(false); o["UCI_LimitStrength"] << Option(false); - o["UCI_ELO"] << Option(1500, 1500, 2800); - o["Book Move2 Probability"]<< Option(0, 0, 100, on_book_move2_prob); - o["BookPath"] << Option("", on_brainbook_path); - o["Respect"] << Option(0, -300, 300); - o["Respect White POV"] << Option(0, -300, 300); - o["Tactical"] << Option(0, 0, 8); - o["MultiPV"] << Option(1, 1, 500); + o["UCI_ELO"] << Option(1500, 1500, 2800); o["Skill Level"] << Option(20, 0, 20); - o["Move Overhead"] << Option(60, 0, 5000); + + o["MultiPV"] << Option(1, 1, 256); + + + o["Move Overhead"] << Option(30, 0, 5000); + o["Minimum Thinking Time"] << Option(20, 0, 5000); + o["Slow Mover"] << Option(89, 10, 1000); o["nodestime"] << Option(0, 0, 10000); - o["UCI_Chess960"] << Option(false); + o["SyzygyPath"] << Option("", on_tb_path); o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(6, 0, 6); - o["Debug Log File"] << Option("", on_logger); + o["Book_Enabled"] << Option(false); + o["BookFile"] << Option("", on_book_file); + o["BestBookMove"] << Option(true, on_best_book_move); + o["BookDepth"] << Option(255, 1, 255, on_book_depth); + o["Debug"] << Option("", on_logger); } @@ -129,6 +142,9 @@ Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f) Option::Option(int v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f) { defaultValue = currentValue = std::to_string(v); } +Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f) +{ defaultValue = v; currentValue = cur; } + Option::operator int() const { assert(type == "check" || type == "spin"); return (type == "spin" ? stoi(currentValue) : currentValue == "true"); @@ -139,6 +155,11 @@ Option::operator std::string() const { return currentValue; } +bool Option::operator==(const char* s) { + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); +} + /// operator<<() inits options and assigns idx in the correct printing order diff --git a/Engines/Linux64/mcbrain/.travis.yml b/Engines/Linux64/mcbrain/.travis.yml index 87a3e7d..0a214d1 100644 --- a/Engines/Linux64/mcbrain/.travis.yml +++ b/Engines/Linux64/mcbrain/.travis.yml @@ -18,11 +18,12 @@ matrix: compiler: clang addons: apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['clang', 'g++-multilib', 'valgrind', 'expect'] + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] + packages: ['clang-5.0', 'llvm-5.0-dev', 'g++-multilib', 'valgrind', 'expect'] env: - - COMPILER=clang++ + - COMPILER=clang++-5.0 - COMP=clang + - LDFLAGS=-fuse-ld=gold - os: osx compiler: gcc @@ -44,30 +45,30 @@ before_script: - cd src script: - # - # checking bench for various build types - # - # obtain reference from git log - - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9][0-9]*\)/\1/g" > git_sig + # Obtain bench reference from git log + - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig - export benchref=$(cat git_sig) - echo "Reference bench:" $benchref - # verify against reference - - make clean && make ARCH=x86-64 build > /dev/null && ../tests/signature.sh $benchref - - make clean && make ARCH=x86-32 build > /dev/null && ../tests/signature.sh $benchref - # - # perft - # - - make clean && make ARCH=x86-64 build > /dev/null && ../tests/perft.sh # - # reproducible search + # Verify bench number against various builds + - export CXXFLAGS=-Werror + - make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-64 build && ../tests/signature.sh $benchref # - - make clean && make ARCH=x86-64 build > /dev/null && ../tests/reprosearch.sh + # Check perft and reproducible search + - ../tests/perft.sh + - ../tests/reprosearch.sh # - # valgrind + # Valgrind # - - if [ -x "$(command -v valgrind )" ]; then make clean && make ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi + - export CXXFLAGS=-O1 + - if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi + - if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi # - # sanitizer + # Sanitizer # - # use g++-6 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc - - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make ARCH=x86-64 sanitize=yes build > /dev/null && ../tests/instrumented.sh --sanitizer; fi + # Use g++-6 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=thread optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi diff --git a/Engines/Linux64/mcbrain/AUTHORS b/Engines/Linux64/mcbrain/AUTHORS index 3095efc..9b91b93 100644 --- a/Engines/Linux64/mcbrain/AUTHORS +++ b/Engines/Linux64/mcbrain/AUTHORS @@ -1,6 +1,5 @@ # Generated with 'git shortlog -sn | cut -c8-', which sorts by commits, manually ordered the first four authors, merged duplicates -Michael Byrne (McBrain) Tord Romstad Marco Costalba (mcostalba) Joona Kiiski (zamar) @@ -95,4 +94,5 @@ ppigazzini renouve sf-x thaspel +unknown diff --git a/Engines/Linux64/mcbrain/McBrain-9_x64_linux b/Engines/Linux64/mcbrain/McBrain-9_x64_linux new file mode 100644 index 0000000..26fd9a3 Binary files /dev/null and b/Engines/Linux64/mcbrain/McBrain-9_x64_linux differ diff --git a/Engines/Linux64/mcbrain/Readme.md b/Engines/Linux64/mcbrain/Readme.md index e3b36cc..46f2609 100644 --- a/Engines/Linux64/mcbrain/Readme.md +++ b/Engines/Linux64/mcbrain/Readme.md @@ -98,9 +98,9 @@ for a quick reference. ### Resource For Understanding the Code Base -* [Chessprogramingwiki](https://chessprogramming.wikispaces.com) has good overall chess engines explanations +* [Chess Programming Wiki](https://chessprogramming.wikispaces.com) has good overall chess engines explanations (techniques used here are well explained like hash maps etc), it was -also recommended by the [support at stockfish.](http://support.stockfishchess.org/discussions/questions/1132-how-to-understand-stockfish-sources) +also recommended by the [support team at stockfish.](http://support.stockfishchess.org/discussions/questions/1132-how-to-understand-stockfish-sources) * [Here](https://chessprogramming.wikispaces.com/Stockfish) you can find a set of features and techniques used by stockfish and each of them is explained at the wiki, however, it's a generic way rather than focusing on stockfish's own implementation, but it will still help you. diff --git a/Engines/Linux64/mcbrain/SF-McBrain-v40_x64_linux b/Engines/Linux64/mcbrain/SF-McBrain-v40_x64_linux deleted file mode 100644 index 0979c1e..0000000 Binary files a/Engines/Linux64/mcbrain/SF-McBrain-v40_x64_linux and /dev/null differ diff --git a/Engines/Linux64/mcbrain/Top CPU Contributors.txt b/Engines/Linux64/mcbrain/Top CPU Contributors.txt index 0bb2a92..4c2aa47 100644 --- a/Engines/Linux64/mcbrain/Top CPU Contributors.txt +++ b/Engines/Linux64/mcbrain/Top CPU Contributors.txt @@ -1,93 +1,132 @@ -Contributors with >10,000 CPU hours as of November 3, 2016 +Contributors with >10,000 CPU hours as of January 23, 2018 Thank you! -Username CPU Hours Games played -cw 220301 16924200 -glinscott 186639 13936027 -fastgm 184045 14608140 -mibere 165859 13563572 -crunchy 160974 14091929 -spams 143806 10956698 -bking_US 136938 10558137 -dsmith 103332 7622414 -BrunoBanani 100008 7448565 -ctoks 99216 7989224 -JojoM 96528 8138437 -vdbergh 88372 6322455 -drabel 75214 6034715 -velislav 71485 5483953 -sqrt2 70825 5471595 -BRAVONE 66265 5356681 -malala 57618 4480635 -psk 54292 4337164 -leszek 52415 4254611 -marrco 51573 4132787 -Freja 48348 3773248 -Thanar 47723 4062940 -Fisherman 46361 3865994 -renouve 46003 3544864 -CSU_Dynasty 45136 4096148 -rap 44619 3219490 -dv8silencer 44175 3961325 -tinker 43975 3261777 -tvijlbrief 42291 2965762 -sunu 41289 3172937 -mhunt 38278 2697512 -Antihistamine 37735 2795761 -finfish 36001 2734928 -brabos 32630 2566008 -jromang 32044 2166097 -robnjr 31781 2726352 -CoffeeOne 29940 2597953 -sterni1971 28924 2737221 -EthanOConnor 28429 2143255 -Pyafue 27266 1986098 -jkiiski 27009 1925255 -biffhero 26557 2033420 -nssy 25231 2037166 -mgrabiak 24000 1974653 -slakovv 23548 2031279 -Sharaf_DG 22175 1790697 -homyur 21415 1705644 -team-oh 20347 1653708 -Zirie 20204 1493227 -nabildanial 19538 1586321 -nesoneg 19306 1493435 -cuistot 19105 1387031 -Patrick_G 19027 1406466 -mhoram 18304 1396701 -rkl 17566 1409460 -ville 17541 1540130 -oryx 17480 1578240 -rstoesser 17264 1335177 -xor12 16786 1492708 -jundery 16786 1115855 -bigpen0r 16700 1287118 -iisiraider 16366 1089410 -davar 16266 1328093 -vdv 16072 1629971 -VoyagerOne 16049 1485459 -Bobo1239 15837 1550883 -DragonLord 15791 1251348 -purplefishies 15602 1106850 -Isidor 14598 1317485 -speedycpu 14215 874201 -OssumOpossum 14078 1029265 -enedene 13378 935618 -bpfliegel 12944 886523 -AdrianSA 12921 924980 -JanErik 12782 1106788 -dju 12600 901552 -jpulman 12015 854815 -ttruscott 11929 976348 -fatmurphy 11726 901134 -ElbertoOne 11641 1082697 -j3corre 11638 973654 -chris 11450 1228430 -pb00067 11248 1021031 -modolief 11185 926456 -Dark_wizzie 10933 1017910 -SC 10637 925516 -Thomas A. 10485 736094 -mschmidt 10354 818594 -infinity 10020 746397 +Username CPU Hours Games played +mibere 518300 41835669 +crunchy 375564 29121434 +cw 371664 28748719 +fastgm 299773 20765374 +JojoM 220590 15299913 +glinscott 204517 13932027 +bking_US 187568 12233168 +ctoks 169342 13475495 +spams 149531 10940322 +Thanar 137015 11714855 +velislav 127305 10047749 +vdbergh 121741 9056874 +malala 117291 8126488 +vdv 117218 8289983 +leszek 114825 8331897 +dsmith 114010 7622414 +CSU_Dynasty 113516 9582758 +sqrt2 112407 8782694 +marrco 111143 8222921 +drabel 108168 9061580 +BrunoBanani 104938 7448565 +Data 94621 8433010 +CoffeeOne 90394 3964243 +BRAVONE 80811 5341681 +psk 77195 6156031 +brabos 70284 5685893 +Fisherman 66650 5572406 +nssy 64587 5369140 +Pking_cda 64499 5704075 +sterni1971 63488 5070004 +mgrabiak 62385 5420812 +tvijlbrief 58957 4154234 +jromang 58854 4704502 +dv8silencer 57421 3961325 +sunu 56620 4609155 +tinker 56039 4204914 +biffhero 55743 4810039 +teddybaer 52982 4740444 +bcross 50548 5071599 +renouve 50318 3544864 +Freja 50296 3805120 +robnjr 47504 4131742 +eva42 46542 4044694 +davar 46538 4030604 +finfish 46244 3481661 +rap 46201 3219490 +ttruscott 45037 3645430 +solarlight 44155 4074841 +TueRens 41372 3891510 +ElbertoOne 41321 3920894 +Antihistamine 39218 2792761 +mhunt 38991 2697512 +bigpen0r 37820 3149955 +homyur 35569 3009637 +VoyagerOne 35137 3302650 +mhoram 34770 2684128 +racerschmacer 33022 3231055 +speedycpu 32043 2531964 +EthanOConnor 31638 2143255 +oryx 29574 2767730 +Pyafue 28885 1986098 +jkiiski 28014 1923255 +Garf 27579 2770144 +slakovv 27017 2031279 +Bobo1239 27000 2488707 +pb00067 26817 2306694 +robal 26337 2316795 +hyperbolic.tom 26248 2200777 +rkl 24898 2236013 +SC 23988 2126825 +nabildanial 23524 1586321 +achambord 23495 1942546 +Sharaf_DG 22975 1790697 +chriswk 22876 1947731 +anst 22568 2013953 +Patrick_G 22435 1682293 +cuistot 22201 1383031 +gri 21901 1820968 +Prcuvu 21182 1890546 +Zirie 21171 1493227 +JanErik 20596 1791991 +Isidor 20560 1730290 +xor12 20535 1819280 +team-oh 20364 1653708 +nesoneg 20264 1493435 +rstoesser 19802 1335177 +grandphish2 19402 1834196 +sg4032 18427 1671742 +dew 18263 1423326 +ianh2105 18133 1668562 +MazeOfGalious 18022 1644593 +ville 17900 1539130 +j3corre 17607 975954 +eudhan 17502 1424648 +iisiraider 17175 1118788 +jundery 17172 1115855 +SFTUser 16635 1363975 +purplefishies 16621 1106850 +DragonLord 16599 1252348 +chris 15274 1575333 +xoto 14900 1486261 +dju 14861 901552 +dex 14647 1228763 +nordlandia 14551 1369718 +ronaldjerum 14361 1210607 +OssumOpossum 14149 1029265 +IgorLeMasson 13844 1228391 +enedene 13762 935618 +ako027ako 13442 1250249 +AdrianSA 13324 924980 +bpfliegel 13318 886523 +ncfish1 13056 932344 +wei 12863 1369596 +jpulman 12776 854815 +horst.prack 12436 1151505 +joster 12424 986622 +cisco2015 12265 1205019 +fatmurphy 12015 901134 +modolief 11228 926456 +Dark_wizzie 11214 1017910 +mschmidt 10973 818594 +eastorwest 10970 1117836 +infinity 10762 746397 +SapphireBrand 10692 1024604 +Thomas A. 10553 736094 +pgontarz 10294 878746 +Andrew Grant 10195 922933 +stocky 10083 718114 diff --git a/Engines/Linux64/mcbrain/appveyor.yml b/Engines/Linux64/mcbrain/appveyor.yml index a46a0f3..c711dd6 100644 --- a/Engines/Linux64/mcbrain/appveyor.yml +++ b/Engines/Linux64/mcbrain/appveyor.yml @@ -1,5 +1,5 @@ version: 1.0.{build} -clone_depth: 5 +clone_depth: 50 branches: only: @@ -13,10 +13,11 @@ os: Visual Studio 2015 platform: - x86 - x64 - - Any CPU # build Configuration, i.e. Debug, Release, etc. -configuration: Debug +configuration: + - Debug + - Release matrix: # The build fail immediately once one of the job fails @@ -28,18 +29,43 @@ init: - msbuild /version before_build: - - cd src - - echo project (Stockfish) >> CMakeLists.txt - - echo add_executable(stockfish benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp >> CMakeLists.txt - - echo main.cpp material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp >> CMakeLists.txt - - echo search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp syzygy/tbprobe.cpp) >> CMakeLists.txt - - echo set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src) >> CMakeLists.txt -# - echo target_compile_options(stockfish PUBLIC "/Ox") >> CMakeLists.txt + - ps: | + # Get sources + $src = get-childitem -Path *.cpp -Recurse | select -ExpandProperty FullName + $src = $src -join ' ' + $src = $src.Replace("\", "/") + + # Build CMakeLists.txt + $t = 'cmake_minimum_required(VERSION 3.8)', + 'project(Stockfish)', + 'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)', + 'set(source_files', $src, ')', + 'add_executable(stockfish ${source_files})' + + # Write CMakeLists.txt withouth BOM + $MyPath = (Get-Item -Path "." -Verbose).FullName + '\CMakeLists.txt' + $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False + [System.IO.File]::WriteAllLines($MyPath, $t, $Utf8NoBomEncoding) + + # Obtain bench reference from git log + $b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1 + $bench = $b -match '\D+(\d+)' | % { $matches[1] } + Write-Host "Reference bench:" $bench + $g = "Visual Studio 14 2015" + If (${env:PLATFORM} -eq 'x64') { $g = $g + ' Win64' } + cmake -G "${g}" . + Write-Host "Generated files for: " $g build_script: - - cmake -G "Visual Studio 14 2015 Win64" . - - cmake --build . + - cmake --build . --config %CONFIGURATION% -- /verbosity:minimal before_test: - - cd Debug - - stockfish.exe bench > null + - cd src/%CONFIGURATION% + - ps: | + # Verify bench number + ./stockfish bench 2> out.txt 1> null + $s = (gc "./out.txt" | out-string) + $r = ($s -match 'Nodes searched \D+(\d+)' | % { $matches[1] }) + Write-Host "Engine bench:" $r + Write-Host "Reference bench:" $bench + If ($r -ne $bench) { exit 1 } diff --git a/Engines/Linux64/mcbrain/src/Makefile b/Engines/Linux64/mcbrain/src/Makefile index f1d5dec..6e66fbb 100644 --- a/Engines/Linux64/mcbrain/src/Makefile +++ b/Engines/Linux64/mcbrain/src/Makefile @@ -5,7 +5,7 @@ # Copyright (C) 2004-2008 Tord Romstad (Glaurung Author) # Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) # Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) -# Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) +# Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) # McBrain is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,8 +30,8 @@ ifeq ($(KERNEL),Linux) endif ### Executable name -EXE = SF-McBrain-v40 -### bench nodes -> Nodes searched : 6376731 +EXE = McBrain-9 +### bench nodes -> Nodes searched : 6871658 ### Installation dir definitions PREFIX = /usr/local @@ -156,7 +156,7 @@ endif ifeq ($(COMP),gcc) comp=gcc CXX=g++ - CXXFLAGS += -pedantic -Wextra -Wshadow -static-libstdc++ + CXXFLAGS += -pedantic -Wextra -Wshadow ifeq ($(ARCH),armv7) ifeq ($(OS),Android) @@ -280,9 +280,7 @@ endif ### 3.3 Optimization ifeq ($(optimize),yes) -CXXFLAGS += -O1 -O3 -msse4.2 -mpopcnt -mtune=native -####-Os -O1 -mtune=native -#### for those on macOS High Sierra 10.13 and higher use, "-Os -O1 -mtune=native" for PGO using gcc7 + CXXFLAGS += -O3 -mtune=native ifeq ($(comp),gcc) @@ -308,8 +306,6 @@ CXXFLAGS += -O1 -O3 -msse4.2 -mpopcnt -mtune=native ifeq ($(comp),clang) ifeq ($(KERNEL),Darwin) - CXXFLAGS += -flto - LDFLAGS += $(CXXFLAGS) ifeq ($(arch),i386) CXXFLAGS += -mdynamic-no-pic endif @@ -355,24 +351,20 @@ endif ### 3.8 Link Time Optimization, it works since gcc 4.5 but not on mingw under Windows. ### This is a mix of compile and link time options because the lto link phase ### needs access to the optimization flags. -ifeq ($(comp),gcc) - ifeq ($(optimize),yes) - ifeq ($(debug),no) +ifeq ($(optimize),yes) +ifeq ($(debug), no) + ifeq ($(comp),$(filter $(comp),gcc clang)) CXXFLAGS += -flto LDFLAGS += $(CXXFLAGS) endif - endif -endif -ifeq ($(comp),mingw) + ifeq ($(comp),mingw) ifeq ($(KERNEL),Linux) - ifeq ($(optimize),yes) - ifeq ($(debug),no) CXXFLAGS += -flto LDFLAGS += $(CXXFLAGS) endif endif - endif +endif endif ### 3.9 Android 5 can only run position independent executables. Note that this @@ -440,7 +432,6 @@ help: build: config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all strip $(EXE) - cp $(EXE) /Users/michaelbyrne/cluster.mfb/$(EXE) profile-build: config-sanity objclean profileclean @echo "" @@ -460,7 +451,12 @@ profile-build: config-sanity objclean profileclean @echo "Step 5/5. Deleting profile data ..." $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean strip $(EXE) - cp $(EXE) /Users/michaelbyrne/cluster.mfb/$(EXE) + +cprom: + make -j profile-build ARCH=x86-64-modern COMP=clang + +cprob: + make -j profile-build ARCH=x86-64-bmi2 COMP=clang strip: strip $(EXE) diff --git a/Engines/Linux64/mcbrain/src/benchmark.cpp b/Engines/Linux64/mcbrain/src/benchmark.cpp index f5499ef..807f9de 100644 --- a/Engines/Linux64/mcbrain/src/benchmark.cpp +++ b/Engines/Linux64/mcbrain/src/benchmark.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/bitbase.cpp b/Engines/Linux64/mcbrain/src/bitbase.cpp index ae5c879..a651e27 100644 --- a/Engines/Linux64/mcbrain/src/bitbase.cpp +++ b/Engines/Linux64/mcbrain/src/bitbase.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/bitboard.cpp b/Engines/Linux64/mcbrain/src/bitboard.cpp index d84d447..9dd637b 100644 --- a/Engines/Linux64/mcbrain/src/bitboard.cpp +++ b/Engines/Linux64/mcbrain/src/bitboard.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/bitboard.h b/Engines/Linux64/mcbrain/src/bitboard.h index 598bbbc..c964d1e 100644 --- a/Engines/Linux64/mcbrain/src/bitboard.h +++ b/Engines/Linux64/mcbrain/src/bitboard.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/endgame.cpp b/Engines/Linux64/mcbrain/src/endgame.cpp index 2bf6501..3d568f5 100644 --- a/Engines/Linux64/mcbrain/src/endgame.cpp +++ b/Engines/Linux64/mcbrain/src/endgame.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/endgame.h b/Engines/Linux64/mcbrain/src/endgame.h index 3bc3a02..3bace6f 100644 --- a/Engines/Linux64/mcbrain/src/endgame.h +++ b/Engines/Linux64/mcbrain/src/endgame.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify diff --git a/Engines/Linux64/mcbrain/src/evaluate.cpp b/Engines/Linux64/mcbrain/src/evaluate.cpp index a7d304a..e420d3d 100644 --- a/Engines/Linux64/mcbrain/src/evaluate.cpp +++ b/Engines/Linux64/mcbrain/src/evaluate.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -98,6 +98,7 @@ namespace { template void initialize(); template Score evaluate_king(); template Score evaluate_threats(); + int king_distance(Color c, Square s); template Score evaluate_passed_pawns(); template Score evaluate_space(); template Score evaluate_pieces(); @@ -112,7 +113,8 @@ namespace { Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; // attackedBy[color][piece type] is a bitboard representing all squares - // attacked by a given color and piece type (can be also ALL_PIECES). + // attacked by a given color and piece type. Special "piece types" which are + // also calculated are QUEEN_DIAGONAL and ALL_PIECES. Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; // attackedBy2[color] are the squares attacked by 2 pieces of a given color, @@ -184,23 +186,23 @@ namespace { // which piece type attacks which one. Attacks on lesser pieces which are // pawn-defended are not considered. const Score ThreatByMinor[PIECE_TYPE_NB] = { - S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72, 107), S(48, 118) - }; + S(0, 0), S(0, 31), S(39, 42), S(57, 44), S(68, 112), S(47, 120) + }; //Fauzi Akram const Score ThreatByRook[PIECE_TYPE_NB] = { - S(0, 0), S(0, 25), S(40, 62), S(40, 59), S(0, 34), S(35, 48) - }; + S(0, 0), S(0, 24), S(38, 71), S(38, 61), S(0, 38), S(36, 38) + }; //Fauzi Akram // ThreatByKing[on one/on many] contains bonuses for king attacks on // pawns or pieces which are not pawn-defended. - const Score ThreatByKing[] = { S(3, 62), S(9, 138) }; + const Score ThreatByKing[] = { S(3, 65), S(9, 145) }; //Fauzi Akram // Passed[mg/eg][Rank] contains midgame and endgame bonuses for passed pawns. // We don't use a Score because we process the two components independently. const Value Passed[][RANK_NB] = { - { V(5), V( 5), V(31), V(73), V(166), V(252) }, - { V(7), V(14), V(38), V(73), V(166), V(252) } - }; + { V(0), V(5), V( 5), V(32), V(70), V(172), V(217) }, + { V(0), V(7), V(13), V(42), V(70), V(170), V(269) } + }; //Fauzi Akram // PassedFile[File] contains a bonus according to the file of a passed pawn const Score PassedFile[FILE_NB] = { @@ -208,6 +210,9 @@ namespace { S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10) }; + // Rank factor applied on some bonus for passed pawn on rank 4 or beyond + const int RankFactor[RANK_NB] = {0, 0, 0, 2, 7, 12, 19}; //Fauzi Akram + // KingProtector[PieceType-2] contains a bonus according to distance from king const Score KingProtector[] = { S(-3, -5), S(-4, -3), S(-3, 0), S(-1, 1) }; @@ -218,17 +223,15 @@ namespace { const Score RookOnPawn = S( 8, 24); const Score TrappedRook = S( 92, 0); const Score WeakQueen = S( 50, 10); - const Score OtherCheck = S( 10, 10); const Score CloseEnemies = S( 7, 0); const Score PawnlessFlank = S( 20, 80); - const Score ThreatByHangingPawn = S( 71, 61); - const Score ThreatBySafePawn = S(192,175); + const Score ThreatBySafePawn = S(175,168); //Fauzi Akram const Score ThreatByRank = S( 16, 3); - const Score Hanging = S( 48, 27); + const Score Hanging = S( 52, 30); //Fauzi Akram const Score WeakUnopposedPawn = S( 5, 25); - const Score ThreatByPawnPush = S( 38, 22); - const Score ThreatByAttackOnQueen = S( 38, 22); - const Score HinderPassedPawn = S( 7, 0); + const Score ThreatByPawnPush = S( 47, 26); //Fauzi Akram + const Score ThreatByAttackOnQueen = S( 42, 21); //Fauzi Akram + const Score HinderPassedPawn = S( 8, 1); //Fauzi Akram const Score TrappedBishopA1H1 = S( 50, 50); #undef S @@ -238,10 +241,10 @@ namespace { const int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 78, 56, 45, 11 }; // Penalties for enemy's safe checks - const int QueenCheck = 780; - const int RookCheck = 880; - const int BishopCheck = 435; - const int KnightCheck = 790; + const int QueenSafeCheck = 780; + const int RookSafeCheck = 880; + const int BishopSafeCheck = 435; + const int KnightSafeCheck = 790; // Threshold for lazy and space evaluation const Value LazyThreshold = Value(1500); @@ -312,8 +315,8 @@ namespace { while ((s = *pl++) != SQ_NONE) { // Find attacked squares, including x-ray attacks for bishops and rooks - b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(Us, QUEEN)) - : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(Us, ROOK, QUEEN)) + b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) + : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) : pos.attacks_from(s); if (pos.pinned_pieces(Us) & s) @@ -428,17 +431,29 @@ namespace { Score Evaluation::evaluate_king() { const Color Them = (Us == WHITE ? BLACK : WHITE); - const Direction Up = (Us == WHITE ? NORTH : SOUTH); const Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB); const Square ksq = pos.square(Us); - Bitboard weak, b, b1, b2, safe, other; - int kingDanger; + Bitboard weak, b, b1, b2, safe, unsafeChecks; // King shelter and enemy pawns storm Score score = pe->king_safety(pos, ksq); + // King tropism: firstly, find squares that opponent attacks in our king flank + File kf = file_of(ksq); + b = attackedBy[Them][ALL_PIECES] & KingFlank[kf] & Camp; + + assert(((Us == WHITE ? b << 4 : b >> 4) & b) == 0); + assert(popcount(Us == WHITE ? b << 4 : b >> 4) == popcount(b)); + + // Secondly, add the squares which are attacked twice in that flank and + // which are not defended by our pawns. + b = (Us == WHITE ? b << 4 : b >> 4) + | (b & attackedBy2[Them] & ~attackedBy[Us][PAWN]); + + int tropism = popcount(b); + // Main king safety evaluation if (kingAttackersCount[Them] > (1 - pos.count(Them))) { @@ -447,18 +462,7 @@ namespace { & ~attackedBy2[Us] & (attackedBy[Us][KING] | attackedBy[Us][QUEEN] | ~attackedBy[Us][ALL_PIECES]); - // Initialize the 'kingDanger' variable, which will be transformed - // later into a king danger score. The initial value is based on the - // number and types of the enemy's attacking pieces, the number of - // attacked and weak squares around our king, the absence of queen and - // the quality of the pawn shelter (current 'score' value). - kingDanger = kingAttackersCount[Them] * kingAttackersWeight[Them] - + 102 * kingAdjacentZoneAttacksCount[Them] - + 191 * popcount(kingRing[Us] & weak) - + 143 * bool(pos.pinned_pieces(Us)) - - 848 * !pos.count(Them) - - 9 * mg_value(score) / 8 - + 40; + int kingDanger = unsafeChecks = 0; // Analyse the safe enemy's checks which are possible on next move safe = ~pos.pieces(Them); @@ -469,54 +473,52 @@ namespace { // Enemy queen safe checks if ((b1 | b2) & attackedBy[Them][QUEEN] & safe & ~attackedBy[Us][QUEEN]) - kingDanger += QueenCheck; - - // Some other potential checks are also analysed, even from squares - // currently occupied by the opponent own pieces, as long as the square - // is not attacked by our pawns, and is not occupied by a blocked pawn. - other = ~( attackedBy[Us][PAWN] - | (pos.pieces(Them, PAWN) & shift(pos.pieces(PAWN)))); - - // Enemy rooks safe and other checks - if (b1 & attackedBy[Them][ROOK] & safe) - kingDanger += RookCheck; + kingDanger += QueenSafeCheck; - else if (b1 & attackedBy[Them][ROOK] & other) - score -= OtherCheck; + b1 &= attackedBy[Them][ROOK]; + b2 &= attackedBy[Them][BISHOP]; - // Enemy bishops safe and other checks - if (b2 & attackedBy[Them][BISHOP] & safe) - kingDanger += BishopCheck; + // Enemy rooks checks + if (b1 & safe) + kingDanger += RookSafeCheck; + else + unsafeChecks |= b1; - else if (b2 & attackedBy[Them][BISHOP] & other) - score -= OtherCheck; + // Enemy bishops checks + if (b2 & safe) + kingDanger += BishopSafeCheck; + else + unsafeChecks |= b2; - // Enemy knights safe and other checks + // Enemy knights checks b = pos.attacks_from(ksq) & attackedBy[Them][KNIGHT]; if (b & safe) - kingDanger += KnightCheck; - - else if (b & other) - score -= OtherCheck; - - // Transform the kingDanger units into a Score, and substract it from the evaluation + kingDanger += KnightSafeCheck; + else + unsafeChecks |= b; + + // Unsafe or occupied checking squares will also be considered, as long as + // the square is in the attacker's mobility area. + unsafeChecks &= mobilityArea[Them]; + + kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] + + 102 * kingAdjacentZoneAttacksCount[Them] + + 191 * popcount(kingRing[Us] & weak) + + 143 * popcount(pos.pinned_pieces(Us) | unsafeChecks) + - 848 * !pos.count(Them) + - 9 * mg_value(score) / 8 + + 4 * tropism; + + // Transform the kingDanger units into a Score, and subtract it from the evaluation if (kingDanger > 0) + { + int mobilityDanger = (mg_value(mobility[Them] - mobility[Us])) * 2; + kingDanger = std::max(0, kingDanger + mobilityDanger); score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16); + } } - // King tropism: firstly, find squares that opponent attacks in our king flank - File kf = file_of(ksq); - b = attackedBy[Them][ALL_PIECES] & KingFlank[kf] & Camp; - - assert(((Us == WHITE ? b << 4 : b >> 4) & b) == 0); - assert(popcount(Us == WHITE ? b << 4 : b >> 4) == popcount(b)); - - // Secondly, add the squares which are attacked twice in that flank and - // which are not defended by our pawns. - b = (Us == WHITE ? b << 4 : b >> 4) - | (b & attackedBy2[Them] & ~attackedBy[Us][PAWN]); - - score -= CloseEnemies * popcount(b); + score -= CloseEnemies * tropism; // Penalty when our king is on a pawnless flank if (!(pos.pieces(PAWN) & KingFlank[kf])) @@ -555,9 +557,6 @@ namespace { safeThreats = (shift(b) | shift(b)) & weak; score += ThreatBySafePawn * popcount(safeThreats); - - if (weak ^ safeThreats) - score += ThreatByHangingPawn; } // Squares strongly protected by the opponent, either because they attack the @@ -634,6 +633,11 @@ namespace { return score; } + // helper used by evaluate_passed_pawns to cap the distance + template + int Evaluation::king_distance(Color c, Square s) { + return std::min(distance(pos.square(c), s), 5); + } // evaluate_passed_pawns() evaluates the passed pawns and candidate passed // pawns of the given color. @@ -658,8 +662,8 @@ namespace { bb = forward_file_bb(Us, s) & (attackedBy[Them][ALL_PIECES] | pos.pieces(Them)); score -= HinderPassedPawn * popcount(bb); - int r = relative_rank(Us, s) - RANK_2; - int rr = r * (r - 1); + int r = relative_rank(Us, s); + int rr = RankFactor[r]; Value mbonus = Passed[MG][r], ebonus = Passed[EG][r]; @@ -668,12 +672,11 @@ namespace { Square blockSq = s + Up; // Adjust bonus based on the king's proximity - ebonus += distance(pos.square(Them), blockSq) * 5 * rr - - distance(pos.square( Us), blockSq) * 2 * rr; + ebonus += (king_distance(Them, blockSq) * 5 - king_distance(Us, blockSq) * 2) * rr; // If blockSq is not the queening square then consider also a second push - if (relative_rank(Us, blockSq) != RANK_8) - ebonus -= distance(pos.square(Us), blockSq + Up) * rr; + if (r != RANK_7) + ebonus -= king_distance(Us, blockSq + Up) * rr; // If the pawn is free to advance, then increase the bonus if (pos.empty(blockSq)) @@ -693,7 +696,7 @@ namespace { // If there aren't any enemy attacks, assign a big bonus. Otherwise // assign a smaller bonus if the block square isn't attacked. - int k = !unsafeSquares ? 18 : !(unsafeSquares & blockSq) ? 8 : 0; + int k = !unsafeSquares ? 20 : !(unsafeSquares & blockSq) ? 9 : 0; //Fauzi Akram // If the path to the queen is fully defended, assign a big bonus. // Otherwise assign a smaller bonus if the block square is defended. @@ -903,7 +906,7 @@ namespace { Trace::add(TOTAL, score); } - return (pos.side_to_move() == WHITE ? v : -v) + Eval::Tempo; // Side to move point of view + return pos.side_to_move() == WHITE ? v : -v; // Side to move point of view } } // namespace @@ -915,7 +918,7 @@ Score Eval::Contempt = SCORE_ZERO; Value Eval::evaluate(const Position& pos) { - return Evaluation<>(pos).value(); + return Evaluation<>(pos).value() + Eval::Tempo; } /// trace() is like evaluate(), but instead of returning a value, it returns @@ -926,7 +929,7 @@ std::string Eval::trace(const Position& pos) { std::memset(scores, 0, sizeof(scores)); - Value v = Evaluation(pos).value(); + Value v = Evaluation(pos).value() + Eval::Tempo; v = pos.side_to_move() == WHITE ? v : -v; // White's point of view std::stringstream ss; diff --git a/Engines/Linux64/mcbrain/src/evaluate.h b/Engines/Linux64/mcbrain/src/evaluate.h index 3772c3f..8d388fa 100644 --- a/Engines/Linux64/mcbrain/src/evaluate.h +++ b/Engines/Linux64/mcbrain/src/evaluate.h @@ -30,7 +30,7 @@ class Position; namespace Eval { -const Value Tempo = Value(24); // Must be visible to search +const Value Tempo = Value(20); // Must be visible to search extern Score Contempt; diff --git a/Engines/Linux64/mcbrain/src/main.cpp b/Engines/Linux64/mcbrain/src/main.cpp index a7beca8..637dc3b 100644 --- a/Engines/Linux64/mcbrain/src/main.cpp +++ b/Engines/Linux64/mcbrain/src/main.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -48,11 +48,11 @@ int main(int argc, char* argv[]) { polybook.init(Options["BookFile"]); Tablebases::init(Options["SyzygyPath"]); TT.resize(Options["Hash"]); - Threads.init(Options["Threads"]); + Threads.set(Options["Threads"]); Search::clear(); // After threads are up UCI::loop(argc, argv); - Threads.exit(); + Threads.set(0); return 0; } diff --git a/Engines/Linux64/mcbrain/src/material.cpp b/Engines/Linux64/mcbrain/src/material.cpp index a58d52f..2b28837 100644 --- a/Engines/Linux64/mcbrain/src/material.cpp +++ b/Engines/Linux64/mcbrain/src/material.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -40,7 +40,7 @@ namespace { { 32, 255, -3 }, // Knight OUR PIECES { 0, 104, 4, 0 }, // Bishop { -26, -2, 47, 105, -149 }, // Rook - {-185, 24, 122, 137, -134, 0 } // Queen + {-189, 24, 117, 133, -134, -10 } // Queen }; const int QuadraticTheirs[][PIECE_TYPE_NB] = { @@ -51,18 +51,7 @@ namespace { { 9, 63, 0 }, // Knight OUR PIECES { 59, 65, 42, 0 }, // Bishop { 46, 39, 24, -24, 0 }, // Rook - { 101, 100, -37, 141, 268, 0 } // Queen - }; - - // PawnSet[pawn count] contains a bonus/malus indexed by number of pawns - const int PawnSet[] = { - 24, -32, 107, -51, 117, -9, -126, -21, 31 - }; - - // QueenMinorsImbalance[opp_minor_count] is applied when only one side has a queen. - // It contains a bonus/malus for the side with the queen. - const int QueenMinorsImbalance[13] = { - 31, -8, -15, -25, -5 + { 97, 100, -42, 137, 268, 0 } // Queen }; // Endgame evaluation and scaling functions are accessed directly and not through @@ -101,7 +90,7 @@ namespace { const Color Them = (Us == WHITE ? BLACK : WHITE); - int bonus = PawnSet[pieceCount[Us][PAWN]]; + int bonus = 0; // Second-degree polynomial material imbalance, by Tord Romstad for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) @@ -118,10 +107,6 @@ namespace { bonus += pieceCount[Us][pt1] * v; } - // Special handling of Queen vs. Minors - if (pieceCount[Us][QUEEN] == 1 && pieceCount[Them][QUEEN] == 0) - bonus += QueenMinorsImbalance[pieceCount[Them][KNIGHT] + pieceCount[Them][BISHOP]]; - return bonus; } diff --git a/Engines/Linux64/mcbrain/src/material.h b/Engines/Linux64/mcbrain/src/material.h index b412bb1..95d6c84 100644 --- a/Engines/Linux64/mcbrain/src/material.h +++ b/Engines/Linux64/mcbrain/src/material.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/misc.cpp b/Engines/Linux64/mcbrain/src/misc.cpp index 2d48cb0..a14d9c4 100644 --- a/Engines/Linux64/mcbrain/src/misc.cpp +++ b/Engines/Linux64/mcbrain/src/misc.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,7 +52,7 @@ namespace { /// Version number. If Version is left empty, then compile date in the format /// DD-MM-YY and show in engine_info. -const string Version = "v4.0"; +const string Version = "9"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We @@ -123,7 +123,7 @@ const string engine_info(bool to_uci) { string month, day, year; stringstream ss, date(__DATE__); // From compiler, format is "Sep 21 2008" - ss << "SF-McBrain " << Version << setfill('0'); + ss << "McBrain " << Version << setfill('0'); if (Version.empty()) { @@ -134,7 +134,7 @@ const string engine_info(bool to_uci) { ss << (Is64Bit ? " 64" : "") << (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : "")) << (to_uci ? "\nid author ": " by ") - << "M. Byrne and others (C) 2017"; + << "M. Byrne, Stockfish authors and others"; return ss.str(); } @@ -294,14 +294,6 @@ int get_group(size_t idx) { void bindThisThread(size_t idx) { - // If OS already scheduled us on a different group than 0 then don't overwrite - // the choice, eventually we are one of many one-threaded processes running on - // some Windows NUMA hardware, for instance in fishtest. To make it simple, - // just check if running threads are below a threshold, in this case all this - // NUMA machinery is not needed. - if (Threads.size() < 8) - return; - // Use only local variables to be thread-safe int group = get_group(idx); diff --git a/Engines/Linux64/mcbrain/src/misc.h b/Engines/Linux64/mcbrain/src/misc.h index f907c96..98b5202 100644 --- a/Engines/Linux64/mcbrain/src/misc.h +++ b/Engines/Linux64/mcbrain/src/misc.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/movegen.cpp b/Engines/Linux64/mcbrain/src/movegen.cpp index e75ccbb..d06bc86 100644 --- a/Engines/Linux64/mcbrain/src/movegen.cpp +++ b/Engines/Linux64/mcbrain/src/movegen.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/movegen.h b/Engines/Linux64/mcbrain/src/movegen.h index 1a02a2a..666e4ef 100644 --- a/Engines/Linux64/mcbrain/src/movegen.h +++ b/Engines/Linux64/mcbrain/src/movegen.h @@ -46,7 +46,7 @@ struct ExtMove { // Inhibit unwanted implicit conversions to Move // with an ambiguity that yields to a compile error. - operator float() const; + operator float() const = delete; }; inline bool operator<(const ExtMove& f, const ExtMove& s) { diff --git a/Engines/Linux64/mcbrain/src/movepick.cpp b/Engines/Linux64/mcbrain/src/movepick.cpp index ef169ff..f62ca2b 100644 --- a/Engines/Linux64/mcbrain/src/movepick.cpp +++ b/Engines/Linux64/mcbrain/src/movepick.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,12 +26,11 @@ namespace { enum Stages { - MAIN_SEARCH, CAPTURES_INIT, CAPTURE_KILLERS, GOOD_CAPTURES, KILLERS, COUNTERMOVE, QUIET_INIT, QUIET, BAD_CAPTURES, - EVASION, EVASIONS_INIT, ALL_EVASIONS, - PROBCUT, PROBCUT_INIT, PROBCUT_CAPTURES, - QSEARCH_WITH_CHECKS, QCAPTURES_1_INIT, QCAPTURES_1, QCHECKS, - QSEARCH_NO_CHECKS, QCAPTURES_2_INIT, QCAPTURES_2, - QSEARCH_RECAPTURES, QRECAPTURES + MAIN_SEARCH, CAPTURES_INIT, GOOD_CAPTURES, KILLERS, COUNTERMOVE, QUIET_INIT, QUIET, BAD_CAPTURES, + EVASION, EVASIONS_INIT, ALL_EVASIONS, + PROBCUT, PROBCUT_INIT, PROBCUT_CAPTURES, + QSEARCH, QCAPTURES_INIT, QCAPTURES, QCHECKS, + QSEARCH_RECAPTURES, QRECAPTURES }; // partial_insertion_sort() sorts moves in descending order up to and including @@ -69,10 +68,10 @@ namespace { /// MovePicker constructor for the main search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, - Move* killers_p) : - pos(p), mainHistory(mh), captureHistory(cph), contHistory(ch), countermove(cm), - killers{killers_p[0], killers_p[1], killers_p[2], killers_p[3]}, depth(d){ + const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, Move* killers_p) + : pos(p), mainHistory(mh), captureHistory(cph), contHistory(ch), countermove(cm), + killers{killers_p[0], killers_p[1]}, depth(d){ + assert(d > DEPTH_ZERO); stage = pos.checkers() ? EVASION : MAIN_SEARCH; @@ -82,18 +81,15 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist /// MovePicker constructor for quiescence search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const CapturePieceToHistory* cph, Square s) - : pos(p), mainHistory(mh), captureHistory(cph) { + : pos(p), mainHistory(mh), captureHistory(cph), depth(d) { assert(d <= DEPTH_ZERO); if (pos.checkers()) stage = EVASION; - else if (d > DEPTH_QS_NO_CHECKS) - stage = QSEARCH_WITH_CHECKS; - else if (d > DEPTH_QS_RECAPTURES) - stage = QSEARCH_NO_CHECKS; + stage = QSEARCH; else { @@ -162,8 +158,7 @@ Move MovePicker::next_move(bool skipQuiets) { switch (stage) { - case MAIN_SEARCH: case EVASION: case QSEARCH_WITH_CHECKS: - case QSEARCH_NO_CHECKS: case PROBCUT: + case MAIN_SEARCH: case EVASION: case QSEARCH: case PROBCUT: ++stage; return ttMove; @@ -172,34 +167,16 @@ Move MovePicker::next_move(bool skipQuiets) { endMoves = generate(pos, cur); score(); ++stage; - move = killers[2]; // First capture killer move - if( move != MOVE_NONE - && move != ttMove - && pos.pseudo_legal(move) - && pos.capture_or_promotion(move)) - return move; /* fallthrough */ - - case CAPTURE_KILLERS: - ++stage; - move = killers[3]; // Second capture killer move - if( move != MOVE_NONE - && move != ttMove - && pos.pseudo_legal(move) - && pos.capture_or_promotion(move)) - return move; - /* fallthrough */ case GOOD_CAPTURES: while (cur < endMoves) { move = pick_best(cur++, endMoves); - if ( move != ttMove - && move != killers[2] - && move != killers[3]) - { - if (pos.see_ge(move, Value(-55 * (cur-1)->value / 1024))) - return move; + if (move != ttMove) + { + if (pos.see_ge(move, Value(-55 * (cur-1)->value / 1024))) + return move; // Losing capture, move it to the beginning of the array *endBadCaptures++ = move; @@ -299,21 +276,21 @@ Move MovePicker::next_move(bool skipQuiets) { } break; - case QCAPTURES_1_INIT: case QCAPTURES_2_INIT: + case QCAPTURES_INIT: cur = moves; endMoves = generate(pos, cur); score(); ++stage; /* fallthrough */ - case QCAPTURES_1: case QCAPTURES_2: + case QCAPTURES: while (cur < endMoves) { move = pick_best(cur++, endMoves); if (move != ttMove) return move; } - if (stage == QCAPTURES_2) + if (depth <= DEPTH_QS_NO_CHECKS) break; cur = moves; endMoves = generate(pos, cur); @@ -332,14 +309,13 @@ Move MovePicker::next_move(bool skipQuiets) { case QSEARCH_RECAPTURES: cur = moves; endMoves = generate(pos, cur); - score(); ++stage; /* fallthrough */ case QRECAPTURES: while (cur < endMoves) { - move = pick_best(cur++, endMoves); + move = *cur++; if (to_sq(move) == recaptureSquare) return move; } diff --git a/Engines/Linux64/mcbrain/src/movepick.h b/Engines/Linux64/mcbrain/src/movepick.h index c651aaf..26e2a14 100644 --- a/Engines/Linux64/mcbrain/src/movepick.h +++ b/Engines/Linux64/mcbrain/src/movepick.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/pawns.cpp b/Engines/Linux64/mcbrain/src/pawns.cpp index d9e789d..3e5c2ed 100644 --- a/Engines/Linux64/mcbrain/src/pawns.cpp +++ b/Engines/Linux64/mcbrain/src/pawns.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -44,23 +44,17 @@ namespace { // Doubled pawn penalty const Score Doubled = S(18, 38); - // Lever bonus by rank - const Score Lever[RANK_NB] = { - S( 0, 0), S( 0, 0), S(0, 0), S(0, 0), - S(17, 16), S(33, 32), S(0, 0), S(0, 0) - }; - // Weakness of our pawn shelter in front of the king by [isKingFile][distance from edge][rank]. // RANK_1 = 0 is used for files where we have no pawns or our pawn is behind our king. const Value ShelterWeakness[][int(FILE_NB) / 2][RANK_NB] = { - { { V( 98), V(20), V(11), V(42), V( 83), V( 84), V(101) }, // Not On King file - { V(103), V( 8), V(33), V(86), V( 87), V(105), V(113) }, - { V(100), V( 2), V(65), V(95), V( 59), V( 89), V(115) }, - { V( 72), V( 6), V(52), V(74), V( 83), V( 84), V(112) } }, - { { V(105), V(19), V( 3), V(27), V( 85), V( 93), V( 84) }, // On King file - { V(121), V( 7), V(33), V(95), V(112), V( 86), V( 72) }, - { V(121), V(26), V(65), V(90), V( 65), V( 76), V(117) }, - { V( 79), V( 0), V(45), V(65), V( 94), V( 92), V(105) } } + { { V( 97), V(17), V( 9), V(44), V( 84), V( 87), V( 99) }, // Not On King file + { V(106), V( 6), V(33), V(86), V( 87), V(104), V(112) }, + { V(101), V( 2), V(65), V(98), V( 58), V( 89), V(115) }, + { V( 73), V( 7), V(54), V(73), V( 84), V( 83), V(111) } }, + { { V(104), V(20), V( 6), V(27), V( 86), V( 93), V( 82) }, // On King file + { V(123), V( 9), V(34), V(96), V(112), V( 88), V( 75) }, + { V(120), V(25), V(65), V(91), V( 66), V( 78), V(117) }, + { V( 81), V( 2), V(47), V(63), V( 94), V( 93), V(104) } } }; // Danger of enemy pawns moving toward our king by [type][distance from edge][rank]. @@ -185,9 +179,6 @@ namespace { if (doubled && !supported) score -= Doubled; - - if (lever) - score += Lever[relative_rank(Us, s)]; } return score; diff --git a/Engines/Linux64/mcbrain/src/pawns.h b/Engines/Linux64/mcbrain/src/pawns.h index 586a056..e834e18 100644 --- a/Engines/Linux64/mcbrain/src/pawns.h +++ b/Engines/Linux64/mcbrain/src/pawns.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-218 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/polybook.cpp b/Engines/Linux64/mcbrain/src/polybook.cpp index aebc91b..9aaacb4 100644 --- a/Engines/Linux64/mcbrain/src/polybook.cpp +++ b/Engines/Linux64/mcbrain/src/polybook.cpp @@ -1,3 +1,24 @@ +/* + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +// polybook.cpp was written by Thomas Ziproth /* BrainFish, a UCI chess playing engine derived from Stockfish Copyright (C) 2016-2017 Thomas Zipproth diff --git a/Engines/Linux64/mcbrain/src/polybook.h b/Engines/Linux64/mcbrain/src/polybook.h index 2e79b60..8696eda 100644 --- a/Engines/Linux64/mcbrain/src/polybook.h +++ b/Engines/Linux64/mcbrain/src/polybook.h @@ -1,6 +1,28 @@ /* + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +//polybook.h was wriiten by Thomas Zipproth +/* +polybook.h was written by Thomas Zipproth BrainFish, a UCI chess playing engine derived from Stockfish -Copyright (C) 2016-2017 Thomas Zipproth +Copyright (C) 2016-2018 Thomas Zipproth BrainFish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -88,4 +110,4 @@ class PolyBook extern PolyBook polybook; -#endif // #ifndef POLYBOOK_H_INCLUDED \ No newline at end of file +#endif // #ifndef POLYBOOK_H_INCLUDED diff --git a/Engines/Linux64/mcbrain/src/position.cpp b/Engines/Linux64/mcbrain/src/position.cpp index 253d4b3..acef66b 100644 --- a/Engines/Linux64/mcbrain/src/position.cpp +++ b/Engines/Linux64/mcbrain/src/position.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/position.h b/Engines/Linux64/mcbrain/src/position.h index 17c559e..c809549 100644 --- a/Engines/Linux64/mcbrain/src/position.h +++ b/Engines/Linux64/mcbrain/src/position.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/psqt.cpp b/Engines/Linux64/mcbrain/src/psqt.cpp index 6f7d210..55a2e72 100644 --- a/Engines/Linux64/mcbrain/src/psqt.cpp +++ b/Engines/Linux64/mcbrain/src/psqt.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,13 +39,13 @@ namespace PSQT { const Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { { }, { // Pawn - { S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }, - { S(-11, 7), S( 6,-4), S( 7, 8), S( 3,-2) }, - { S(-18,-4), S( -2,-5), S( 19, 5), S(24, 4) }, - { S(-17, 3), S( -9, 3), S( 20,-8), S(35,-3) }, - { S( -6, 7), S( 5, 8), S( 3, 7), S(21, 6) }, - { S( -6, 8), S( -8, 9), S( -6, 8), S(-2, 7) }, - { S( -4, 9), S( 20, 10), S( -8, 9), S(-4, 8) } + { S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }, + { S(-11, 7), S( 6,-4), S( 7, 8), S( 3,-2) }, + { S(-18,-4), S( -2,-5), S( 19, 5), S(24, 4) }, + { S(-17, 3), S( -9, 3), S( 20,-8), S(35,-3) }, + { S( -6, 8), S( 5, 9), S( 3, 7), S(21,-6) }, + { S( -6, 8), S( -8,-5), S( -6, 2), S(-2, 4) }, + { S( -4, 3), S( 20,-9), S( -8, 1), S(-4,18) } }, { // Knight { S(-161,-105), S(-96,-82), S(-80,-46), S(-73,-14) }, diff --git a/Engines/Linux64/mcbrain/src/search.cpp b/Engines/Linux64/mcbrain/src/search.cpp index 7935441..92753c0 100644 --- a/Engines/Linux64/mcbrain/src/search.cpp +++ b/Engines/Linux64/mcbrain/src/search.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,16 +23,16 @@ #include #include #include // For std::memset -#include //for sleep +#include //for sleep //MichaelB7 #include #include -#include +#include // ELO MichaelB7 #include "evaluate.h" #include "misc.h" #include "movegen.h" #include "movepick.h" -#include "polybook.h" +#include "polybook.h" // Cerebellum #include "position.h" #include "search.h" #include "timeman.h" @@ -71,16 +71,16 @@ namespace { const int skipPhase[] = { 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7 }; // Razoring and futility margin based on depth - // razor_margin[0] is unused as long as depth >= ONE_PLY in search - const int razor_margin[] = { 0, 570, 603, 554 }; - Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); } + const int razor_margin = 600; + //Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); } + Value futility_margin(Depth d) { return Value(16 * d^2 / ONE_PLY + 200 * d / ONE_PLY);} // idea from JD Watson, code modfied by MichaelB7 // Futility and reductions lookup tables, initialized at startup int FutilityMoveCounts[2][16]; // [improving][depth] - int Reductions[2][2][128][96]; // [pv][improving][depth][moveNumber] + int Reductions[2][2][128][96]; // [pv][improving][depth][moveNumber] //MichaelB7 template Depth reduction(bool i, Depth d, int mn) { - return Reductions[PvNode][i][std::min(d / ONE_PLY, 127)][std::min(mn, 95)] * ONE_PLY; + return Reductions[PvNode][i][std::min(d / ONE_PLY, 127)][std::min(mn, 95)] * ONE_PLY; //MichaelB7 } // History and stats update bonus, based on depth @@ -102,7 +102,7 @@ namespace { int tactical; - bool bruteForce, limitStrength, noNULL; + bool bookEnabled, bruteForce, limitStrength, noNULL; template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning); @@ -152,10 +152,10 @@ namespace { void Search::init() { for (int imp = 0; imp <= 1; ++imp) - for (int d = 1; d < 128; ++d) - for (int mc = 1; mc < 96; ++mc) + for (int d = 1; d < 128; ++d) // MichaelB7 + for (int mc = 1; mc < 96; ++mc) // MichaelB7 { - double r = log(d) * log(mc) / 1.89; + double r = log(d) * log(mc) / 1.89 ; Reductions[NonPV][imp][d][mc] = int(std::round(r)); Reductions[PV][imp][d][mc] = std::max(Reductions[NonPV][imp][d][mc] - 1, 0); @@ -181,13 +181,7 @@ void Search::clear() { Time.availableNodes = 0; TT.clear(); - - for (Thread* th : Threads) - th->clear(); - - Threads.main()->callsCnt = 0; - Threads.main()->previousScore = VALUE_INFINITE; - Threads.main()->previousTimeReduction = 1; + Threads.clear(); } @@ -207,6 +201,7 @@ void MainThread::search() { limitStrength = Options["UCI_LimitStrength"]; noNULL = Options["No_Null_Moves"]; tactical = Options["Tactical"]; + bookEnabled = Options["Book_Enabled"]; Color us = rootPos.side_to_move(); Time.init(Limits, us, rootPos.game_ply()); @@ -214,6 +209,14 @@ void MainThread::search() { int contempt = Options["Contempt"] * PawnValueEg / 100; // From centipawns + // In analysis mode, adjust contempt in accordance with user preference + if (Limits.infinite || Options["UCI_AnalyseMode"]) + contempt = Options["Analysis Contempt"] == "Off" ? 0 + : Options["Analysis Contempt"] == "White" && us == BLACK ? -contempt + : Options["Analysis Contempt"] == "Black" && us == WHITE ? -contempt + : contempt; // contempt remains with the side to move + + // Eval::Contempt is from white's point of view Eval::Contempt = (us == WHITE ? make_score(contempt, contempt / 2) : -make_score(contempt, contempt / 2)); @@ -231,7 +234,7 @@ void MainThread::search() { if (!Limits.infinite && !Limits.mate) bookMove = polybook.probe(rootPos); - if (bookMove && std::count(rootMoves.begin(), rootMoves.end(), bookMove)) + if (bookEnabled && bookMove && std::count(rootMoves.begin(), rootMoves.end(), bookMove)) { for (Thread* th : Threads) std::swap(th->rootMoves[0], *std::find(th->rootMoves.begin(), th->rootMoves.end(), bookMove)); @@ -240,9 +243,7 @@ void MainThread::search() { { if (limitStrength) { - int uci_elo = (Options["UCI_ELO"]); - std::mt19937 gen(now()); std::uniform_int_distribution dis(-33, 33); int rand = dis(gen); @@ -346,10 +347,10 @@ void Thread::search() { mainThread->bestMoveChanges = 0; } - size_t multiPV = Options["MultiPV"]; + multiPV = Options["MultiPV"]; Skill skill(Options["Skill Level"]); if (tactical) multiPV = pow(2, tactical); - if (::pv_is_draw(rootPos)) multiPV = 8 ; + // When playing with strength handicap enable MultiPV search that we will // use behind the scenes to retrieve a set of possible moves. @@ -357,19 +358,25 @@ void Thread::search() { multiPV = std::max(multiPV, (size_t)4); multiPV = std::min(multiPV, rootMoves.size()); + + // UCI options specify 256 as the max MultiPv option + int consecutiveEarlyExits[257] = {0}; // Iterative deepening loop until requested to stop or the target depth is reached while ( (rootDepth += ONE_PLY) < DEPTH_MAX && !Threads.stop && !(Limits.depth && mainThread && rootDepth / ONE_PLY > Limits.depth)) { - // Distribute search depths across the threads - if (idx) - { - int i = (idx - 1) % 20; - if (((rootDepth / ONE_PLY + rootPos.game_ply() + skipPhase[i]) / skipSize[i]) % 2) - continue; - } + // Distribute search depths across the threads + if (idx) + { + int i = (idx - 1) % 20; + if (idx == Threads.size() - 1 && !ss->excludedMove && rootDepth < 14 * ONE_PLY && rootMoves.size() > 1 && rootPos.see_ge(rootMoves[0].pv[0], VALUE_ZERO + 1)) + ss->excludedMove = rootMoves[0].pv[0]; //lazy_or_stubborn by pb00068 + else + if (((rootDepth / ONE_PLY + rootPos.game_ply() + skipPhase[i]) / skipSize[i]) % 2) + continue; + } // Age out PV variability metric if (mainThread) @@ -409,7 +416,8 @@ void Thread::search() { // and we want to keep the same order for all the moves except the // new PV that goes to the front. Note that in case of MultiPV // search the already searched PV lines are preserved. - std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end()); + if (PVIdx + 1 == multiPV) + std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end()); // If search has been stopped, we break immediately. Sorting and // writing PV back to TT is safe because RootMoves is still @@ -438,15 +446,20 @@ void Thread::search() { Threads.stopOnPonderhit = false; } } - else if (bestValue >= beta) - beta = std::min(bestValue + delta2, VALUE_INFINITE); + + else if ( bestValue >= beta) + beta = std::min(bestValue + delta2, VALUE_INFINITE); //Ivan Ivec + else + { + delta1 += delta1 / 4 + 5; //Ivan Ivec + delta2 += delta2 / 4 + 5; //Ivan Ivec + + assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + + consecutiveEarlyExits[PVIdx] = (bestValue >= beta) ? consecutiveEarlyExits[PVIdx] + 1 : 0; break; - - delta1 += delta1 / 4 + 5; - delta2 += delta2 / 4 + 5; - - assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + } } // Sort the PV lines searched so far and update the GUI @@ -473,14 +486,14 @@ void Thread::search() { if (!mainThread) continue; - - if (Options["FastPlay"]) - { - if ( Time.elapsed() > Time.optimum() / 256 - && ( abs(bestValue) > 12300 || abs(bestValue) >= VALUE_MATE_IN_MAX_PLY )) - Threads.stop = true; - } - +//BEGINS MichaelB7 + if (Options["FastPlay"]) + { + if ( Time.elapsed() > Time.optimum() / 256 + && ( abs(bestValue) > 12300 || abs(bestValue) >= VALUE_MATE_IN_MAX_PLY )) + Threads.stop = true; + } +//ENDS MichaelB7 // If skill level is enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(rootDepth)) skill.pick_best(multiPV); @@ -618,7 +631,7 @@ namespace { // search to overwrite a previous full search TT value, so we use a different // position key in case of an excluded move. excludedMove = ss->excludedMove; - posKey = pos.key() ^ Key(excludedMove); + posKey = pos.key() ^ Key(excludedMove << 16); // isn't a very good hash tte = TT.probe(posKey, ttHit); ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0] @@ -739,18 +752,18 @@ namespace { ss->staticEval, TT.generation()); } - if (skipEarlyPruning || !pos.non_pawn_material()) + if (skipEarlyPruning || !pos.non_pawn_material(pos.side_to_move())) goto moves_loop; // Step 6. Razoring (skipped when in check) if ( !bruteForce && !PvNode && depth < 4 * ONE_PLY - && eval + razor_margin[depth / ONE_PLY] <= alpha) + && eval + razor_margin <= alpha) { if (depth <= ONE_PLY) return qsearch(pos, ss, alpha, alpha+1); - Value ralpha = alpha - razor_margin[depth / ONE_PLY]; + Value ralpha = alpha - razor_margin; Value v = qsearch(pos, ss, ralpha, ralpha+1); if (v <= ralpha) return v; @@ -760,18 +773,17 @@ namespace { if ( !bruteForce && !rootNode && depth < 7 * ONE_PLY && eval - futility_margin(depth) >= beta - && eval < VALUE_KNOWN_WIN ) // Do not return unproven wins + && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; // Step 8. Null move search with verification search (is omitted in PV nodes) - if ( !noNULL - && !PvNode + if ( !PvNode && eval >= beta - && (ss->staticEval >= beta - int(320 * log(depth / ONE_PLY)) + 500) - && thisThread->selDepth + 3 > thisThread->rootDepth / ONE_PLY - && (ss->ply >= thisThread->nmp_ply || ss->ply % 2 == thisThread->pair) - && !(MoveList(pos).size() < 4) - ) + && ss->staticEval >= beta - int(320 * log(depth / ONE_PLY)) + 500 //Corchess/IIvec + && (((ss-2)->currentMove != MOVE_NULL) || (ss-2)->staticEval >= beta - 36 * depth / ONE_PLY + 225) + && thisThread->selDepth + 3 > thisThread->rootDepth / ONE_PLY // idea from Corchess/IIvec + && (ss->ply >= thisThread->nmp_ply || ss->ply % 2 != thisThread->pair) + && !(MoveList(pos).size() < 4)) //MichaelB7 { assert(eval - beta >= 0); @@ -874,7 +886,7 @@ namespace { && tte->depth() >= depth - 3 * ONE_PLY; skipQuiets = false; ttCapture = false; - goodCap = false; + goodCap = false; pvExact = PvNode && ttHit && tte->bound() == BOUND_EXACT; // Step 11. Loop through moves @@ -886,12 +898,20 @@ namespace { if (move == excludedMove) continue; - // At root obey the "searchmoves" option and skip moves not listed in Root - // Move List. As a consequence any illegal move is also skipped. In MultiPV - // mode we also skip PV moves which have been already searched. - if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx, - thisThread->rootMoves.end(), move)) - continue; + if (rootNode) + { + // At root obey the "searchmoves" option and skip moves not listed in Root + // Move List. As a consequence any illegal move is also skipped. + if (!std::count(thisThread->rootMoves.begin() + thisThread->PVIdx, + thisThread->rootMoves.end(), move)) + continue; + + // In MultiPV mode we not only skip PV moves which have already been searched, + // but also any other move except we have reached the last PV line. + if ( thisThread->PVIdx + 1 < thisThread->multiPV + && move != ttMove) + continue; + } ss->moveCount = ++moveCount; @@ -933,31 +953,22 @@ namespace { ss->excludedMove = MOVE_NONE; if (value < rBeta) - extension = ONE_PLY; + extension = ONE_PLY; } - else if ( givesCheck - && !moveCountPruning - && (pos.see_ge(move) || PvNode )) - extension = ONE_PLY; - - else if ( PvNode - && depth < 13 * ONE_PLY - && pos.advanced_pawn_push(move) - && !moveCountPruning) - extension = ONE_PLY; + else if (!moveCountPruning) + { + if ( givesCheck + && (pos.see_ge(move) || PvNode )) + extension = ONE_PLY; + + else if ( PvNode + && depth < 13 * ONE_PLY + && ( givesCheck || pos.advanced_pawn_push(move) + || ( ss->forcingTree && (ss)->newDepth - depth > 1))) + extension = ONE_PLY; + } - else if ( PvNode - && depth < 13 * ONE_PLY - && ss->forcingTree && (ss)->newDepth - depth > 1 - && !moveCountPruning) - extension = ONE_PLY; - else if ( depth % 5 == 0 - && !pos.non_pawn_material() - && type_of(movedPiece) == KING - && !moveCountPruning) - extension = ONE_PLY; - // Calculate new depth for this move newDepth = depth - ONE_PLY + extension; @@ -989,7 +1000,7 @@ namespace { // Futility pruning: parent node if ( lmrDepth < 7 && !inCheck - && ss->staticEval + 256 + 200 * lmrDepth <= alpha) + && ss->staticEval + 256 + futility_margin(lmrDepth * ONE_PLY) <= alpha)// idea from JD Watson, modifed by MichaelB7 continue; // Prune moves with negative SEE @@ -1031,7 +1042,7 @@ namespace { if ( !bruteForce && depth >= 3 * ONE_PLY && moveCount > 1 && (!captureOrPromotion || moveCountPruning) - && (ss+2)->killers[0] != move + && (ss+2)->killers[0] != move && (ss+2)->killers[1] != move) { Depth r = reduction(improving, depth, moveCount); @@ -1041,7 +1052,7 @@ namespace { else { // Decrease reduction if opponent's move count is high - if ((ss-1)->moveCount > 15) + if ((ss-1)->moveCount > 13 ) r -= ONE_PLY; // Decrease reduction for exact PV nodes @@ -1049,7 +1060,7 @@ namespace { r -= ONE_PLY; // Increase reduction if ttMove is a capture - if (ttCapture) + if (ttCapture || (!pvExact && depth > 13 * ONE_PLY)) r += ONE_PLY; else if(goodCap && !inCheck && !givesCheck) r += ONE_PLY; @@ -1068,7 +1079,7 @@ namespace { ss->statScore = thisThread->mainHistory[~pos.side_to_move()][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] - 4000; // Decrease/increase reduction by comparing opponent's stat score @@ -1388,7 +1399,6 @@ namespace { // Don't search moves with negative SEE values if ( (!InCheck || evasionPrunable) - && type_of(move) != PROMOTION && !pos.see_ge(move)) continue; @@ -1643,12 +1653,19 @@ namespace { string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { std::stringstream ss; + int temp = 0; int elapsed = Time.elapsed() + 1; const RootMoves& rootMoves = pos.this_thread()->rootMoves; size_t PVIdx = pos.this_thread()->PVIdx; - size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); + size_t multiPV = pos.this_thread()->multiPV; uint64_t nodesSearched = Threads.nodes_searched(); uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); + + if (tactical) + { + temp = multiPV; + multiPV = 1; + } for (size_t i = 0; i < multiPV; ++i) { @@ -1688,11 +1705,10 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { for (Move m : rootMoves[i].pv) ss << " " << UCI::move(m, pos.is_chess960()); } - + if (tactical) multiPV = temp; return ss.str(); } - /// RootMove::extract_ponder_from_tt() is called in case we have no ponder move /// before exiting the search, for instance, in case we stop the search during a /// fail high at root. We try hard to have a ponder move to return to the GUI, diff --git a/Engines/Linux64/mcbrain/src/search.h b/Engines/Linux64/mcbrain/src/search.h index 90842f6..f1ac439 100644 --- a/Engines/Linux64/mcbrain/src/search.h +++ b/Engines/Linux64/mcbrain/src/search.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -104,8 +104,7 @@ extern LimitsType Limits; void init(); void clear(); -Value respect(const Position& pos, Color c); - + } // namespace Search #endif // #ifndef SEARCH_H_INCLUDED diff --git a/Engines/Linux64/mcbrain/src/tbprobe.cpp b/Engines/Linux64/mcbrain/src/tbprobe.cpp index d019f28..09e031c 100644 --- a/Engines/Linux64/mcbrain/src/tbprobe.cpp +++ b/Engines/Linux64/mcbrain/src/tbprobe.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/tbprobe.h b/Engines/Linux64/mcbrain/src/tbprobe.h index 7c0bbe7..97ec7b1 100644 --- a/Engines/Linux64/mcbrain/src/tbprobe.h +++ b/Engines/Linux64/mcbrain/src/tbprobe.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/thread.cpp b/Engines/Linux64/mcbrain/src/thread.cpp index 616b88f..47a21c1 100644 --- a/Engines/Linux64/mcbrain/src/thread.cpp +++ b/Engines/Linux64/mcbrain/src/thread.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017*2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,6 +25,7 @@ #include "movegen.h" #include "search.h" #include "thread.h" +#include "uci.h" #include "tbprobe.h" ThreadPool Threads; // Global object @@ -36,7 +37,6 @@ ThreadPool Threads; // Global object Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { wait_for_search_finished(); - clear(); // Zero-init histories (based on std::array) } @@ -93,7 +93,13 @@ void Thread::wait_for_search_finished() { void Thread::idle_loop() { - WinProcGroup::bindThisThread(idx); + // If OS already scheduled us on a different group than 0 then don't overwrite + // the choice, eventually we are one of many one-threaded processes running on + // some Windows NUMA hardware, for instance in fishtest. To make it simple, + // just check if running threads are below a threshold, in this case all this + // NUMA machinery is not needed. + if (Options["Threads"] >= 8) + WinProcGroup::bindThisThread(idx); while (true) { @@ -111,42 +117,40 @@ void Thread::idle_loop() { } } +/// ThreadPool::set() creates/destroys threads to match the requested number. +/// Created and launced threads wil go immediately to sleep in idle_loop. +/// Upon resizing, threads are recreated to allow for binding if necessary. -/// ThreadPool::init() creates and launches the threads that will go -/// immediately to sleep in idle_loop. We cannot use the constructor because -/// Threads is a static object and we need a fully initialized engine at -/// this point due to allocation of Endgames in the Thread constructor. - -void ThreadPool::init(size_t requested) { - - push_back(new MainThread(0)); - set(requested); -} +void ThreadPool::set(size_t requested) { + if (size() > 0) { // destroy any existing thread(s) + main()->wait_for_search_finished(); -/// ThreadPool::exit() terminates threads before the program exits. Cannot be -/// done in the destructor because threads must be terminated before deleting -/// any static object, so before main() returns. + while (size() > 0) + delete back(), pop_back(); + } -void ThreadPool::exit() { + if (requested > 0) { // create new thread(s) + push_back(new MainThread(0)); - main()->wait_for_search_finished(); - set(0); + while (size() < requested) + push_back(new Thread(size())); + clear(); + } } +/// ThreadPool::clear() sets threadPool data to initial values. -/// ThreadPool::set() creates/destroys threads to match the requested number +void ThreadPool::clear() { -void ThreadPool::set(size_t requested) { + for (Thread* th : *this) + th->clear(); - while (size() < requested) - push_back(new Thread(size())); - - while (size() > requested) - delete back(), pop_back(); + main()->callsCnt = 0; + main()->previousScore = VALUE_INFINITE; + main()->previousTimeReduction = 1; } - /// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and /// returns immediately. Main thread will wake up other threads and start the search. @@ -182,7 +186,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, // is shared by threads but is accessed in read-only mode. StateInfo tmp = setupStates->back(); - for (Thread* th : Threads) + for (Thread* th : *this) { th->nodes = th->tbHits = 0; th->rootDepth = th->completedDepth = DEPTH_ZERO; diff --git a/Engines/Linux64/mcbrain/src/thread.h b/Engines/Linux64/mcbrain/src/thread.h index 87c5029..25a6511 100644 --- a/Engines/Linux64/mcbrain/src/thread.h +++ b/Engines/Linux64/mcbrain/src/thread.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -61,8 +61,8 @@ class Thread { Pawns::Table pawnsTable; Material::Table materialTable; Endgames endgames; - size_t PVIdx; - int selDepth, nmp_ply, pair; + size_t PVIdx, multiPV; + int selDepth, nmp_ply, pair; std::atomic nodes, tbHits; Position rootPos; @@ -100,11 +100,19 @@ struct ThreadPool : public std::vector { void init(size_t); // No constructor and destructor, threads rely on globals that should void exit(); // be initialized and valid during the whole thread lifetime. void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); + void clear(); void set(size_t); MainThread* main() const { return static_cast(front()); } uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } + + Depth lowestDepth(){ + Depth lowest = DEPTH_MAX; + for (Thread* th: *this) + lowest = std::min(lowest, th->rootDepth); + return lowest; + } std::atomic_bool stop, ponder, stopOnPonderhit; diff --git a/Engines/Linux64/mcbrain/src/thread_win32.h b/Engines/Linux64/mcbrain/src/thread_win32.h index ca737e9..d69e817 100644 --- a/Engines/Linux64/mcbrain/src/thread_win32.h +++ b/Engines/Linux64/mcbrain/src/thread_win32.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/timeman.cpp b/Engines/Linux64/mcbrain/src/timeman.cpp index 579921e..6a854c9 100644 --- a/Engines/Linux64/mcbrain/src/timeman.cpp +++ b/Engines/Linux64/mcbrain/src/timeman.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,8 @@ */ #include +#include +#include #include "search.h" #include "timeman.h" @@ -31,46 +33,41 @@ namespace { enum TimeType { OptimumTime, MaxTime }; - int remaining(int myTime, int myInc, int moveOverhead, int movesToGo, - int moveNum, bool ponder, TimeType type) { + const int MoveHorizon = 50; // Plan time management at most this many moves ahead + const double MaxRatio = 7.09; // When in trouble, we can step over reserved time with this ratio + const double StealRatio = 0.35; // However we must not steal time from remaining moves over this ratio - if (myTime <= 0) - return 0; - double ratio; // Which ratio of myTime we are going to use + // move_importance() is a skew-logistic function based on naive statistical + // analysis of "how many games are still undecided after n half-moves". Game + // is considered "undecided" as long as neither side has >275cp advantage. + // Data was extracted from the CCRL game database with some simple filtering criteria. - // Usage of increment follows quadratic distribution with the maximum at move 25 - double inc = myInc * std::max(55.0, 120 - 0.12 * (moveNum - 25) * (moveNum - 25)); + double move_importance(int ply) { - // In moves-to-go we distribute time according to a quadratic function with - // the maximum around move 20 for 40 moves in y time case. - if (movesToGo) - { - ratio = (type == OptimumTime ? 1.0 : 6.0) / std::min(50, movesToGo); + const double XScale = 7.64; + const double XShift = 58.4; + const double Skew = 0.183; - if (moveNum <= 40) - ratio *= 1.1 - 0.001 * (moveNum - 20) * (moveNum - 20); - else - ratio *= 1.5; + return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero + } + + template + int remaining(int myTime, int movesToGo, int ply, int slowMover) { - if (movesToGo > 1) - ratio = std::min(0.75, ratio); + const double TMaxRatio = (T == OptimumTime ? 1 : MaxRatio); + const double TStealRatio = (T == OptimumTime ? 0 : StealRatio); - ratio *= 1 + inc / (myTime * 8.5); - } - // Otherwise we increase usage of remaining time as the game goes on - else - { - double k = 1 + 20 * moveNum / (500.0 + moveNum); - ratio = (type == OptimumTime ? 0.017 : 0.07) * (k + inc / myTime); - } + double moveImportance = (move_importance(ply) * slowMover) / 100; + double otherMovesImportance = 0; - int time = int(std::min(1.0, ratio) * std::max(0, myTime - moveOverhead)); + for (int i = 1; i < movesToGo; ++i) + otherMovesImportance += move_importance(ply + 2 * i); - if (type == OptimumTime && ponder) - time = 5 * time / 4; + double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance); + double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance); - return time; + return int(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast } } // namespace @@ -85,11 +82,12 @@ namespace { /// inc > 0 && movestogo == 0 means: x basetime + z increment /// inc > 0 && movestogo != 0 means: x moves in y minutes + z increment -void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) -{ - int moveOverhead = Options["Move Overhead"]; - int npmsec = Options["nodestime"]; - bool ponder = Options["Ponder"]; +void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { + + int minThinkingTime = Options["Minimum Thinking Time"]; + int moveOverhead = Options["Move Overhead"]; + int slowMover = Options["Slow Mover"]; + int npmsec = Options["nodestime"]; // If we have to play in 'nodes as time' mode, then convert from time // to nodes, and use resulting values in time management formulas. @@ -106,11 +104,30 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) limits.npmsec = npmsec; } - int moveNum = (ply + 1) / 2; - startTime = limits.startTime; - optimumTime = remaining(limits.time[us], limits.inc[us], moveOverhead, - limits.movestogo, moveNum, ponder, OptimumTime); - maximumTime = remaining(limits.time[us], limits.inc[us], moveOverhead, - limits.movestogo, moveNum, ponder, MaxTime); + optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime); + + const int MaxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon; + + // We calculate optimum time usage for different hypothetical "moves to go"-values + // and choose the minimum of calculated search time values. Usually the greatest + // hypMTG gives the minimum values. + for (int hypMTG = 1; hypMTG <= MaxMTG; ++hypMTG) + { + // Calculate thinking time for hypothetical "moves to go"-value + int hypMyTime = limits.time[us] + + limits.inc[us] * (hypMTG - 1) + - moveOverhead * (2 + std::min(hypMTG, 40)); + + hypMyTime = std::max(hypMyTime, 0); + + int t1 = minThinkingTime + remaining(hypMyTime, hypMTG, ply, slowMover); + int t2 = minThinkingTime + remaining(hypMyTime, hypMTG, ply, slowMover); + + optimumTime = std::min(t1, optimumTime); + maximumTime = std::min(t2, maximumTime); + } + + if (Options["Ponder"]) + optimumTime += optimumTime / 4; } diff --git a/Engines/Linux64/mcbrain/src/timeman.h b/Engines/Linux64/mcbrain/src/timeman.h index 427271a..4d3172b 100644 --- a/Engines/Linux64/mcbrain/src/timeman.h +++ b/Engines/Linux64/mcbrain/src/timeman.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/mcbrain/src/tt.cpp b/Engines/Linux64/mcbrain/src/tt.cpp index 9bb093d..1aed0f3 100644 --- a/Engines/Linux64/mcbrain/src/tt.cpp +++ b/Engines/Linux64/mcbrain/src/tt.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ TranspositionTable TT; // Our global transposition table void TranspositionTable::resize(size_t mbSize) { - size_t newClusterCount = size_t(1) << msb((mbSize * 1024 * 1024) / sizeof(Cluster)); + size_t newClusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); if (newClusterCount == clusterCount) return; @@ -42,7 +42,7 @@ void TranspositionTable::resize(size_t mbSize) { clusterCount = newClusterCount; free(mem); - mem = calloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1, 1); + mem = malloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1); if (!mem) { @@ -52,6 +52,7 @@ void TranspositionTable::resize(size_t mbSize) { } table = (Cluster*)((uintptr_t(mem) + CacheLineSize - 1) & ~(CacheLineSize - 1)); + clear(); } diff --git a/Engines/Linux64/mcbrain/src/tt.h b/Engines/Linux64/mcbrain/src/tt.h index 56646a6..e0dc386 100644 --- a/Engines/Linux64/mcbrain/src/tt.h +++ b/Engines/Linux64/mcbrain/src/tt.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -105,9 +105,9 @@ class TranspositionTable { void resize(size_t mbSize); void clear(); - // The lowest order bits of the key are used to get the index of the cluster + // The 32 lowest order bits of the key are used to get the index of the cluster TTEntry* first_entry(const Key key) const { - return &table[(size_t)key & (clusterCount - 1)].entry[0]; + return &table[(uint32_t(key) * uint64_t(clusterCount)) >> 32].entry[0]; } private: diff --git a/Engines/Linux64/mcbrain/src/types.h b/Engines/Linux64/mcbrain/src/types.h index 77acc44..7b4318e 100644 --- a/Engines/Linux64/mcbrain/src/types.h +++ b/Engines/Linux64/mcbrain/src/types.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -334,7 +334,7 @@ inline Square& operator-=(Square &s, Direction d) { return s = s - d; } /// Only declared but not defined. We don't want to multiply two scores due to /// a very high risk of overflow. So user should explicitly convert to integer. -Score operator*(Score s1, Score s2) = delete; +Score operator*(Score, Score) = delete; /// Division of a Score must be handled separately for each term inline Score operator/(Score s, int i) { diff --git a/Engines/Linux64/mcbrain/src/tzbook.cpp b/Engines/Linux64/mcbrain/src/tzbook.cpp deleted file mode 100644 index 4052256..0000000 --- a/Engines/Linux64/mcbrain/src/tzbook.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "tzbook.h" -#include "uci.h" -#include "movegen.h" -#include "thread.h" -#include -#include "misc.h" -#include - -TZBook tzbook; // global TZBook - -using namespace std; - -int qsort_compare_int(const void* a, const void* b) -{ - const int int_a = *((const int*)a); - const int int_b = *((const int*)b); - - if (int_a == int_b) return 0; - else if (int_a < int_b) return -1; - else return 1; -} - -TZBook::TZBook() -{ - keycount = 0; - book_move2_probability = 0; - last_position = 0; - akt_position = 0; - last_anz_pieces = 0; - akt_anz_pieces = 0; - search_counter = 0; - tzhash2 = NULL; - do_search = true; - enabled = false; -} - -TZBook::~TZBook() -{ -} - - -void TZBook::init(const std::string& path) -{ - if (path.length() == 0) return; - - const char *p = path.c_str(); - if (strcmp(p, "") == 0) - return; - - FILE *fpt = fopen(p, "rb"); - if (fpt == NULL) - { - sync_cout << "info string Could not open " << path << sync_endl; - return; - } - - if (tzhash2 != NULL) - { - free(tzhash2); - tzhash2 = NULL; - } - - fseek(fpt, 0L, SEEK_END); - int filesize = ftell(fpt); - fseek(fpt, 0L, SEEK_SET); - - keycount = filesize / 8; - tzhash2 = new TZHash2[keycount]; - - fread(tzhash2, 1, filesize, fpt); - fclose(fpt); - - sync_cout << "info string Book loaded: " << path << sync_endl; - - srand((int)time(NULL)); - enabled = true; -} - - -void TZBook::set_book_move2_probability(int book_move2_prob) -{ - book_move2_probability = book_move2_prob; -} - - -Move TZBook::probe2(Position& pos) -{ - Move m = MOVE_NONE; - if (!enabled) return m; - - akt_position = pos.pieces(); - akt_anz_pieces = popcount(akt_position); - - if (do_search == false) - { - Bitboard b = akt_position ^ last_position; - int n2 = popcount(b); - - if (n2 > 4) do_search = true; - if (akt_anz_pieces > last_anz_pieces) do_search = true; - if (akt_anz_pieces < last_anz_pieces - 2) do_search = true; - } - - last_position = akt_position; - last_anz_pieces = akt_anz_pieces; - - if (do_search) - { - TZHash2 *tz = probe2(pos.key()); - if (tz == NULL) - { - search_counter++; - if (search_counter > 2) - { - do_search = false; - search_counter = 0; - } - } - else - { - if (pos.is_draw(64)) - m = get_move_from_draw_position(pos, tz); - else - m = get_move(pos, tz); - } - } - - return m; -} - - -TZHash2 *TZBook::probe2(Key key) -{ - uint32_t key1 = key >> 32; - unsigned short key2 = key >> 16 & 0xFFFF; - - int start = 0; - int end = keycount; - - for (;;) - { - int mid = (end + start) / 2; - - if (tzhash2[mid].key1 < key1) - start = mid; - else - { - if (tzhash2[mid].key1 > key1) - end = mid; - else - { - start = max(mid - 4, 0); - end = min(mid + 4, keycount); - } - } - - if (end - start < 9) - break; - } - - for (int i = start; i < end; i++) - if ((key1 == tzhash2[i].key1) && (key2 == tzhash2[i].key2)) - return &(tzhash2[i]); - - return NULL; -} - -Move TZBook::movenumber_to_move(Position& pos, int n) -{ - const ExtMove *m = MoveList(pos).begin(); - size_t size = MoveList(pos).size(); - Move *mv = new Move[size]; - for (unsigned int i = 0; i < size; i++) - mv[i] = m[i].move; - - qsort(mv, size, sizeof(mv[0]), qsort_compare_int); - - return mv[n]; -} - -bool TZBook::check_draw(Move m, Position& pos) -{ - StateInfo st; - - pos.do_move(m, st, pos.gives_check(m)); - bool draw = pos.is_draw(64); - pos.undo_move(m); - - return draw; -} - - -Move TZBook::get_move_from_draw_position(Position& pos, TZHash2 *tz) -{ - Move m = movenumber_to_move(pos, tz->move_number); - if (!check_draw(m, pos)) - return m; - - if (tz->move_number2 == 255) - return m; - - m = movenumber_to_move(pos, tz->move_number2); - if (!check_draw(m, pos)) - return m; - - return MOVE_NONE; -} - - -Move TZBook::get_move(Position& pos, TZHash2 *tz) -{ - Move m1 = movenumber_to_move(pos, tz->move_number); - if ((book_move2_probability == 0) || (tz->move_number2 == 255)) - return m1; - - Move m2 = movenumber_to_move(pos, tz->move_number2); - if ((book_move2_probability == 100) || (rand() % 100 < book_move2_probability)) - return m2; - - return m1; -} \ No newline at end of file diff --git a/Engines/Linux64/mcbrain/src/tzbook.h b/Engines/Linux64/mcbrain/src/tzbook.h deleted file mode 100644 index 744618a..0000000 --- a/Engines/Linux64/mcbrain/src/tzbook.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef TZBOOK_H_INCLUDED -#define TZBOOK_H_INCLUDED - -#include "bitboard.h" -#include "position.h" -#include "string.h" - -struct TZHash2 -{ - uint32_t key1; - uint16_t key2; - unsigned char move_number; - unsigned char move_number2; -}; - -class TZBook -{ - public: - - Bitboard last_position; - Bitboard akt_position; - int last_anz_pieces; - int akt_anz_pieces; - int search_counter; - - bool enabled, do_search; - int book_move2_probability; - - TZBook(); - ~TZBook(); - - void init(const std::string& path); - void set_book_move2_probability(int book_move2_prob); - - Move probe2(Position& pos); - TZHash2 *probe2(Key key); - -private: - - int keycount; - TZHash2 *tzhash2; - bool check_draw(Move m, Position& pos); - Move get_move_from_draw_position(Position& pos, TZHash2 *tz); - Move get_move(Position& pos, TZHash2 *tz); - Move movenumber_to_move(Position& pos, int n); -}; - -extern TZBook tzbook; - -#endif // #ifndef TZBOOK_H_INCLUDED \ No newline at end of file diff --git a/Engines/Linux64/mcbrain/src/uci.cpp b/Engines/Linux64/mcbrain/src/uci.cpp index ecbbe8b..40efb6d 100644 --- a/Engines/Linux64/mcbrain/src/uci.cpp +++ b/Engines/Linux64/mcbrain/src/uci.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -273,13 +273,13 @@ void UCI::loop(int argc, char* argv[]) { else if (token == "position") { position(pos, is, states); - if (Options["Clean_Search"] == 1) + if (Options["Clean_Search"] == true) Search::clear(); } else if (token == "p") { position(pos, is, states); - if (Options["Clean Search"] == 1) + if (Options["Clean_Search"] == true) Search::clear(); } else if (token == "ucinewgame") Search::clear(); diff --git a/Engines/Linux64/mcbrain/src/uci.h b/Engines/Linux64/mcbrain/src/uci.h index 1564f72..713468b 100644 --- a/Engines/Linux64/mcbrain/src/uci.h +++ b/Engines/Linux64/mcbrain/src/uci.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -51,11 +51,13 @@ class Option { Option(bool v, OnChange = nullptr); Option(const char* v, OnChange = nullptr); Option(int v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char *cur, OnChange = nullptr); Option& operator=(const std::string&); void operator<<(const Option&); operator int() const; operator std::string() const; + bool operator==(const char*); private: friend std::ostream& operator<<(std::ostream&, const OptionsMap&); diff --git a/Engines/Linux64/mcbrain/src/ucioption.cpp b/Engines/Linux64/mcbrain/src/ucioption.cpp index 8389ca1..3c31396 100644 --- a/Engines/Linux64/mcbrain/src/ucioption.cpp +++ b/Engines/Linux64/mcbrain/src/ucioption.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -60,8 +60,15 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const void init(OptionsMap& o) { - const int MaxHashMB = Is64Bit ? 1024 * 1024 : 2048; + // at most 2^32 clusters. + const int MaxHashMB = Is64Bit ? 131072 : 2048; + o["Debug Log File"] << Option("", on_logger); + o["Contempt"] << Option(20, -100, 100); + o["Analysis Contempt"] << Option("Off var Off var White var Black var Both", "Off"); + o["UCI_AnalyseMode"] << Option(false); + o["Tactical"] << Option(0, 0, 8); + o["Threads"] << Option(1, 1, 512, on_threads); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Ponder"] << Option(false); o["Threads"] << Option(1, 1, 512, on_threads); @@ -70,21 +77,25 @@ void init(OptionsMap& o) { o["BruteForce"] << Option(false); o["FastPlay"] << Option(false); o["No_Null_Moves"] << Option(false); - + o["UCI_Chess960"] << Option(false); o["UCI_LimitStrength"] << Option(false); + o["UCI_ELO"] << Option(1500, 1500, 2800); - o["MultiPV"] << Option(1, 1, 500); o["Skill Level"] << Option(20, 0, 20); - o["Contempt"] << Option(2, -150, 150); - o["Tactical"] << Option(0, 0, 8); - o["Move Overhead"] << Option(100, 0, 5000); + o["MultiPV"] << Option(1, 1, 256); + + + o["Move Overhead"] << Option(30, 0, 5000); + o["Minimum Thinking Time"] << Option(20, 0, 5000); + o["Slow Mover"] << Option(89, 10, 1000); o["nodestime"] << Option(0, 0, 10000); - o["UCI_Chess960"] << Option(false); + o["SyzygyPath"] << Option("", on_tb_path); o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(6, 0, 6); + o["Book_Enabled"] << Option(false); o["BookFile"] << Option("", on_book_file); o["BestBookMove"] << Option(true, on_best_book_move); o["BookDepth"] << Option(255, 1, 255, on_book_depth); @@ -131,6 +142,9 @@ Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f) Option::Option(int v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f) { defaultValue = currentValue = std::to_string(v); } +Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f) +{ defaultValue = v; currentValue = cur; } + Option::operator int() const { assert(type == "check" || type == "spin"); return (type == "spin" ? stoi(currentValue) : currentValue == "true"); @@ -141,6 +155,11 @@ Option::operator std::string() const { return currentValue; } +bool Option::operator==(const char* s) { + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); +} + /// operator<<() inits options and assigns idx in the correct printing order diff --git a/Engines/Linux64/stockfish/.travis.yml b/Engines/Linux64/stockfish/.travis.yml index af0bb2b..8586921 100644 --- a/Engines/Linux64/stockfish/.travis.yml +++ b/Engines/Linux64/stockfish/.travis.yml @@ -9,7 +9,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-6', 'g++-6-multilib', 'g++-multilib', 'valgrind'] + packages: ['g++-6', 'g++-6-multilib', 'g++-multilib', 'valgrind', 'expect'] env: - COMPILER=g++-6 - COMP=gcc @@ -19,7 +19,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['clang', 'g++-multilib', 'valgrind'] + packages: ['clang', 'g++-multilib', 'valgrind', 'expect'] env: - COMPILER=clang++ - COMP=clang @@ -44,12 +44,30 @@ before_script: - cd src script: - - make clean && make build ARCH=x86-64 && ./stockfish bench 2>&1 >/dev/null | grep 'Nodes searched' | tee bench1 - - make clean && make build ARCH=x86-32 && ./stockfish bench 2>&1 >/dev/null | grep 'Nodes searched' | tee bench2 - - echo "Checking for same bench numbers..." - - diff bench1 bench2 > result - - test ! -s result - # if valgrind is available check the build is without error, reduce depth to speedup testing, but not too shallow to catch more cases. - - if [ -x "$(command -v valgrind )" ] ; then make clean && make ARCH=x86-64 debug=yes build && valgrind --error-exitcode=42 ./stockfish bench 128 1 10 default depth 1>/dev/null ; fi - # use g++-6 as a proxy for having sanitizers ... might need revision as they become available for more recent versions of clang/gcc than trusty provides - - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make ARCH=x86-64 sanitize=yes build && ! ./stockfish bench 2>&1 | grep "runtime error:" ; fi + # Obtain bench reference from git log + - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9][0-9]*\)/\1/g" > git_sig + - export benchref=$(cat git_sig) + - echo "Reference bench:" $benchref + # + # Verify bench number against various builds + - export CXXFLAGS=-Werror + - make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-64 build && ../tests/signature.sh $benchref + # + # Check perft and reproducible search + - ../tests/perft.sh + - ../tests/reprosearch.sh + # + # Valgrind + # + - export CXXFLAGS=-O1 + - if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi + - if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi + # + # Sanitizer + # + # Use g++-6 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=thread optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi diff --git a/Engines/Linux64/stockfish/AUTHORS b/Engines/Linux64/stockfish/AUTHORS index 5cd63d2..9b91b93 100644 --- a/Engines/Linux64/stockfish/AUTHORS +++ b/Engines/Linux64/stockfish/AUTHORS @@ -1,98 +1,98 @@ -# Generated with git shortlog -sn | cut -c8-', which sorts by commits (manually ordered the first four authors) +# Generated with 'git shortlog -sn | cut -c8-', which sorts by commits, manually ordered the first four authors, merged duplicates Tord Romstad -Marco Costalba -Joona Kiiski -Gary Linscott -lucasart +Marco Costalba (mcostalba) +Joona Kiiski (zamar) +Gary Linscott (glinscott) +Lucas Braesch (lucasart) +Bill Henry (VoyagerOne) mstembera -Lucas Braesch +Stéphane Nicolet (Stephane Nicolet, snicolet) Stefan Geschwentner +Alain SAVARD (Rocky640) +Jörg Oster (Joerg Oster, joergoster) Reuven Peleg -Chris Caino -joergoster -VoyagerOne +Chris Caino (Chris Cain, ceebo) Jean-Francois Romang homoSapiensSapiens -Alain SAVARD +Leonid Pechenik +Stefano Cardanobile (Stefano80) Arjun Temurnikar -Stéphane Nicolet -Uri Blass +Uri Blass (uriblass) jundery -Ralph Stößer -Ajith -Leonid Pechenik -Stefano80 -Tom Vijlbrief +Ajith (ajithcj) hxim -snicolet +Ralph Stößer (Ralph Stoesser) +Guenther Demetz +Jonathan Calovski (Mysseno) +Tom Vijlbrief +mbootsector Daylen Yang +ElbertoOne Henri Wiechers -Jonathan Calovski -mbootsector +loco-loco +Joost VandeVondele (Joost Vandevondele) +Ronald de Man (syzygy) +DU-jdto David Zar Eelco de Groot Jerry Donald -Joerg Oster -Jörg Oster +NicklasPersson Ryan Schmitt -mcostalba Alexander Kure Dan Schmidt H. Felix Wittmann +Jacques Joseph R. Prostko Justin Blanchard Linus Arver -NicklasPersson +Luca Brivio +Lyudmil Antonov Rodrigo Exterckötter Tjäder Ron Britvich -Ronald de Man RyanTaker Vince Negri -ceebo -jhellis3 -ppigazzini +erbsenzaehler +Joseph Hellis (jhellis3) shane31 +Andrew Grant Andy Duplain Auguste Pop Balint Pfliegel -Chris Cain -DU-jdto Dariusz Orzechowski DiscanX Ernesto Gatti Gregor Cramer -Guenther Demetz -Hiraoka Takuya +Hiraoka Takuya (HiraokaTakuya) Hongzhi Cheng -Joseph Hellis +IIvec Kelly Wilson Ken T Takusagawa Kojirion -Luca Brivio +Krgp Matt Sullivan Matthew Lai Matthew Sullivan Michel Van den Bergh -Mysseno +Niklas Fiekas Oskar Werkelin Ahlin Pablo Vazquez Pascal Romaret -Ralph Stoesser -Ralph Stößer Raminder Singh Richard Lloyd Ryan Takker -Stephane Nicolet Thanar2 absimaldata +atumanian braich +fanon +gamander gguliash kinderchocolate -loco-loco pellanda +ppigazzini renouve sf-x thaspel unknown -uriblass + diff --git a/Engines/Linux64/stockfish/Linux/stockfish-9-64 b/Engines/Linux64/stockfish/Linux/stockfish-9-64 new file mode 100644 index 0000000..69e30cc Binary files /dev/null and b/Engines/Linux64/stockfish/Linux/stockfish-9-64 differ diff --git a/Engines/Linux64/stockfish/Readme.md b/Engines/Linux64/stockfish/Readme.md index 29ebf59..e3b36cc 100644 --- a/Engines/Linux64/stockfish/Readme.md +++ b/Engines/Linux64/stockfish/Readme.md @@ -10,7 +10,7 @@ Partner or Fritz) in order to be used comfortably. Read the documentation for your GUI of choice for information about how to use Stockfish with it. -This version of Stockfish supports up to 128 cores. The engine defaults +This version of Stockfish supports up to 512 cores. The engine defaults to one search thread, so it is therefore recommended to inspect the value of the *Threads* UCI parameter, and to make sure it equals the number of CPU cores on your computer. @@ -96,6 +96,14 @@ compile (for instance with Microsoft MSVC) you need to manually set/unset some switches in the compiler command line; see file *types.h* for a quick reference. +### Resource For Understanding the Code Base + +* [Chessprogramingwiki](https://chessprogramming.wikispaces.com) has good overall chess engines explanations +(techniques used here are well explained like hash maps etc), it was +also recommended by the [support at stockfish.](http://support.stockfishchess.org/discussions/questions/1132-how-to-understand-stockfish-sources) + +* [Here](https://chessprogramming.wikispaces.com/Stockfish) you can find a set of features and techniques used by stockfish and each of them is explained at the wiki, however, it's a generic way rather than focusing on stockfish's own implementation, but it will still help you. + ### Terms of use diff --git a/Engines/Linux64/stockfish/Top CPU Contributors.txt b/Engines/Linux64/stockfish/Top CPU Contributors.txt new file mode 100644 index 0000000..4c2aa47 --- /dev/null +++ b/Engines/Linux64/stockfish/Top CPU Contributors.txt @@ -0,0 +1,132 @@ +Contributors with >10,000 CPU hours as of January 23, 2018 +Thank you! + +Username CPU Hours Games played +mibere 518300 41835669 +crunchy 375564 29121434 +cw 371664 28748719 +fastgm 299773 20765374 +JojoM 220590 15299913 +glinscott 204517 13932027 +bking_US 187568 12233168 +ctoks 169342 13475495 +spams 149531 10940322 +Thanar 137015 11714855 +velislav 127305 10047749 +vdbergh 121741 9056874 +malala 117291 8126488 +vdv 117218 8289983 +leszek 114825 8331897 +dsmith 114010 7622414 +CSU_Dynasty 113516 9582758 +sqrt2 112407 8782694 +marrco 111143 8222921 +drabel 108168 9061580 +BrunoBanani 104938 7448565 +Data 94621 8433010 +CoffeeOne 90394 3964243 +BRAVONE 80811 5341681 +psk 77195 6156031 +brabos 70284 5685893 +Fisherman 66650 5572406 +nssy 64587 5369140 +Pking_cda 64499 5704075 +sterni1971 63488 5070004 +mgrabiak 62385 5420812 +tvijlbrief 58957 4154234 +jromang 58854 4704502 +dv8silencer 57421 3961325 +sunu 56620 4609155 +tinker 56039 4204914 +biffhero 55743 4810039 +teddybaer 52982 4740444 +bcross 50548 5071599 +renouve 50318 3544864 +Freja 50296 3805120 +robnjr 47504 4131742 +eva42 46542 4044694 +davar 46538 4030604 +finfish 46244 3481661 +rap 46201 3219490 +ttruscott 45037 3645430 +solarlight 44155 4074841 +TueRens 41372 3891510 +ElbertoOne 41321 3920894 +Antihistamine 39218 2792761 +mhunt 38991 2697512 +bigpen0r 37820 3149955 +homyur 35569 3009637 +VoyagerOne 35137 3302650 +mhoram 34770 2684128 +racerschmacer 33022 3231055 +speedycpu 32043 2531964 +EthanOConnor 31638 2143255 +oryx 29574 2767730 +Pyafue 28885 1986098 +jkiiski 28014 1923255 +Garf 27579 2770144 +slakovv 27017 2031279 +Bobo1239 27000 2488707 +pb00067 26817 2306694 +robal 26337 2316795 +hyperbolic.tom 26248 2200777 +rkl 24898 2236013 +SC 23988 2126825 +nabildanial 23524 1586321 +achambord 23495 1942546 +Sharaf_DG 22975 1790697 +chriswk 22876 1947731 +anst 22568 2013953 +Patrick_G 22435 1682293 +cuistot 22201 1383031 +gri 21901 1820968 +Prcuvu 21182 1890546 +Zirie 21171 1493227 +JanErik 20596 1791991 +Isidor 20560 1730290 +xor12 20535 1819280 +team-oh 20364 1653708 +nesoneg 20264 1493435 +rstoesser 19802 1335177 +grandphish2 19402 1834196 +sg4032 18427 1671742 +dew 18263 1423326 +ianh2105 18133 1668562 +MazeOfGalious 18022 1644593 +ville 17900 1539130 +j3corre 17607 975954 +eudhan 17502 1424648 +iisiraider 17175 1118788 +jundery 17172 1115855 +SFTUser 16635 1363975 +purplefishies 16621 1106850 +DragonLord 16599 1252348 +chris 15274 1575333 +xoto 14900 1486261 +dju 14861 901552 +dex 14647 1228763 +nordlandia 14551 1369718 +ronaldjerum 14361 1210607 +OssumOpossum 14149 1029265 +IgorLeMasson 13844 1228391 +enedene 13762 935618 +ako027ako 13442 1250249 +AdrianSA 13324 924980 +bpfliegel 13318 886523 +ncfish1 13056 932344 +wei 12863 1369596 +jpulman 12776 854815 +horst.prack 12436 1151505 +joster 12424 986622 +cisco2015 12265 1205019 +fatmurphy 12015 901134 +modolief 11228 926456 +Dark_wizzie 11214 1017910 +mschmidt 10973 818594 +eastorwest 10970 1117836 +infinity 10762 746397 +SapphireBrand 10692 1024604 +Thomas A. 10553 736094 +pgontarz 10294 878746 +Andrew Grant 10195 922933 +stocky 10083 718114 diff --git a/Engines/Linux64/stockfish/appveyor.yml b/Engines/Linux64/stockfish/appveyor.yml index a46a0f3..c711dd6 100644 --- a/Engines/Linux64/stockfish/appveyor.yml +++ b/Engines/Linux64/stockfish/appveyor.yml @@ -1,5 +1,5 @@ version: 1.0.{build} -clone_depth: 5 +clone_depth: 50 branches: only: @@ -13,10 +13,11 @@ os: Visual Studio 2015 platform: - x86 - x64 - - Any CPU # build Configuration, i.e. Debug, Release, etc. -configuration: Debug +configuration: + - Debug + - Release matrix: # The build fail immediately once one of the job fails @@ -28,18 +29,43 @@ init: - msbuild /version before_build: - - cd src - - echo project (Stockfish) >> CMakeLists.txt - - echo add_executable(stockfish benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp >> CMakeLists.txt - - echo main.cpp material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp >> CMakeLists.txt - - echo search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp syzygy/tbprobe.cpp) >> CMakeLists.txt - - echo set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src) >> CMakeLists.txt -# - echo target_compile_options(stockfish PUBLIC "/Ox") >> CMakeLists.txt + - ps: | + # Get sources + $src = get-childitem -Path *.cpp -Recurse | select -ExpandProperty FullName + $src = $src -join ' ' + $src = $src.Replace("\", "/") + + # Build CMakeLists.txt + $t = 'cmake_minimum_required(VERSION 3.8)', + 'project(Stockfish)', + 'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)', + 'set(source_files', $src, ')', + 'add_executable(stockfish ${source_files})' + + # Write CMakeLists.txt withouth BOM + $MyPath = (Get-Item -Path "." -Verbose).FullName + '\CMakeLists.txt' + $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False + [System.IO.File]::WriteAllLines($MyPath, $t, $Utf8NoBomEncoding) + + # Obtain bench reference from git log + $b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1 + $bench = $b -match '\D+(\d+)' | % { $matches[1] } + Write-Host "Reference bench:" $bench + $g = "Visual Studio 14 2015" + If (${env:PLATFORM} -eq 'x64') { $g = $g + ' Win64' } + cmake -G "${g}" . + Write-Host "Generated files for: " $g build_script: - - cmake -G "Visual Studio 14 2015 Win64" . - - cmake --build . + - cmake --build . --config %CONFIGURATION% -- /verbosity:minimal before_test: - - cd Debug - - stockfish.exe bench > null + - cd src/%CONFIGURATION% + - ps: | + # Verify bench number + ./stockfish bench 2> out.txt 1> null + $s = (gc "./out.txt" | out-string) + $r = ($s -match 'Nodes searched \D+(\d+)' | % { $matches[1] }) + Write-Host "Engine bench:" $r + Write-Host "Reference bench:" $bench + If ($r -ne $bench) { exit 1 } diff --git a/Engines/Linux64/stockfish/src/Makefile b/Engines/Linux64/stockfish/src/Makefile index cdedd25..72afcc3 100644 --- a/Engines/Linux64/stockfish/src/Makefile +++ b/Engines/Linux64/stockfish/src/Makefile @@ -1,7 +1,7 @@ # Stockfish, a UCI chess playing engine derived from Glaurung 2.1 # Copyright (C) 2004-2008 Tord Romstad (Glaurung author) # Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad -# Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad +# Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad # # Stockfish is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -50,7 +50,9 @@ OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \ # ---------------------------------------------------------------------------- # # debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode -# sanitize = yes/no --- (-fsanitize ) --- enable undefined behavior checks +# sanitize = undefined/thread/no (-fsanitize ) +# --- ( undefined ) --- enable undefined behavior checks +# --- ( thread ) --- enable threading error checks # optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations # arch = (name) --- (-arch) --- Target architecture # bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system @@ -139,7 +141,7 @@ endif ### 3.1 Selecting compiler (default = gcc) -CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -fno-rtti -std=c++11 $(EXTRACXXFLAGS) +CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++11 $(EXTRACXXFLAGS) DEPENDFLAGS += -std=c++11 LDFLAGS += $(EXTRALDFLAGS) @@ -155,9 +157,11 @@ ifeq ($(COMP),gcc) ifeq ($(ARCH),armv7) ifeq ($(OS),Android) CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) endif else CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) endif ifneq ($(KERNEL),Darwin) @@ -200,6 +204,11 @@ ifeq ($(COMP),clang) comp=clang CXX=clang++ CXXFLAGS += -pedantic -Wextra -Wshadow +ifneq ($(KERNEL),Darwin) +ifneq ($(KERNEL),OpenBSD) + LDFLAGS += -latomic +endif +endif ifeq ($(ARCH),armv7) ifeq ($(OS),Android) @@ -210,23 +219,19 @@ ifeq ($(COMP),clang) CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) endif - - ifeq ($(KERNEL),Darwin) - CXXFLAGS += -stdlib=libc++ - DEPENDFLAGS += -stdlib=libc++ - endif endif ifeq ($(comp),icc) - profile_prepare = icc-profile-prepare profile_make = icc-profile-make profile_use = icc-profile-use - profile_clean = icc-profile-clean else - profile_prepare = gcc-profile-prepare +ifeq ($(comp),clang) + profile_make = clang-profile-make + profile_use = clang-profile-use +else profile_make = gcc-profile-make profile_use = gcc-profile-use - profile_clean = gcc-profile-clean +endif endif ifeq ($(KERNEL),Darwin) @@ -263,8 +268,9 @@ else endif ### 3.2.2 Debugging with undefined behavior sanitizers -ifeq ($(sanitize),yes) - CXXFLAGS += -g3 -fsanitize=undefined +ifneq ($(sanitize),no) + CXXFLAGS += -g3 -fsanitize=$(sanitize) -fuse-ld=gold + LDFLAGS += -fsanitize=$(sanitize) -fuse-ld=gold endif ### 3.3 Optimization @@ -296,10 +302,8 @@ ifeq ($(optimize),yes) ifeq ($(comp),clang) ifeq ($(KERNEL),Darwin) - ifeq ($(pext),no) CXXFLAGS += -flto LDFLAGS += $(CXXFLAGS) - endif ifeq ($(arch),i386) CXXFLAGS += -mdynamic-no-pic endif @@ -423,30 +427,27 @@ help: @echo "" -.PHONY: build profile-build -build: - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity +.PHONY: help build profile-build strip install clean objclean profileclean help \ + config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \ + clang-profile-use clang-profile-make + +build: config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all -profile-build: - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity - @echo "" - @echo "Step 0/4. Preparing for profile build." - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_prepare) +profile-build: config-sanity objclean profileclean @echo "" - @echo "Step 1/4. Building executable for benchmark ..." - @touch *.cpp *.h syzygy/*.cpp syzygy/*.h + @echo "Step 1/4. Building instrumented executable ..." $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) @echo "" @echo "Step 2/4. Running benchmark for pgo-build ..." $(PGOBENCH) > /dev/null @echo "" - @echo "Step 3/4. Building final executable ..." - @touch *.cpp *.h syzygy/*.cpp syzygy/*.h + @echo "Step 3/4. Building optimized executable ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use) @echo "" @echo "Step 4/4. Deleting profile data ..." - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_clean) + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean strip: strip $(EXE) @@ -456,8 +457,19 @@ install: -cp $(EXE) $(BINDIR) -strip $(BINDIR)/$(EXE) -clean: - $(RM) $(EXE) $(EXE).exe *.o .depend *~ core bench.txt *.gcda ./syzygy/*.o ./syzygy/*.gcda +#clean all +clean: objclean profileclean + @rm -f .depend *~ core + +# clean binaries and objects +objclean: + @rm -f $(EXE) $(EXE).exe *.o ./syzygy/*.o + +# clean auxiliary profiling files +profileclean: + @rm -rf profdir + @rm -f bench.txt *.gcda ./syzygy/*.gcda *.gcno ./syzygy/*.gcno + @rm -f stockfish.profdata *.profraw default: help @@ -472,6 +484,7 @@ config-sanity: @echo "" @echo "Config:" @echo "debug: '$(debug)'" + @echo "sanitize: '$(sanitize)'" @echo "optimize: '$(optimize)'" @echo "arch: '$(arch)'" @echo "bits: '$(bits)'" @@ -490,6 +503,7 @@ config-sanity: @echo "Testing config sanity. If this fails, try 'make help' ..." @echo "" @test "$(debug)" = "yes" || test "$(debug)" = "no" + @test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "no" @test "$(optimize)" = "yes" || test "$(optimize)" = "no" @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "armv7" @@ -503,8 +517,18 @@ config-sanity: $(EXE): $(OBJS) $(CXX) -o $@ $(OBJS) $(LDFLAGS) -gcc-profile-prepare: - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) gcc-profile-clean +clang-profile-make: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-generate ' \ + EXTRALDFLAGS=' -fprofile-instr-generate' \ + all + +clang-profile-use: + llvm-profdata merge -output=stockfish.profdata *.profraw + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \ + EXTRALDFLAGS='-fprofile-use ' \ + all gcc-profile-make: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ @@ -518,14 +542,8 @@ gcc-profile-use: EXTRALDFLAGS='-lgcov' \ all -gcc-profile-clean: - @rm -rf *.gcda *.gcno syzygy/*.gcda syzygy/*.gcno bench.txt - -icc-profile-prepare: - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) icc-profile-clean - @mkdir profdir - icc-profile-make: + @mkdir -p profdir $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \ all @@ -535,9 +553,6 @@ icc-profile-use: EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \ all -icc-profile-clean: - @rm -rf profdir bench.txt - .depend: -@$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) > $@ 2> /dev/null diff --git a/Engines/Linux64/stockfish/src/benchmark.cpp b/Engines/Linux64/stockfish/src/benchmark.cpp index f2d1f06..1c69dca 100644 --- a/Engines/Linux64/stockfish/src/benchmark.cpp +++ b/Engines/Linux64/stockfish/src/benchmark.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,23 +23,20 @@ #include #include -#include "misc.h" #include "position.h" -#include "search.h" -#include "thread.h" -#include "uci.h" using namespace std; namespace { const vector Defaults = { + "setoption name UCI_Chess960 value false", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11", "4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19", - "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14", - "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14", + "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6", + "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4", "r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15", "r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13", "r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16", @@ -52,7 +49,7 @@ const vector Defaults = { "3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26", "6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1", "3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1", - "2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1", + "2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3", "8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1", "7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1", "8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1", @@ -79,28 +76,35 @@ const vector Defaults = { "8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124", // Draw // Mate and stalemate positions + "6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1", + "r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1", "8/8/8/8/8/6k1/6p1/6K1 w - -", - "5k2/5P2/5K2/8/8/8/8/8 b - -", - "8/8/8/8/8/4k3/4p3/4K3 w - -", - "8/8/8/8/8/5K2/8/3Q1k2 b - -", - "7k/7P/6K1/8/3B4/8/8/8 b - -" + "7k/7P/6K1/8/3B4/8/8/8 b - -", + + // Chess 960 + "setoption name UCI_Chess960 value true", + "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w KQkq - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6", + "setoption name UCI_Chess960 value false" }; } // namespace -/// benchmark() runs a simple benchmark by letting Stockfish analyze a set -/// of positions for a given limit each. There are five parameters: the -/// transposition table size, the number of search threads that should -/// be used, the limit value spent for each position (optional, default is -/// depth 13), an optional file name where to look for positions in FEN -/// format (defaults are the positions defined above) and the type of the -/// limit value: depth (default), time in millisecs or number of nodes. +/// setup_bench() builds a list of UCI commands to be run by bench. There +/// are five parameters: TT size in MB, number of search threads that +/// should be used, the limit value spent for each position, a file name +/// where to look for positions in FEN format and the type of the limit: +/// depth, perft, nodes and movetime (in millisecs). +/// +/// bench -> search default positions up to depth 13 +/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB) +/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec +/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each +/// bench 16 1 5 default perft -> run a perft 5 on default positions -void benchmark(const Position& current, istream& is) { +vector setup_bench(const Position& current, istream& is) { - string token; - vector fens; - Search::LimitsType limits; + vector fens, list; + string go, token; // Assign default values to missing arguments string ttSize = (is >> token) ? token : "16"; @@ -109,21 +113,7 @@ void benchmark(const Position& current, istream& is) { string fenFile = (is >> token) ? token : "default"; string limitType = (is >> token) ? token : "depth"; - Options["Hash"] = ttSize; - Options["Threads"] = threads; - Search::clear(); - - if (limitType == "time") - limits.movetime = stoi(limit); // movetime is in millisecs - - else if (limitType == "nodes") - limits.nodes = stoi(limit); - - else if (limitType == "mate") - limits.mate = stoi(limit); - - else - limits.depth = stoi(limit); + go = "go " + limitType + " " + limit; if (fenFile == "default") fens = Defaults; @@ -139,7 +129,7 @@ void benchmark(const Position& current, istream& is) { if (!file.is_open()) { cerr << "Unable to open file " << fenFile << endl; - return; + exit(EXIT_FAILURE); } while (getline(file, fen)) @@ -149,35 +139,18 @@ void benchmark(const Position& current, istream& is) { file.close(); } - uint64_t nodes = 0; - TimePoint elapsed = now(); - Position pos; - - for (size_t i = 0; i < fens.size(); ++i) - { - StateListPtr states(new std::deque(1)); - pos.set(fens[i], Options["UCI_Chess960"], &states->back(), Threads.main()); - - cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl; - - if (limitType == "perft") - nodes += Search::perft(pos, limits.depth * ONE_PLY); + list.emplace_back("ucinewgame"); + list.emplace_back("setoption name Threads value " + threads); + list.emplace_back("setoption name Hash value " + ttSize); + for (const string& fen : fens) + if (fen.find("setoption") != string::npos) + list.emplace_back(fen); else { - limits.startTime = now(); - Threads.start_thinking(pos, states, limits); - Threads.main()->wait_for_search_finished(); - nodes += Threads.nodes_searched(); + list.emplace_back("position fen " + fen); + list.emplace_back(go); } - } - - elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' - - dbg_print(); // Just before exiting - cerr << "\n===========================" - << "\nTotal time (ms) : " << elapsed - << "\nNodes searched : " << nodes - << "\nNodes/second : " << 1000 * nodes / elapsed << endl; + return list; } diff --git a/Engines/Linux64/stockfish/src/bitbase.cpp b/Engines/Linux64/stockfish/src/bitbase.cpp index 4170952..3c9bf6e 100644 --- a/Engines/Linux64/stockfish/src/bitbase.cpp +++ b/Engines/Linux64/stockfish/src/bitbase.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -111,13 +111,13 @@ namespace { ksq[WHITE] = Square((idx >> 0) & 0x3F); ksq[BLACK] = Square((idx >> 6) & 0x3F); us = Color ((idx >> 12) & 0x01); - psq = make_square(File((idx >> 13) & 0x3), RANK_7 - Rank((idx >> 15) & 0x7)); + psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7))); // Check if two pieces are on the same square or if a king can be captured if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 || ksq[WHITE] == psq || ksq[BLACK] == psq - || (us == WHITE && (StepAttacksBB[PAWN][psq] & ksq[BLACK]))) + || (us == WHITE && (PawnAttacks[WHITE][psq] & ksq[BLACK]))) result = INVALID; // Immediate win if a pawn can be promoted without getting captured @@ -125,13 +125,13 @@ namespace { && rank_of(psq) == RANK_7 && ksq[us] != psq + NORTH && ( distance(ksq[~us], psq + NORTH) > 1 - || (StepAttacksBB[KING][ksq[us]] & (psq + NORTH)))) + || (PseudoAttacks[KING][ksq[us]] & (psq + NORTH)))) result = WIN; // Immediate draw if it is a stalemate or a king captures undefended pawn else if ( us == BLACK - && ( !(StepAttacksBB[KING][ksq[us]] & ~(StepAttacksBB[KING][ksq[~us]] | StepAttacksBB[PAWN][psq])) - || (StepAttacksBB[KING][ksq[us]] & psq & ~StepAttacksBB[KING][ksq[~us]]))) + && ( !(PseudoAttacks[KING][ksq[us]] & ~(PseudoAttacks[KING][ksq[~us]] | PawnAttacks[~us][psq])) + || (PseudoAttacks[KING][ksq[us]] & psq & ~PseudoAttacks[KING][ksq[~us]]))) result = DRAW; // Position will be classified later @@ -157,7 +157,7 @@ namespace { const Result Bad = (Us == WHITE ? DRAW : WIN); Result r = INVALID; - Bitboard b = StepAttacksBB[KING][ksq[Us]]; + Bitboard b = PseudoAttacks[KING][ksq[Us]]; while (b) r |= Us == WHITE ? db[index(Them, ksq[Them] , pop_lsb(&b), psq)] diff --git a/Engines/Linux64/stockfish/src/bitboard.cpp b/Engines/Linux64/stockfish/src/bitboard.cpp index e329ab5..ba00c78 100644 --- a/Engines/Linux64/stockfish/src/bitboard.cpp +++ b/Engines/Linux64/stockfish/src/bitboard.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,29 +26,22 @@ uint8_t PopCnt16[1 << 16]; int SquareDistance[SQUARE_NB][SQUARE_NB]; -Bitboard RookMasks [SQUARE_NB]; -Bitboard RookMagics [SQUARE_NB]; -Bitboard* RookAttacks[SQUARE_NB]; -unsigned RookShifts [SQUARE_NB]; - -Bitboard BishopMasks [SQUARE_NB]; -Bitboard BishopMagics [SQUARE_NB]; -Bitboard* BishopAttacks[SQUARE_NB]; -unsigned BishopShifts [SQUARE_NB]; - Bitboard SquareBB[SQUARE_NB]; Bitboard FileBB[FILE_NB]; Bitboard RankBB[RANK_NB]; Bitboard AdjacentFilesBB[FILE_NB]; -Bitboard InFrontBB[COLOR_NB][RANK_NB]; -Bitboard StepAttacksBB[PIECE_NB][SQUARE_NB]; +Bitboard ForwardRanksBB[COLOR_NB][RANK_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard DistanceRingBB[SQUARE_NB][8]; -Bitboard ForwardBB[COLOR_NB][SQUARE_NB]; +Bitboard ForwardFileBB[COLOR_NB][SQUARE_NB]; Bitboard PassedPawnMask[COLOR_NB][SQUARE_NB]; Bitboard PawnAttackSpan[COLOR_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; +Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; + +Magic RookMagics[SQUARE_NB]; +Magic BishopMagics[SQUARE_NB]; namespace { @@ -61,10 +54,7 @@ namespace { Bitboard RookTable[0x19000]; // To store rook attacks Bitboard BishopTable[0x1480]; // To store bishop attacks - typedef unsigned (Fn)(Square, Bitboard); - - void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[], - Bitboard masks[], unsigned shifts[], Square deltas[], Fn index); + void init_magics(Bitboard table[], Magic magics[], Direction directions[]); // bsf_index() returns the index into BSFTable[] to look up the bitscan. Uses // Matt Taylor's folding for 32 bit case, extended to 64 bit by Kim Walisch. @@ -173,14 +163,14 @@ void Bitboards::init() { AdjacentFilesBB[f] = (f > FILE_A ? FileBB[f - 1] : 0) | (f < FILE_H ? FileBB[f + 1] : 0); for (Rank r = RANK_1; r < RANK_8; ++r) - InFrontBB[WHITE][r] = ~(InFrontBB[BLACK][r + 1] = InFrontBB[BLACK][r] | RankBB[r]); + ForwardRanksBB[WHITE][r] = ~(ForwardRanksBB[BLACK][r + 1] = ForwardRanksBB[BLACK][r] | RankBB[r]); for (Color c = WHITE; c <= BLACK; ++c) for (Square s = SQ_A1; s <= SQ_H8; ++s) { - ForwardBB[c][s] = InFrontBB[c][rank_of(s)] & FileBB[file_of(s)]; - PawnAttackSpan[c][s] = InFrontBB[c][rank_of(s)] & AdjacentFilesBB[file_of(s)]; - PassedPawnMask[c][s] = ForwardBB[c][s] | PawnAttackSpan[c][s]; + ForwardFileBB [c][s] = ForwardRanksBB[c][rank_of(s)] & FileBB[file_of(s)]; + PawnAttackSpan[c][s] = ForwardRanksBB[c][rank_of(s)] & AdjacentFilesBB[file_of(s)]; + PassedPawnMask[c][s] = ForwardFileBB [c][s] | PawnAttackSpan[c][s]; } for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) @@ -191,39 +181,43 @@ void Bitboards::init() { DistanceRingBB[s1][SquareDistance[s1][s2] - 1] |= s2; } - int steps[][9] = { {}, { 7, 9 }, { 17, 15, 10, 6, -6, -10, -15, -17 }, - {}, {}, {}, { 9, 7, -7, -9, 8, 1, -1, -8 } }; + int steps[][5] = { {}, { 7, 9 }, { 6, 10, 15, 17 }, {}, {}, {}, { 1, 7, 8, 9 } }; for (Color c = WHITE; c <= BLACK; ++c) - for (PieceType pt = PAWN; pt <= KING; ++pt) + for (PieceType pt : { PAWN, KNIGHT, KING }) for (Square s = SQ_A1; s <= SQ_H8; ++s) for (int i = 0; steps[pt][i]; ++i) { - Square to = s + Square(c == WHITE ? steps[pt][i] : -steps[pt][i]); + Square to = s + Direction(c == WHITE ? steps[pt][i] : -steps[pt][i]); if (is_ok(to) && distance(s, to) < 3) - StepAttacksBB[make_piece(c, pt)][s] |= to; + { + if (pt == PAWN) + PawnAttacks[c][s] |= to; + else + PseudoAttacks[pt][s] |= to; + } } - Square RookDeltas[] = { NORTH, EAST, SOUTH, WEST }; - Square BishopDeltas[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; + Direction RookDirections[] = { NORTH, EAST, SOUTH, WEST }; + Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; - init_magics(RookTable, RookAttacks, RookMagics, RookMasks, RookShifts, RookDeltas, magic_index); - init_magics(BishopTable, BishopAttacks, BishopMagics, BishopMasks, BishopShifts, BishopDeltas, magic_index); + init_magics(RookTable, RookMagics, RookDirections); + init_magics(BishopTable, BishopMagics, BishopDirections); for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) { PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0); - for (Piece pc = W_BISHOP; pc <= W_ROOK; ++pc) + for (PieceType pt : { BISHOP, ROOK }) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) { - if (!(PseudoAttacks[pc][s1] & s2)) + if (!(PseudoAttacks[pt][s1] & s2)) continue; - LineBB[s1][s2] = (attacks_bb(pc, s1, 0) & attacks_bb(pc, s2, 0)) | s1 | s2; - BetweenBB[s1][s2] = attacks_bb(pc, s1, SquareBB[s2]) & attacks_bb(pc, s2, SquareBB[s1]); + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = attacks_bb(pt, s1, SquareBB[s2]) & attacks_bb(pt, s2, SquareBB[s1]); } } } @@ -231,14 +225,14 @@ void Bitboards::init() { namespace { - Bitboard sliding_attack(Square deltas[], Square sq, Bitboard occupied) { + Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied) { Bitboard attack = 0; for (int i = 0; i < 4; ++i) - for (Square s = sq + deltas[i]; - is_ok(s) && distance(s, s - deltas[i]) == 1; - s += deltas[i]) + for (Square s = sq + directions[i]; + is_ok(s) && distance(s, s - directions[i]) == 1; + s += directions[i]) { attack |= s; @@ -255,17 +249,14 @@ namespace { // chessprogramming.wikispaces.com/Magic+Bitboards. In particular, here we // use the so called "fancy" approach. - void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[], - Bitboard masks[], unsigned shifts[], Square deltas[], Fn index) { + void init_magics(Bitboard table[], Magic magics[], Direction directions[]) { + // Optimal PRNG seeds to pick the correct magics in the shortest time int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, { 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } }; Bitboard occupancy[4096], reference[4096], edges, b; - int age[4096] = {0}, current = 0, i, size; - - // attacks[s] is a pointer to the beginning of the attacks table for square 's' - attacks[SQ_A1] = table; + int epoch[4096] = {}, cnt = 0, size = 0; for (Square s = SQ_A1; s <= SQ_H8; ++s) { @@ -277,28 +268,28 @@ namespace { // all the attacks for each possible subset of the mask and so is 2 power // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. - masks[s] = sliding_attack(deltas, s, 0) & ~edges; - shifts[s] = (Is64Bit ? 64 : 32) - popcount(masks[s]); + Magic& m = magics[s]; + m.mask = sliding_attack(directions, s, 0) & ~edges; + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); + + // Set the offset for the attacks table of the square. We have individual + // table sizes for each square with "Fancy Magic Bitboards". + m.attacks = s == SQ_A1 ? table : magics[s - 1].attacks + size; // Use Carry-Rippler trick to enumerate all subsets of masks[s] and // store the corresponding sliding attack bitboard in reference[]. b = size = 0; do { occupancy[size] = b; - reference[size] = sliding_attack(deltas, s, b); + reference[size] = sliding_attack(directions, s, b); if (HasPext) - attacks[s][pext(b, masks[s])] = reference[size]; + m.attacks[pext(b, m.mask)] = reference[size]; size++; - b = (b - masks[s]) & masks[s]; + b = (b - m.mask) & m.mask; } while (b); - // Set the offset for the table of the next square. We have individual - // table sizes for each square with "Fancy Magic Bitboards". - if (s < SQ_H8) - attacks[s + 1] = attacks[s] + size; - if (HasPext) continue; @@ -306,28 +297,30 @@ namespace { // Find a magic for square 's' picking up an (almost) random number // until we find the one that passes the verification test. - do { - do - magics[s] = rng.sparse_rand(); - while (popcount((magics[s] * masks[s]) >> 56) < 6); + for (int i = 0; i < size; ) + { + for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; ) + m.magic = rng.sparse_rand(); // A good magic must map every possible occupancy to an index that // looks up the correct sliding attack in the attacks[s] database. // Note that we build up the database for square 's' as a side - // effect of verifying the magic. - for (++current, i = 0; i < size; ++i) + // effect of verifying the magic. Keep track of the attempt count + // and save it in epoch[], little speed-up trick to avoid resetting + // m.attacks[] after every failed attempt. + for (++cnt, i = 0; i < size; ++i) { - unsigned idx = index(s, occupancy[i]); + unsigned idx = m.index(occupancy[i]); - if (age[idx] < current) + if (epoch[idx] < cnt) { - age[idx] = current; - attacks[s][idx] = reference[i]; + epoch[idx] = cnt; + m.attacks[idx] = reference[i]; } - else if (attacks[s][idx] != reference[i]) + else if (m.attacks[idx] != reference[i]) break; } - } while (i < size); + } } } } diff --git a/Engines/Linux64/stockfish/src/bitboard.h b/Engines/Linux64/stockfish/src/bitboard.h index 715f6c4..c9f7242 100644 --- a/Engines/Linux64/stockfish/src/bitboard.h +++ b/Engines/Linux64/stockfish/src/bitboard.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,6 +39,7 @@ const std::string pretty(Bitboard b); } +const Bitboard AllSquares = ~Bitboard(0); const Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; const Bitboard FileABB = 0x0101010101010101ULL; @@ -65,15 +66,41 @@ extern Bitboard SquareBB[SQUARE_NB]; extern Bitboard FileBB[FILE_NB]; extern Bitboard RankBB[RANK_NB]; extern Bitboard AdjacentFilesBB[FILE_NB]; -extern Bitboard InFrontBB[COLOR_NB][RANK_NB]; -extern Bitboard StepAttacksBB[PIECE_NB][SQUARE_NB]; +extern Bitboard ForwardRanksBB[COLOR_NB][RANK_NB]; extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard DistanceRingBB[SQUARE_NB][8]; -extern Bitboard ForwardBB[COLOR_NB][SQUARE_NB]; +extern Bitboard ForwardFileBB[COLOR_NB][SQUARE_NB]; extern Bitboard PassedPawnMask[COLOR_NB][SQUARE_NB]; extern Bitboard PawnAttackSpan[COLOR_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; + + +/// Magic holds all magic bitboards relevant data for a single square +struct Magic { + Bitboard mask; + Bitboard magic; + Bitboard* attacks; + unsigned shift; + + // Compute the attack's index using the 'magic bitboards' approach + unsigned index(Bitboard occupied) const { + + if (HasPext) + return unsigned(pext(occupied, mask)); + + if (Is64Bit) + return unsigned(((occupied & mask) * magic) >> shift); + + unsigned lo = unsigned(occupied) & unsigned(mask); + unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); + return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; + } +}; + +extern Magic RookMagics[SQUARE_NB]; +extern Magic BishopMagics[SQUARE_NB]; /// Overloads of bitwise operators between a Bitboard and a Square for testing @@ -99,11 +126,10 @@ inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= SquareBB[s]; } -inline bool more_than_one(Bitboard b) { +constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } - /// rank_bb() and file_bb() return a bitboard representing all the squares on /// the given file or rank. @@ -126,8 +152,8 @@ inline Bitboard file_bb(Square s) { /// shift() moves a bitboard one step along direction D. Mainly for pawns -template -inline Bitboard shift(Bitboard b) { +template +constexpr Bitboard shift(Bitboard b) { return D == NORTH ? b << 8 : D == SOUTH ? b >> 8 : D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == NORTH_WEST ? (b & ~FileABB) << 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9 @@ -153,28 +179,28 @@ inline Bitboard between_bb(Square s1, Square s2) { } -/// in_front_bb() returns a bitboard representing all the squares on all the ranks +/// forward_ranks_bb() returns a bitboard representing all the squares on all the ranks /// in front of the given one, from the point of view of the given color. For -/// instance, in_front_bb(BLACK, RANK_3) will return the squares on ranks 1 and 2. +/// instance, forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. -inline Bitboard in_front_bb(Color c, Rank r) { - return InFrontBB[c][r]; +inline Bitboard forward_ranks_bb(Color c, Square s) { + return ForwardRanksBB[c][rank_of(s)]; } -/// forward_bb() returns a bitboard representing all the squares along the line +/// forward_file_bb() returns a bitboard representing all the squares along the line /// in front of the given one, from the point of view of the given color: -/// ForwardBB[c][s] = in_front_bb(c, s) & file_bb(s) +/// ForwardFileBB[c][s] = forward_ranks_bb(c, s) & file_bb(s) -inline Bitboard forward_bb(Color c, Square s) { - return ForwardBB[c][s]; +inline Bitboard forward_file_bb(Color c, Square s) { + return ForwardFileBB[c][s]; } /// pawn_attack_span() returns a bitboard representing all the squares that can be /// attacked by a pawn of the given color when it moves along its file, starting /// from the given square: -/// PawnAttackSpan[c][s] = in_front_bb(c, s) & adjacent_files_bb(s); +/// PawnAttackSpan[c][s] = forward_ranks_bb(c, s) & adjacent_files_bb(file_of(s)); inline Bitboard pawn_attack_span(Color c, Square s) { return PawnAttackSpan[c][s]; @@ -183,7 +209,7 @@ inline Bitboard pawn_attack_span(Color c, Square s) { /// passed_pawn_mask() returns a bitboard mask which can be used to test if a /// pawn of the given color and on the given square is a passed pawn: -/// PassedPawnMask[c][s] = pawn_attack_span(c, s) | forward_bb(c, s) +/// PassedPawnMask[c][s] = pawn_attack_span(c, s) | forward_file_bb(c, s) inline Bitboard passed_pawn_mask(Color c, Square s) { return PassedPawnMask[c][s]; @@ -210,50 +236,25 @@ template<> inline int distance(Square x, Square y) { return distance(rank_ /// attacks_bb() returns a bitboard representing all the squares attacked by a -/// piece of type Pt (bishop or rook) placed on 's'. The helper magic_index() -/// looks up the index using the 'magic bitboards' approach. -template -inline unsigned magic_index(Square s, Bitboard occupied) { - - extern Bitboard RookMasks[SQUARE_NB]; - extern Bitboard RookMagics[SQUARE_NB]; - extern unsigned RookShifts[SQUARE_NB]; - extern Bitboard BishopMasks[SQUARE_NB]; - extern Bitboard BishopMagics[SQUARE_NB]; - extern unsigned BishopShifts[SQUARE_NB]; - - Bitboard* const Masks = Pt == ROOK ? RookMasks : BishopMasks; - Bitboard* const Magics = Pt == ROOK ? RookMagics : BishopMagics; - unsigned* const Shifts = Pt == ROOK ? RookShifts : BishopShifts; - - if (HasPext) - return unsigned(pext(occupied, Masks[s])); - - if (Is64Bit) - return unsigned(((occupied & Masks[s]) * Magics[s]) >> Shifts[s]); - - unsigned lo = unsigned(occupied) & unsigned(Masks[s]); - unsigned hi = unsigned(occupied >> 32) & unsigned(Masks[s] >> 32); - return (lo * unsigned(Magics[s]) ^ hi * unsigned(Magics[s] >> 32)) >> Shifts[s]; -} +/// piece of type Pt (bishop or rook) placed on 's'. template inline Bitboard attacks_bb(Square s, Bitboard occupied) { - extern Bitboard* RookAttacks[SQUARE_NB]; - extern Bitboard* BishopAttacks[SQUARE_NB]; - - return (Pt == ROOK ? RookAttacks : BishopAttacks)[s][magic_index(s, occupied)]; + const Magic& m = Pt == ROOK ? RookMagics[s] : BishopMagics[s]; + return m.attacks[m.index(occupied)]; } -inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) { +inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { + + assert(pt != PAWN); - switch (type_of(pc)) + switch (pt) { case BISHOP: return attacks_bb(s, occupied); - case ROOK : return attacks_bb(s, occupied); + case ROOK : return attacks_bb< ROOK>(s, occupied); case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : return StepAttacksBB[pc][s]; + default : return PseudoAttacks[pt][s]; } } @@ -291,7 +292,7 @@ inline Square lsb(Bitboard b) { inline Square msb(Bitboard b) { assert(b); - return Square(63 - __builtin_clzll(b)); + return Square(63 ^ __builtin_clzll(b)); } #elif defined(_WIN64) && defined(_MSC_VER) diff --git a/Engines/Linux64/stockfish/src/endgame.cpp b/Engines/Linux64/stockfish/src/endgame.cpp index cbca34b..39db219 100644 --- a/Engines/Linux64/stockfish/src/endgame.cpp +++ b/Engines/Linux64/stockfish/src/endgame.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -83,26 +83,6 @@ namespace { return sq; } - // Get the material key of Position out of the given endgame key code - // like "KBPKN". The trick here is to first forge an ad-hoc FEN string - // and then let a Position object do the work for us. - Key key(const string& code, Color c) { - - assert(code.length() > 0 && code.length() < 8); - assert(code[0] == 'K'); - - string sides[] = { code.substr(code.find('K', 1)), // Weak - code.substr(0, code.find('K', 1)) }; // Strong - - std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); - - string fen = sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/" - + sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10"; - - StateInfo st; - return Position().set(fen, false, &st, nullptr).material_key(); - } - } // namespace @@ -130,13 +110,6 @@ Endgames::Endgames() { } -template -void Endgames::add(const string& code) { - map()[key(code, WHITE)] = std::unique_ptr>(new Endgame(WHITE)); - map()[key(code, BLACK)] = std::unique_ptr>(new Endgame(BLACK)); -} - - /// Mate with KX vs K. This function is used to evaluate positions with /// king and plenty of material vs a lone king. It simply gives the /// attacking side a bonus for driving the defending king towards the edge @@ -162,8 +135,8 @@ Value Endgame::operator()(const Position& pos) const { if ( pos.count(strongSide) || pos.count(strongSide) ||(pos.count(strongSide) && pos.count(strongSide)) - ||(pos.count(strongSide) > 1 && opposite_colors(pos.squares(strongSide)[0], - pos.squares(strongSide)[1]))) + || ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares) + && (pos.pieces(strongSide, BISHOP) & DarkSquares))) result = std::min(result + VALUE_KNOWN_WIN, VALUE_MATE_IN_MAX_PLY - 1); return strongSide == pos.side_to_move() ? result : -result; @@ -551,7 +524,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { Square bsq = pos.square(weakSide); Square psq = pos.square(strongSide); Rank rk = relative_rank(strongSide, psq); - Square push = pawn_push(strongSide); + Direction push = pawn_push(strongSide); // If the pawn is on the 5th rank and the pawn (currently) is on // the same color square as the bishop then there is a chance of @@ -625,7 +598,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // If all pawns are ahead of the king, on a single rook file and // the king is within one file of the pawns, it's a draw. - if ( !(pawns & ~in_front_bb(weakSide, rank_of(ksq))) + if ( !(pawns & ~forward_ranks_bb(weakSide, ksq)) && !((pawns & ~FileABB) && (pawns & ~FileHBB)) && distance(ksq, lsb(pawns)) <= 1) return SCALE_FACTOR_DRAW; @@ -671,17 +644,15 @@ ScaleFactor Endgame::operator()(const Position& pos) const { if (relative_rank(strongSide, pawnSq) <= RANK_5) return SCALE_FACTOR_DRAW; - else - { - Bitboard path = forward_bb(strongSide, pawnSq); - if (path & pos.pieces(weakSide, KING)) - return SCALE_FACTOR_DRAW; + Bitboard path = forward_file_bb(strongSide, pawnSq); - if ( (pos.attacks_from(weakBishopSq) & path) - && distance(weakBishopSq, pawnSq) >= 3) - return SCALE_FACTOR_DRAW; - } + if (path & pos.pieces(weakSide, KING)) + return SCALE_FACTOR_DRAW; + + if ( (pos.attacks_from(weakBishopSq) & path) + && distance(weakBishopSq, pawnSq) >= 3) + return SCALE_FACTOR_DRAW; } return SCALE_FACTOR_NONE; } @@ -809,7 +780,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // King needs to get close to promoting pawn to prevent knight from blocking. // Rules for this are very tricky, so just approximate. - if (forward_bb(strongSide, pawnSq) & pos.attacks_from(bishopSq)) + if (forward_file_bb(strongSide, pawnSq) & pos.attacks_from(bishopSq)) return ScaleFactor(distance(weakKingSq, pawnSq)); return SCALE_FACTOR_NONE; diff --git a/Engines/Linux64/stockfish/src/endgame.h b/Engines/Linux64/stockfish/src/endgame.h index 5f6b4bb..b5255a2 100644 --- a/Engines/Linux64/stockfish/src/endgame.h +++ b/Engines/Linux64/stockfish/src/endgame.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,12 +31,11 @@ #include "types.h" -/// EndgameType lists all supported endgames +/// EndgameCode lists all supported endgame functions by corresponding codes -enum EndgameType { - - // Evaluation functions +enum EndgameCode { + EVALUATION_FUNCTIONS, KNNK, // KNN vs K KXK, // Generic "mate lone king" eval KBNK, // KBN vs K @@ -47,10 +46,7 @@ enum EndgameType { KQKP, // KQ vs KP KQKR, // KQ vs KR - - // Scaling functions SCALING_FUNCTIONS, - KBPsK, // KB and pawns vs K KQKRPs, // KQ vs KR and pawns KRPKR, // KRP vs KR @@ -68,30 +64,28 @@ enum EndgameType { /// Endgame functions can be of two types depending on whether they return a /// Value or a ScaleFactor. -template using +template using eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type; -/// Base and derived templates for endgame evaluation and scaling functions +/// Base and derived functors for endgame evaluation and scaling functions template struct EndgameBase { + explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {} virtual ~EndgameBase() = default; - virtual Color strong_side() const = 0; virtual T operator()(const Position&) const = 0; + + const Color strongSide, weakSide; }; -template> +template> struct Endgame : public EndgameBase { - explicit Endgame(Color c) : strongSide(c), weakSide(~c) {} - Color strong_side() const { return strongSide; } - T operator()(const Position&) const; - -private: - Color strongSide, weakSide; + explicit Endgame(Color c) : EndgameBase(c) {} + T operator()(const Position&) const override; }; @@ -101,16 +95,22 @@ struct Endgame : public EndgameBase { class Endgames { - template using Map = std::map>>; - - template> - void add(const std::string& code); + template using Ptr = std::unique_ptr>; + template using Map = std::map>; template Map& map() { return std::get::value>(maps); } + template, typename P = Ptr> + void add(const std::string& code) { + + StateInfo st; + map()[Position().set(code, WHITE, &st).material_key()] = P(new Endgame(WHITE)); + map()[Position().set(code, BLACK, &st).material_key()] = P(new Endgame(BLACK)); + } + std::pair, Map> maps; public: diff --git a/Engines/Linux64/stockfish/src/evaluate.cpp b/Engines/Linux64/stockfish/src/evaluate.cpp index 434ebd6..8d9dd6e 100644 --- a/Engines/Linux64/stockfish/src/evaluate.cpp +++ b/Engines/Linux64/stockfish/src/evaluate.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,10 +31,21 @@ namespace { + const Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB); + const Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; + const Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; + const Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; + + const Bitboard KingFlank[FILE_NB] = { + QueenSide, QueenSide, QueenSide, CenterFiles, CenterFiles, KingSide, KingSide, KingSide + }; + namespace Trace { + enum Tracing {NO_TRACE, TRACE}; + enum Term { // The first 8 entries are for PieceType - MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL, TERM_NB + MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, INITIATIVE, TOTAL, TERM_NB }; double scores[TERM_NB][COLOR_NB][PHASE_NB]; @@ -52,7 +63,7 @@ namespace { std::ostream& operator<<(std::ostream& os, Term t) { - if (t == MATERIAL || t == IMBALANCE || t == Term(PAWN) || t == TOTAL) + if (t == MATERIAL || t == IMBALANCE || t == Term(PAWN) || t == INITIATIVE || t == TOTAL) os << " --- --- | --- --- | "; else os << std::setw(5) << scores[t][WHITE][MG] << " " @@ -69,12 +80,40 @@ namespace { using namespace Trace; - // Struct EvalInfo contains various information computed and collected + // Evaluation class contains various information computed and collected // by the evaluation functions. - struct EvalInfo { + template + class Evaluation { + + public: + Evaluation() = delete; + Evaluation(const Position& p) : pos(p) {} + Evaluation& operator=(const Evaluation&) = delete; + + Value value(); + + private: + // Evaluation helpers (used when calling value()) + template void initialize(); + template Score evaluate_king(); + template Score evaluate_threats(); + int king_distance(Color c, Square s); + template Score evaluate_passed_pawns(); + template Score evaluate_space(); + template Score evaluate_pieces(); + ScaleFactor evaluate_scale_factor(Value eg); + Score evaluate_initiative(Value eg); + + // Data members + const Position& pos; + Material::Entry* me; + Pawns::Entry* pe; + Bitboard mobilityArea[COLOR_NB]; + Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; // attackedBy[color][piece type] is a bitboard representing all squares - // attacked by a given color and piece type (can be also ALL_PIECES). + // attacked by a given color and piece type. Special "piece types" which are + // also calculated are QUEEN_DIAGONAL and ALL_PIECES. Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; // attackedBy2[color] are the squares attacked by 2 pieces of a given color, @@ -84,7 +123,7 @@ namespace { // kingRing[color] is the zone around the king which is considered // by the king safety evaluation. This consists of the squares directly - // adjacent to the king, and the three (or two, for a king on an edge file) + // adjacent to the king, and (only for a king on its first rank) the // squares two ranks in front of the king. For instance, if black's king // is on g8, kingRing[BLACK] is a bitboard containing the squares f8, h8, // f7, g7, h7, f6, g6 and h6. @@ -106,105 +145,93 @@ namespace { // a white knight on g5 and black's king is on g8, this white knight adds 2 // to kingAdjacentZoneAttacksCount[WHITE]. int kingAdjacentZoneAttacksCount[COLOR_NB]; - - Bitboard pinnedPieces[COLOR_NB]; - Material::Entry* me; - Pawns::Entry* pi; }; #define V(v) Value(v) #define S(mg, eg) make_score(mg, eg) - // MobilityBonus[PieceType][attacked] contains bonuses for middle and end - // game, indexed by piece type and number of attacked squares in the MobilityArea. + // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game, + // indexed by piece type and number of attacked squares in the mobility area. const Score MobilityBonus[][32] = { - {}, {}, - { S(-75,-76), S(-56,-54), S( -9,-26), S( -2,-10), S( 6, 5), S( 15, 11), // Knights - S( 22, 26), S( 30, 28), S( 36, 29) }, - { S(-48,-58), S(-21,-19), S( 16, -2), S( 26, 12), S( 37, 22), S( 51, 42), // Bishops - S( 54, 54), S( 63, 58), S( 65, 63), S( 71, 70), S( 79, 74), S( 81, 86), - S( 92, 90), S( 97, 94) }, - { S(-56,-78), S(-25,-18), S(-11, 26), S( -5, 55), S( -4, 70), S( -1, 81), // Rooks - S( 8,109), S( 14,120), S( 21,128), S( 23,143), S( 31,154), S( 32,160), - S( 43,165), S( 49,168), S( 59,169) }, - { S(-40,-35), S(-25,-12), S( 2, 7), S( 4, 19), S( 14, 37), S( 24, 55), // Queens - S( 25, 62), S( 40, 76), S( 43, 79), S( 47, 87), S( 54, 94), S( 56,102), - S( 60,111), S( 70,116), S( 72,118), S( 73,122), S( 75,128), S( 77,130), - S( 85,133), S( 94,136), S( 99,140), S(108,157), S(112,158), S(113,161), - S(118,174), S(119,177), S(123,191), S(128,199) } + { S(-75,-76), S(-57,-54), S( -9,-28), S( -2,-10), S( 6, 5), S( 14, 12), // Knights + S( 22, 26), S( 29, 29), S( 36, 29) }, + { S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishops + S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86), + S( 91, 88), S( 98, 97) }, + { S(-58,-76), S(-27,-18), S(-15, 28), S(-10, 55), S( -5, 69), S( -2, 82), // Rooks + S( 9,112), S( 16,118), S( 30,132), S( 29,142), S( 32,155), S( 38,165), + S( 46,166), S( 48,169), S( 58,171) }, + { S(-39,-36), S(-21,-15), S( 3, 8), S( 3, 18), S( 14, 34), S( 22, 54), // Queens + S( 28, 61), S( 41, 73), S( 43, 79), S( 48, 92), S( 56, 94), S( 60,104), + S( 60,113), S( 66,120), S( 67,123), S( 70,126), S( 71,133), S( 73,136), + S( 79,140), S( 88,143), S( 88,148), S( 99,166), S(102,170), S(102,175), + S(106,184), S(109,191), S(113,206), S(116,212) } }; - // Outpost[knight/bishop][supported by pawn] contains bonuses for knights and - // bishops outposts, bigger if outpost piece is supported by a pawn. + // Outpost[knight/bishop][supported by pawn] contains bonuses for minor + // pieces if they can reach an outpost square, bigger if that square is + // supported by a pawn. If the minor piece occupies an outpost square + // then score is doubled. const Score Outpost[][2] = { - { S(43,11), S(65,20) }, // Knights - { S(20, 3), S(29, 8) } // Bishops - }; - - // ReachableOutpost[knight/bishop][supported by pawn] contains bonuses for - // knights and bishops which can reach an outpost square in one move, bigger - // if outpost square is supported by a pawn. - const Score ReachableOutpost[][2] = { - { S(21, 5), S(35, 8) }, // Knights - { S( 8, 0), S(14, 4) } // Bishops + { S(22, 6), S(36,12) }, // Knight + { S( 9, 2), S(15, 5) } // Bishop }; // RookOnFile[semiopen/open] contains bonuses for each rook when there is no // friendly pawn on the rook file. - const Score RookOnFile[2] = { S(20, 7), S(45, 20) }; + const Score RookOnFile[] = { S(20, 7), S(45, 20) }; - // ThreatBySafePawn[PieceType] contains bonuses according to which piece - // type is attacked by a pawn which is protected or is not attacked. - const Score ThreatBySafePawn[PIECE_TYPE_NB] = { - S(0, 0), S(0, 0), S(176, 139), S(131, 127), S(217, 218), S(203, 215) + // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to + // which piece type attacks which one. Attacks on lesser pieces which are + // pawn-defended are not considered. + const Score ThreatByMinor[PIECE_TYPE_NB] = { + S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72, 107), S(48, 118) }; - // Threat[by minor/by rook][attacked PieceType] contains - // bonuses according to which piece type attacks which one. - // Attacks on lesser pieces which are pawn-defended are not considered. - const Score Threat[][PIECE_TYPE_NB] = { - { S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72,107), S(48,118) }, // by Minor - { S(0, 0), S(0, 25), S(40, 62), S(40, 59), S( 0, 34), S(35, 48) } // by Rook + const Score ThreatByRook[PIECE_TYPE_NB] = { + S(0, 0), S(0, 25), S(40, 62), S(40, 59), S(0, 34), S(35, 48) }; - // ThreatByKing[on one/on many] contains bonuses for King attacks on + // ThreatByKing[on one/on many] contains bonuses for king attacks on // pawns or pieces which are not pawn-defended. - const Score ThreatByKing[2] = { S(3, 62), S(9, 138) }; + const Score ThreatByKing[] = { S(3, 62), S(9, 138) }; // Passed[mg/eg][Rank] contains midgame and endgame bonuses for passed pawns. // We don't use a Score because we process the two components independently. const Value Passed[][RANK_NB] = { - { V(5), V( 5), V(31), V(73), V(166), V(252) }, - { V(7), V(14), V(38), V(73), V(166), V(252) } + { V(0), V(5), V( 5), V(31), V(73), V(166), V(252) }, + { V(0), V(7), V(14), V(38), V(73), V(166), V(252) } }; // PassedFile[File] contains a bonus according to the file of a passed pawn const Score PassedFile[FILE_NB] = { S( 9, 10), S( 2, 10), S( 1, -8), S(-20,-12), - S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10) + S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10) }; + // Rank factor applied on some bonus for passed pawn on rank 4 or beyond + const int RankFactor[RANK_NB] = {0, 0, 0, 2, 6, 11, 16}; + + // KingProtector[PieceType-2] contains a bonus according to distance from king + const Score KingProtector[] = { S(-3, -5), S(-4, -3), S(-3, 0), S(-1, 1) }; + // Assorted bonuses and penalties used by evaluation - const Score MinorBehindPawn = S(16, 0); - const Score BishopPawns = S( 8, 12); - const Score RookOnPawn = S( 8, 24); - const Score TrappedRook = S(92, 0); - const Score CloseEnemies = S( 7, 0); - const Score SafeCheck = S(20, 20); - const Score OtherCheck = S(10, 10); - const Score ThreatByHangingPawn = S(71, 61); - const Score LooseEnemies = S( 0, 25); - const Score WeakQueen = S(35, 0); - const Score Hanging = S(48, 27); - const Score ThreatByPawnPush = S(38, 22); - const Score Unstoppable = S( 0, 20); - const Score PawnlessFlank = S(20, 80); - const Score HinderPassedPawn = S( 7, 0); - - // Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by - // a friendly pawn on b2/g2 (b7/g7 for black). This can obviously only - // happen in Chess960 games. - const Score TrappedBishopA1H1 = S(50, 50); + const Score MinorBehindPawn = S( 16, 0); + const Score BishopPawns = S( 8, 12); + const Score LongRangedBishop = S( 22, 0); + const Score RookOnPawn = S( 8, 24); + const Score TrappedRook = S( 92, 0); + const Score WeakQueen = S( 50, 10); + const Score CloseEnemies = S( 7, 0); + const Score PawnlessFlank = S( 20, 80); + const Score ThreatBySafePawn = S(192,175); + const Score ThreatByRank = S( 16, 3); + const Score Hanging = S( 48, 27); + const Score WeakUnopposedPawn = S( 5, 25); + const Score ThreatByPawnPush = S( 38, 22); + const Score ThreatByAttackOnQueen = S( 38, 22); + const Score HinderPassedPawn = S( 7, 0); + const Score TrappedBishopA1H1 = S( 50, 50); #undef S #undef V @@ -213,99 +240,117 @@ namespace { const int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 78, 56, 45, 11 }; // Penalties for enemy's safe checks - const int QueenContactCheck = 997; - const int QueenCheck = 695; - const int RookCheck = 638; - const int BishopCheck = 538; - const int KnightCheck = 874; + const int QueenSafeCheck = 780; + const int RookSafeCheck = 880; + const int BishopSafeCheck = 435; + const int KnightSafeCheck = 790; + + // Threshold for lazy and space evaluation + const Value LazyThreshold = Value(1500); + const Value SpaceThreshold = Value(12222); + + + // initialize() computes king and pawn attacks, and the king ring bitboard + // for a given color. This is done at the beginning of the evaluation. + template template + void Evaluation::initialize() { - // eval_init() initializes king and attack bitboards for a given color - // adding pawn attacks. To be done at the beginning of the evaluation. + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Down = (Us == WHITE ? SOUTH : NORTH); + const Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB: Rank7BB | Rank6BB); - template - void eval_init(const Position& pos, EvalInfo& ei) { + // Find our pawns on the first two ranks, and those which are blocked + Bitboard b = pos.pieces(Us, PAWN) & (shift(pos.pieces()) | LowRanks); - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Down = (Us == WHITE ? SOUTH : NORTH); + // Squares occupied by those pawns, by our king, or controlled by enemy pawns + // are excluded from the mobility area. + mobilityArea[Us] = ~(b | pos.square(Us) | pe->pawn_attacks(Them)); - ei.pinnedPieces[Us] = pos.pinned_pieces(Us); - Bitboard b = ei.attackedBy[Them][KING]; - ei.attackedBy[Them][ALL_PIECES] |= b; - ei.attackedBy[Us][ALL_PIECES] |= ei.attackedBy[Us][PAWN] = ei.pi->pawn_attacks(Us); - ei.attackedBy2[Us] = ei.attackedBy[Us][PAWN] & ei.attackedBy[Us][KING]; + // Initialise the attack bitboards with the king and pawn information + b = attackedBy[Us][KING] = pos.attacks_from(pos.square(Us)); + attackedBy[Us][PAWN] = pe->pawn_attacks(Us); - // Init king safety tables only if we are going to use them - if (pos.non_pawn_material(Us) >= QueenValueMg) + attackedBy2[Us] = b & attackedBy[Us][PAWN]; + attackedBy[Us][ALL_PIECES] = b | attackedBy[Us][PAWN]; + + // Init our king safety tables only if we are going to use them + if (pos.non_pawn_material(Them) >= RookValueMg + KnightValueMg) { - ei.kingRing[Them] = b | shift(b); - b &= ei.attackedBy[Us][PAWN]; - ei.kingAttackersCount[Us] = popcount(b); - ei.kingAdjacentZoneAttacksCount[Us] = ei.kingAttackersWeight[Us] = 0; + kingRing[Us] = b; + if (relative_rank(Us, pos.square(Us)) == RANK_1) + kingRing[Us] |= shift(b); + + kingAttackersCount[Them] = popcount(b & pe->pawn_attacks(Them)); + kingAdjacentZoneAttacksCount[Them] = kingAttackersWeight[Them] = 0; } else - ei.kingRing[Them] = ei.kingAttackersCount[Us] = 0; + kingRing[Us] = kingAttackersCount[Them] = 0; } // evaluate_pieces() assigns bonuses and penalties to the pieces of a given // color and type. - template - Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score* mobility, - const Bitboard* mobilityArea) { - Bitboard b, bb; - Square s; - Score score = SCORE_ZERO; + template template + Score Evaluation::evaluate_pieces() { - const PieceType NextPt = (Us == WHITE ? Pt : PieceType(Pt + 1)); const Color Them = (Us == WHITE ? BLACK : WHITE); const Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB : Rank5BB | Rank4BB | Rank3BB); const Square* pl = pos.squares(Us); - ei.attackedBy[Us][Pt] = 0; + Bitboard b, bb; + Square s; + Score score = SCORE_ZERO; + + attackedBy[Us][Pt] = 0; + + if (Pt == QUEEN) + attackedBy[Us][QUEEN_DIAGONAL] = 0; while ((s = *pl++) != SQ_NONE) { // Find attacked squares, including x-ray attacks for bishops and rooks - b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(Us, QUEEN)) - : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(Us, ROOK, QUEEN)) + b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) + : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) : pos.attacks_from(s); - if (ei.pinnedPieces[Us] & s) + if (pos.pinned_pieces(Us) & s) b &= LineBB[pos.square(Us)][s]; - ei.attackedBy2[Us] |= ei.attackedBy[Us][ALL_PIECES] & b; - ei.attackedBy[Us][ALL_PIECES] |= ei.attackedBy[Us][Pt] |= b; + attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b; + attackedBy[Us][ALL_PIECES] |= attackedBy[Us][Pt] |= b; - if (b & ei.kingRing[Them]) + if (Pt == QUEEN) + attackedBy[Us][QUEEN_DIAGONAL] |= b & PseudoAttacks[BISHOP][s]; + + if (b & kingRing[Them]) { - ei.kingAttackersCount[Us]++; - ei.kingAttackersWeight[Us] += KingAttackWeights[Pt]; - ei.kingAdjacentZoneAttacksCount[Us] += popcount(b & ei.attackedBy[Them][KING]); + kingAttackersCount[Us]++; + kingAttackersWeight[Us] += KingAttackWeights[Pt]; + kingAdjacentZoneAttacksCount[Us] += popcount(b & attackedBy[Them][KING]); } - if (Pt == QUEEN) - b &= ~( ei.attackedBy[Them][KNIGHT] - | ei.attackedBy[Them][BISHOP] - | ei.attackedBy[Them][ROOK]); - int mob = popcount(b & mobilityArea[Us]); - mobility[Us] += MobilityBonus[Pt][mob]; + mobility[Us] += MobilityBonus[Pt - 2][mob]; + + // Bonus for this piece as a king protector + score += KingProtector[Pt - 2] * distance(s, pos.square(Us)); if (Pt == BISHOP || Pt == KNIGHT) { // Bonus for outpost squares - bb = OutpostRanks & ~ei.pi->pawn_attacks_span(Them); + bb = OutpostRanks & ~pe->pawn_attacks_span(Them); if (bb & s) - score += Outpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & s)]; + score += Outpost[Pt == BISHOP][bool(attackedBy[Us][PAWN] & s)] * 2; else { bb &= b & ~pos.pieces(Us); if (bb) - score += ReachableOutpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & bb)]; + score += Outpost[Pt == BISHOP][bool(attackedBy[Us][PAWN] & bb)]; } // Bonus when behind a pawn @@ -313,9 +358,15 @@ namespace { && (pos.pieces(PAWN) & (s + pawn_push(Us)))) score += MinorBehindPawn; - // Penalty for pawns on the same color square as the bishop if (Pt == BISHOP) - score -= BishopPawns * ei.pi->pawns_on_same_color_squares(Us, s); + { + // Penalty for pawns on the same color square as the bishop + score -= BishopPawns * pe->pawns_on_same_color_squares(Us, s); + + // Bonus for bishop on a long diagonal which can "see" both center squares + if (more_than_one(Center & (attacks_bb(s, pos.pieces(PAWN)) | s))) + score += LongRangedBishop; + } // An important Chess960 pattern: A cornered bishop blocked by a friendly // pawn diagonally in front of it is a very serious problem, especially @@ -324,7 +375,7 @@ namespace { && pos.is_chess960() && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1))) { - Square d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); + Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); if (pos.piece_on(s + d) == make_piece(Us, PAWN)) score -= !pos.empty(s + d + pawn_push(Us)) ? TrappedBishopA1H1 * 4 : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? TrappedBishopA1H1 * 2 @@ -339,17 +390,16 @@ namespace { score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]); // Bonus when on an open or semi-open file - if (ei.pi->semiopen_file(Us, file_of(s))) - score += RookOnFile[!!ei.pi->semiopen_file(Them, file_of(s))]; + if (pe->semiopen_file(Us, file_of(s))) + score += RookOnFile[bool(pe->semiopen_file(Them, file_of(s)))]; - // Penalize when trapped by the king, even more if the king cannot castle + // Penalty when trapped by the king, even more if the king cannot castle else if (mob <= 3) { Square ksq = pos.square(Us); if ( ((file_of(ksq) < FILE_E) == (file_of(s) < file_of(ksq))) - && (rank_of(ksq) == rank_of(s) || relative_rank(Us, ksq) == RANK_1) - && !ei.pi->semiopen_side(Us, file_of(ksq), file_of(s) < file_of(ksq))) + && !pe->semiopen_side(Us, file_of(ksq), file_of(s) < file_of(ksq))) score -= (TrappedRook - make_score(mob * 22, 0)) * (1 + !pos.can_castle(Us)); } } @@ -363,129 +413,95 @@ namespace { } } - if (DoTrace) + if (T) Trace::add(Pt, Us, score); - // Recursively call evaluate_pieces() of next piece type until KING is excluded - return score - evaluate_pieces(pos, ei, mobility, mobilityArea); + return score; } - template<> - Score evaluate_pieces(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; } - template<> - Score evaluate_pieces< true, WHITE, KING>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; } - // evaluate_king() assigns bonuses and penalties to a king of a given color - const Bitboard WhiteCamp = Rank1BB | Rank2BB | Rank3BB | Rank4BB | Rank5BB; - const Bitboard BlackCamp = Rank8BB | Rank7BB | Rank6BB | Rank5BB | Rank4BB; - const Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; - const Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; - const Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; - - const Bitboard KingFlank[COLOR_NB][FILE_NB] = { - { QueenSide & WhiteCamp, QueenSide & WhiteCamp, QueenSide & WhiteCamp, CenterFiles & WhiteCamp, - CenterFiles & WhiteCamp, KingSide & WhiteCamp, KingSide & WhiteCamp, KingSide & WhiteCamp }, - { QueenSide & BlackCamp, QueenSide & BlackCamp, QueenSide & BlackCamp, CenterFiles & BlackCamp, - CenterFiles & BlackCamp, KingSide & BlackCamp, KingSide & BlackCamp, KingSide & BlackCamp }, - }; - - template - Score evaluate_king(const Position& pos, const EvalInfo& ei) { + template template + Score Evaluation::evaluate_king() { - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB + : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB); - Bitboard undefended, b, b1, b2, safe, other; - int kingDanger; const Square ksq = pos.square(Us); + Bitboard weak, b, b1, b2, safe, unsafeChecks; // King shelter and enemy pawns storm - Score score = ei.pi->king_safety(pos, ksq); + Score score = pe->king_safety(pos, ksq); // Main king safety evaluation - if (ei.kingAttackersCount[Them]) + if (kingAttackersCount[Them] > (1 - pos.count(Them))) { - // Find the attacked squares which are defended only by the king... - undefended = ei.attackedBy[Them][ALL_PIECES] - & ei.attackedBy[Us][KING] - & ~ei.attackedBy2[Us]; - - // ... and those which are not defended at all in the larger king ring - b = ei.attackedBy[Them][ALL_PIECES] & ~ei.attackedBy[Us][ALL_PIECES] - & ei.kingRing[Us] & ~pos.pieces(Them); - - // Initialize the 'kingDanger' variable, which will be transformed - // later into a king danger score. The initial value is based on the - // number and types of the enemy's attacking pieces, the number of - // attacked and undefended squares around our king and the quality of - // the pawn shelter (current 'score' value). - kingDanger = std::min(807, ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them]) - + 101 * ei.kingAdjacentZoneAttacksCount[Them] - + 235 * popcount(undefended) - + 134 * (popcount(b) + !!ei.pinnedPieces[Us]) - - 717 * !pos.count(Them) - - 7 * mg_value(score) / 5 - 5; - - // Analyse the enemy's safe queen contact checks. Firstly, find the - // undefended squares around the king reachable by the enemy queen... - b = undefended & ei.attackedBy[Them][QUEEN] & ~pos.pieces(Them); - - // ...and keep squares supported by another enemy piece - kingDanger += QueenContactCheck * popcount(b & ei.attackedBy2[Them]); - - // Analyse the safe enemy's checks which are possible on next move... - safe = ~(ei.attackedBy[Us][ALL_PIECES] | pos.pieces(Them)); - - // ... and some other potential checks, only requiring the square to be - // safe from pawn-attacks, and not being occupied by a blocked pawn. - other = ~( ei.attackedBy[Us][PAWN] - | (pos.pieces(Them, PAWN) & shift(pos.pieces(PAWN)))); - - b1 = pos.attacks_from(ksq); - b2 = pos.attacks_from(ksq); - - // Enemy queen safe checks - if ((b1 | b2) & ei.attackedBy[Them][QUEEN] & safe) - kingDanger += QueenCheck, score -= SafeCheck; + // Attacked squares defended at most once by our queen or king + weak = attackedBy[Them][ALL_PIECES] + & ~attackedBy2[Us] + & (attackedBy[Us][KING] | attackedBy[Us][QUEEN] | ~attackedBy[Us][ALL_PIECES]); - // For other pieces, also consider the square safe if attacked twice, - // and only defended by a queen. - safe |= ei.attackedBy2[Them] - & ~(ei.attackedBy2[Us] | pos.pieces(Them)) - & ei.attackedBy[Us][QUEEN]; + int kingDanger = unsafeChecks = 0; - // Enemy rooks safe and other checks - if (b1 & ei.attackedBy[Them][ROOK] & safe) - kingDanger += RookCheck, score -= SafeCheck; + // Analyse the safe enemy's checks which are possible on next move + safe = ~pos.pieces(Them); + safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]); - else if (b1 & ei.attackedBy[Them][ROOK] & other) - score -= OtherCheck; + b1 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); + b2 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); - // Enemy bishops safe and other checks - if (b2 & ei.attackedBy[Them][BISHOP] & safe) - kingDanger += BishopCheck, score -= SafeCheck; - - else if (b2 & ei.attackedBy[Them][BISHOP] & other) - score -= OtherCheck; - - // Enemy knights safe and other checks - b = pos.attacks_from(ksq) & ei.attackedBy[Them][KNIGHT]; + // Enemy queen safe checks + if ((b1 | b2) & attackedBy[Them][QUEEN] & safe & ~attackedBy[Us][QUEEN]) + kingDanger += QueenSafeCheck; + + b1 &= attackedBy[Them][ROOK]; + b2 &= attackedBy[Them][BISHOP]; + + // Enemy rooks checks + if (b1 & safe) + kingDanger += RookSafeCheck; + else + unsafeChecks |= b1; + + // Enemy bishops checks + if (b2 & safe) + kingDanger += BishopSafeCheck; + else + unsafeChecks |= b2; + + // Enemy knights checks + b = pos.attacks_from(ksq) & attackedBy[Them][KNIGHT]; if (b & safe) - kingDanger += KnightCheck, score -= SafeCheck; - - else if (b & other) - score -= OtherCheck; - - // Compute the king danger score and subtract it from the evaluation + kingDanger += KnightSafeCheck; + else + unsafeChecks |= b; + + // Unsafe or occupied checking squares will also be considered, as long as + // the square is in the attacker's mobility area. + unsafeChecks &= mobilityArea[Them]; + + kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] + + 102 * kingAdjacentZoneAttacksCount[Them] + + 191 * popcount(kingRing[Us] & weak) + + 143 * popcount(pos.pinned_pieces(Us) | unsafeChecks) + - 848 * !pos.count(Them) + - 9 * mg_value(score) / 8 + + 40; + + // Transform the kingDanger units into a Score, and subtract it from the evaluation if (kingDanger > 0) - score -= make_score(std::min(kingDanger * kingDanger / 4096, 2 * int(BishopValueMg)), 0); + { + int mobilityDanger = mg_value(mobility[Them] - mobility[Us]); + kingDanger = std::max(0, kingDanger + mobilityDanger); + score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16); + } } // King tropism: firstly, find squares that opponent attacks in our king flank File kf = file_of(ksq); - b = ei.attackedBy[Them][ALL_PIECES] & KingFlank[Us][kf]; + b = attackedBy[Them][ALL_PIECES] & KingFlank[kf] & Camp; assert(((Us == WHITE ? b << 4 : b >> 4) & b) == 0); assert(popcount(Us == WHITE ? b << 4 : b >> 4) == popcount(b)); @@ -493,15 +509,15 @@ namespace { // Secondly, add the squares which are attacked twice in that flank and // which are not defended by our pawns. b = (Us == WHITE ? b << 4 : b >> 4) - | (b & ei.attackedBy2[Them] & ~ei.attackedBy[Us][PAWN]); + | (b & attackedBy2[Them] & ~attackedBy[Us][PAWN]); score -= CloseEnemies * popcount(b); // Penalty when our king is on a pawnless flank - if (!(pos.pieces(PAWN) & (KingFlank[WHITE][kf] | KingFlank[BLACK][kf]))) + if (!(pos.pieces(PAWN) & KingFlank[kf])) score -= PawnlessFlank; - if (DoTrace) + if (T) Trace::add(KING, Us, score); return score; @@ -511,128 +527,149 @@ namespace { // evaluate_threats() assigns bonuses according to the types of the attacking // and the attacked pieces. - template - Score evaluate_threats(const Position& pos, const EvalInfo& ei) { - - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); - const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); - const Bitboard TRank2BB = (Us == WHITE ? Rank2BB : Rank7BB); - const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + template template + Score Evaluation::evaluate_threats() { - enum { Minor, Rook }; + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + const Direction Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); - Bitboard b, weak, defended, safeThreats; + Bitboard b, weak, defended, stronglyProtected, safeThreats; Score score = SCORE_ZERO; - // Small bonus if the opponent has loose pawns or pieces - if ( (pos.pieces(Them) ^ pos.pieces(Them, QUEEN, KING)) - & ~(ei.attackedBy[Us][ALL_PIECES] | ei.attackedBy[Them][ALL_PIECES])) - score += LooseEnemies; - // Non-pawn enemies attacked by a pawn - weak = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Us][PAWN]; + weak = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & attackedBy[Us][PAWN]; if (weak) { - b = pos.pieces(Us, PAWN) & ( ~ei.attackedBy[Them][ALL_PIECES] - | ei.attackedBy[Us][ALL_PIECES]); + b = pos.pieces(Us, PAWN) & ( ~attackedBy[Them][ALL_PIECES] + | attackedBy[Us][ALL_PIECES]); safeThreats = (shift(b) | shift(b)) & weak; - if (weak ^ safeThreats) - score += ThreatByHangingPawn; - - while (safeThreats) - score += ThreatBySafePawn[type_of(pos.piece_on(pop_lsb(&safeThreats)))]; + score += ThreatBySafePawn * popcount(safeThreats); } - // Non-pawn enemies defended by a pawn - defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Them][PAWN]; + // Squares strongly protected by the opponent, either because they attack the + // square with a pawn, or because they attack the square twice and we don't. + stronglyProtected = attackedBy[Them][PAWN] + | (attackedBy2[Them] & ~attackedBy2[Us]); + + // Non-pawn enemies, strongly protected + defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) + & stronglyProtected; - // Enemies not defended by a pawn and under our attack + // Enemies not strongly protected and under our attack weak = pos.pieces(Them) - & ~ei.attackedBy[Them][PAWN] - & ei.attackedBy[Us][ALL_PIECES]; + & ~stronglyProtected + & attackedBy[Us][ALL_PIECES]; // Add a bonus according to the kind of attacking pieces if (defended | weak) { - b = (defended | weak) & (ei.attackedBy[Us][KNIGHT] | ei.attackedBy[Us][BISHOP]); + b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); while (b) - score += Threat[Minor][type_of(pos.piece_on(pop_lsb(&b)))]; + { + Square s = pop_lsb(&b); + score += ThreatByMinor[type_of(pos.piece_on(s))]; + if (type_of(pos.piece_on(s)) != PAWN) + score += ThreatByRank * (int)relative_rank(Them, s); + } - b = (pos.pieces(Them, QUEEN) | weak) & ei.attackedBy[Us][ROOK]; + b = (pos.pieces(Them, QUEEN) | weak) & attackedBy[Us][ROOK]; while (b) - score += Threat[Rook ][type_of(pos.piece_on(pop_lsb(&b)))]; + { + Square s = pop_lsb(&b); + score += ThreatByRook[type_of(pos.piece_on(s))]; + if (type_of(pos.piece_on(s)) != PAWN) + score += ThreatByRank * (int)relative_rank(Them, s); + } - score += Hanging * popcount(weak & ~ei.attackedBy[Them][ALL_PIECES]); + score += Hanging * popcount(weak & ~attackedBy[Them][ALL_PIECES]); - b = weak & ei.attackedBy[Us][KING]; + b = weak & attackedBy[Us][KING]; if (b) score += ThreatByKing[more_than_one(b)]; } - // Bonus if some pawns can safely push and attack an enemy piece - b = pos.pieces(Us, PAWN) & ~TRank7BB; - b = shift(b | (shift(b & TRank2BB) & ~pos.pieces())); + // Bonus for opponent unopposed weak pawns + if (pos.pieces(Us, ROOK, QUEEN)) + score += WeakUnopposedPawn * pe->weak_unopposed(Them); + + // Find squares where our pawns can push on the next move + b = shift(pos.pieces(Us, PAWN)) & ~pos.pieces(); + b |= shift(b & TRank3BB) & ~pos.pieces(); - b &= ~pos.pieces() - & ~ei.attackedBy[Them][PAWN] - & (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]); + // Keep only the squares which are not completely unsafe + b &= ~attackedBy[Them][PAWN] + & (attackedBy[Us][ALL_PIECES] | ~attackedBy[Them][ALL_PIECES]); + // Add a bonus for each new pawn threats from those squares b = (shift(b) | shift(b)) & pos.pieces(Them) - & ~ei.attackedBy[Us][PAWN]; + & ~attackedBy[Us][PAWN]; score += ThreatByPawnPush * popcount(b); - if (DoTrace) + // Add a bonus for safe slider attack threats on opponent queen + safeThreats = ~pos.pieces(Us) & ~attackedBy2[Them] & attackedBy2[Us]; + b = (attackedBy[Us][BISHOP] & attackedBy[Them][QUEEN_DIAGONAL]) + | (attackedBy[Us][ROOK ] & attackedBy[Them][QUEEN] & ~attackedBy[Them][QUEEN_DIAGONAL]); + + score += ThreatByAttackOnQueen * popcount(b & safeThreats); + + if (T) Trace::add(THREAT, Us, score); return score; } + // helper used by evaluate_passed_pawns to cap the distance + template + int Evaluation::king_distance(Color c, Square s) { + return std::min(distance(pos.square(c), s), 5); + } - // evaluate_passed_pawns() evaluates the passed pawns of the given color + // evaluate_passed_pawns() evaluates the passed pawns and candidate passed + // pawns of the given color. - template - Score evaluate_passed_pawns(const Position& pos, const EvalInfo& ei) { + template template + Score Evaluation::evaluate_passed_pawns() { - const Color Them = (Us == WHITE ? BLACK : WHITE); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); Bitboard b, bb, squaresToQueen, defendedSquares, unsafeSquares; Score score = SCORE_ZERO; - b = ei.pi->passed_pawns(Us); + b = pe->passed_pawns(Us); while (b) { Square s = pop_lsb(&b); - assert(pos.pawn_passed(Us, s)); - assert(!(pos.pieces(PAWN) & forward_bb(Us, s))); + assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); - bb = forward_bb(Us, s) & (ei.attackedBy[Them][ALL_PIECES] | pos.pieces(Them)); + bb = forward_file_bb(Us, s) & (attackedBy[Them][ALL_PIECES] | pos.pieces(Them)); score -= HinderPassedPawn * popcount(bb); - int r = relative_rank(Us, s) - RANK_2; - int rr = r * (r - 1); + int r = relative_rank(Us, s); + int rr = RankFactor[r]; Value mbonus = Passed[MG][r], ebonus = Passed[EG][r]; if (rr) { - Square blockSq = s + pawn_push(Us); + Square blockSq = s + Up; // Adjust bonus based on the king's proximity - ebonus += distance(pos.square(Them), blockSq) * 5 * rr - - distance(pos.square(Us ), blockSq) * 2 * rr; + ebonus += (king_distance(Them, blockSq) * 5 - king_distance(Us, blockSq) * 2) * rr; // If blockSq is not the queening square then consider also a second push - if (relative_rank(Us, blockSq) != RANK_8) - ebonus -= distance(pos.square(Us), blockSq + pawn_push(Us)) * rr; + if (r != RANK_7) + ebonus -= king_distance(Us, blockSq + Up) * rr; // If the pawn is free to advance, then increase the bonus if (pos.empty(blockSq)) @@ -640,15 +677,15 @@ namespace { // If there is a rook or queen attacking/defending the pawn from behind, // consider all the squaresToQueen. Otherwise consider only the squares // in the pawn's path attacked or occupied by the enemy. - defendedSquares = unsafeSquares = squaresToQueen = forward_bb(Us, s); + defendedSquares = unsafeSquares = squaresToQueen = forward_file_bb(Us, s); - bb = forward_bb(Them, s) & pos.pieces(ROOK, QUEEN) & pos.attacks_from(s); + bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN) & pos.attacks_from(s); if (!(pos.pieces(Us) & bb)) - defendedSquares &= ei.attackedBy[Us][ALL_PIECES]; + defendedSquares &= attackedBy[Us][ALL_PIECES]; if (!(pos.pieces(Them) & bb)) - unsafeSquares &= ei.attackedBy[Them][ALL_PIECES] | pos.pieces(Them); + unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); // If there aren't any enemy attacks, assign a big bonus. Otherwise // assign a smaller bonus if the block square isn't attacked. @@ -668,13 +705,17 @@ namespace { mbonus += rr + r * 2, ebonus += rr + r * 2; } // rr != 0 + // Scale down bonus for candidate passers which need more than one + // pawn push to become passed or have a pawn in front of them. + if (!pos.pawn_passed(Us, s + Up) || (pos.pieces(PAWN) & forward_file_bb(Us, s))) + mbonus /= 2, ebonus /= 2; + score += make_score(mbonus, ebonus) + PassedFile[file_of(s)]; } - if (DoTrace) + if (T) Trace::add(PASSED, Us, score); - // Add the scores to the middlegame and endgame eval return score; } @@ -685,21 +726,22 @@ namespace { // squares one, two or three squares behind a friendly pawn are counted // twice. Finally, the space bonus is multiplied by a weight. The aim is to // improve play on game opening. - template - Score evaluate_space(const Position& pos, const EvalInfo& ei) { + + template template + Score Evaluation::evaluate_space() { const Color Them = (Us == WHITE ? BLACK : WHITE); const Bitboard SpaceMask = - Us == WHITE ? (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank2BB | Rank3BB | Rank4BB) - : (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank7BB | Rank6BB | Rank5BB); + Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB) + : CenterFiles & (Rank7BB | Rank6BB | Rank5BB); // Find the safe squares for our pieces inside the area defined by // SpaceMask. A square is unsafe if it is attacked by an enemy // pawn, or if it is undefended and attacked by an enemy piece. Bitboard safe = SpaceMask & ~pos.pieces(Us, PAWN) - & ~ei.attackedBy[Them][PAWN] - & (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]); + & ~attackedBy[Them][PAWN] + & (attackedBy[Us][ALL_PIECES] | ~attackedBy[Them][ALL_PIECES]); // Find all squares which are at most three squares behind some friendly pawn Bitboard behind = pos.pieces(Us, PAWN); @@ -709,46 +751,51 @@ namespace { // Since SpaceMask[Us] is fully on our half of the board... assert(unsigned(safe >> (Us == WHITE ? 32 : 0)) == 0); - // ...count safe + (behind & safe) with a single popcount + // ...count safe + (behind & safe) with a single popcount. int bonus = popcount((Us == WHITE ? safe << 32 : safe >> 32) | (behind & safe)); - bonus = std::min(16, bonus); - int weight = pos.count(Us) - 2 * ei.pi->open_files(); + int weight = pos.count(Us) - 2 * pe->open_files(); - return make_score(bonus * weight * weight / 18, 0); + return make_score(bonus * weight * weight / 16, 0); } // evaluate_initiative() computes the initiative correction value for the // position, i.e., second order bonus/malus based on the known attacking/defending // status of the players. - Score evaluate_initiative(const Position& pos, int asymmetry, Value eg) { + + template + Score Evaluation::evaluate_initiative(Value eg) { int kingDistance = distance(pos.square(WHITE), pos.square(BLACK)) - distance(pos.square(WHITE), pos.square(BLACK)); - int pawns = pos.count(WHITE) + pos.count(BLACK); + bool bothFlanks = (pos.pieces(PAWN) & QueenSide) && (pos.pieces(PAWN) & KingSide); // Compute the initiative bonus for the attacking side - int initiative = 8 * (asymmetry + kingDistance - 15) + 12 * pawns; + int initiative = 8 * (pe->pawn_asymmetry() + kingDistance - 17) + 12 * pos.count() + 16 * bothFlanks; // Now apply the bonus: note that we find the attacking side by extracting // the sign of the endgame value, and that we carefully cap the bonus so - // that the endgame score will never be divided by more than two. - int value = ((eg > 0) - (eg < 0)) * std::max(initiative, -abs(eg / 2)); + // that the endgame score will never change sign after the bonus. + int v = ((eg > 0) - (eg < 0)) * std::max(initiative, -abs(eg)); + + if (T) + Trace::add(INITIATIVE, make_score(0, v)); - return make_score(0, value); + return make_score(0, v); } // evaluate_scale_factor() computes the scale factor for the winning side - ScaleFactor evaluate_scale_factor(const Position& pos, const EvalInfo& ei, Value eg) { + + template + ScaleFactor Evaluation::evaluate_scale_factor(Value eg) { Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; - ScaleFactor sf = ei.me->scale_factor(pos, strongSide); + ScaleFactor sf = me->scale_factor(pos, strongSide); // If we don't already have an unusual scale factor, check for certain // types of endgames, and use a lower scale for those. - if ( ei.me->game_phase() < PHASE_MIDGAME - && (sf == SCALE_FACTOR_NORMAL || sf == SCALE_FACTOR_ONEPAWN)) + if (sf == SCALE_FACTOR_NORMAL || sf == SCALE_FACTOR_ONEPAWN) { if (pos.opposite_bishops()) { @@ -756,139 +803,116 @@ namespace { // is almost a draw, in case of KBP vs KB, it is even more a draw. if ( pos.non_pawn_material(WHITE) == BishopValueMg && pos.non_pawn_material(BLACK) == BishopValueMg) - sf = more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9); + return more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9); // Endgame with opposite-colored bishops, but also other pieces. Still // a bit drawish, but not as drawish as with only the two bishops. - else - sf = ScaleFactor(46); + return ScaleFactor(46); } // Endings where weaker side can place his king in front of the opponent's // pawns are drawish. else if ( abs(eg) <= BishopValueEg && pos.count(strongSide) <= 2 && !pos.pawn_passed(~strongSide, pos.square(~strongSide))) - sf = ScaleFactor(37 + 7 * pos.count(strongSide)); + return ScaleFactor(37 + 7 * pos.count(strongSide)); } return sf; } -} // namespace + // value() is the main function of the class. It computes the various parts of + // the evaluation and returns the value of the position from the point of view + // of the side to move. -/// evaluate() is the main evaluation function. It returns a static evaluation -/// of the position from the point of view of the side to move. + template + Value Evaluation::value() { -template -Value Eval::evaluate(const Position& pos) { + assert(!pos.checkers()); - assert(!pos.checkers()); + // Probe the material hash table + me = Material::probe(pos); - Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; - EvalInfo ei; + // If we have a specialized evaluation function for the current material + // configuration, call it and return. + if (me->specialized_eval_exists()) + return me->evaluate(pos); - // Probe the material hash table - ei.me = Material::probe(pos); + // Initialize score by reading the incrementally updated scores included in + // the position object (material + piece square tables) and the material + // imbalance. Score is computed internally from the white point of view. + Score score = pos.psq_score() + me->imbalance() + Eval::Contempt; - // If we have a specialized evaluation function for the current material - // configuration, call it and return. - if (ei.me->specialized_eval_exists()) - return ei.me->evaluate(pos); + // Probe the pawn hash table + pe = Pawns::probe(pos); + score += pe->pawns_score(); - // Initialize score by reading the incrementally updated scores included in - // the position object (material + piece square tables) and the material - // imbalance. Score is computed internally from the white point of view. - Score score = pos.psq_score() + ei.me->imbalance(); + // Early exit if score is high + Value v = (mg_value(score) + eg_value(score)) / 2; + if (abs(v) > LazyThreshold) + return pos.side_to_move() == WHITE ? v : -v; - // Probe the pawn hash table - ei.pi = Pawns::probe(pos); - score += ei.pi->pawns_score(); + // Main evaluation begins here - // Initialize attack and king safety bitboards - ei.attackedBy[WHITE][ALL_PIECES] = ei.attackedBy[BLACK][ALL_PIECES] = 0; - ei.attackedBy[WHITE][KING] = pos.attacks_from(pos.square(WHITE)); - ei.attackedBy[BLACK][KING] = pos.attacks_from(pos.square(BLACK)); - eval_init(pos, ei); - eval_init(pos, ei); + initialize(); + initialize(); - // Pawns blocked or on ranks 2 and 3 will be excluded from the mobility area - Bitboard blockedPawns[] = { - pos.pieces(WHITE, PAWN) & (shift(pos.pieces()) | Rank2BB | Rank3BB), - pos.pieces(BLACK, PAWN) & (shift(pos.pieces()) | Rank7BB | Rank6BB) - }; + score += evaluate_pieces() - evaluate_pieces(); + score += evaluate_pieces() - evaluate_pieces(); + score += evaluate_pieces() - evaluate_pieces(); + score += evaluate_pieces() - evaluate_pieces(); - // Do not include in mobility area squares protected by enemy pawns, or occupied - // by our blocked pawns or king. - Bitboard mobilityArea[] = { - ~(ei.attackedBy[BLACK][PAWN] | blockedPawns[WHITE] | pos.square(WHITE)), - ~(ei.attackedBy[WHITE][PAWN] | blockedPawns[BLACK] | pos.square(BLACK)) - }; + score += mobility[WHITE] - mobility[BLACK]; - // Evaluate all pieces but king and pawns - score += evaluate_pieces(pos, ei, mobility, mobilityArea); - score += mobility[WHITE] - mobility[BLACK]; + score += evaluate_king() + - evaluate_king(); - // Evaluate kings after all other pieces because we need full attack - // information when computing the king safety evaluation. - score += evaluate_king(pos, ei) - - evaluate_king(pos, ei); + score += evaluate_threats() + - evaluate_threats(); - // Evaluate tactical threats, we need full attack information including king - score += evaluate_threats(pos, ei) - - evaluate_threats(pos, ei); + score += evaluate_passed_pawns() + - evaluate_passed_pawns(); - // Evaluate passed pawns, we need full attack information including king - score += evaluate_passed_pawns(pos, ei) - - evaluate_passed_pawns(pos, ei); + if (pos.non_pawn_material() >= SpaceThreshold) + score += evaluate_space() + - evaluate_space(); - // If both sides have only pawns, score for potential unstoppable pawns - if (!pos.non_pawn_material(WHITE) && !pos.non_pawn_material(BLACK)) - { - Bitboard b; - if ((b = ei.pi->passed_pawns(WHITE)) != 0) - score += Unstoppable * int(relative_rank(WHITE, frontmost_sq(WHITE, b))); + score += evaluate_initiative(eg_value(score)); - if ((b = ei.pi->passed_pawns(BLACK)) != 0) - score -= Unstoppable * int(relative_rank(BLACK, frontmost_sq(BLACK, b))); - } + // Interpolate between a middlegame and a (scaled by 'sf') endgame score + ScaleFactor sf = evaluate_scale_factor(eg_value(score)); + v = mg_value(score) * int(me->game_phase()) + + eg_value(score) * int(PHASE_MIDGAME - me->game_phase()) * sf / SCALE_FACTOR_NORMAL; + + v /= int(PHASE_MIDGAME); + + // In case of tracing add all remaining individual evaluation terms + if (T) + { + Trace::add(MATERIAL, pos.psq_score()); + Trace::add(IMBALANCE, me->imbalance()); + Trace::add(PAWN, pe->pawns_score()); + Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); + if (pos.non_pawn_material() >= SpaceThreshold) + Trace::add(SPACE, evaluate_space() + , evaluate_space()); + Trace::add(TOTAL, score); + } - // Evaluate space for both sides, only during opening - if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 12222) - score += evaluate_space(pos, ei) - - evaluate_space(pos, ei); - - // Evaluate position potential for the winning side - score += evaluate_initiative(pos, ei.pi->pawn_asymmetry(), eg_value(score)); - - // Evaluate scale factor for the winning side - ScaleFactor sf = evaluate_scale_factor(pos, ei, eg_value(score)); - - // Interpolate between a middlegame and a (scaled by 'sf') endgame score - Value v = mg_value(score) * int(ei.me->game_phase()) - + eg_value(score) * int(PHASE_MIDGAME - ei.me->game_phase()) * sf / SCALE_FACTOR_NORMAL; - - v /= int(PHASE_MIDGAME); - - // In case of tracing add all remaining individual evaluation terms - if (DoTrace) - { - Trace::add(MATERIAL, pos.psq_score()); - Trace::add(IMBALANCE, ei.me->imbalance()); - Trace::add(PAWN, ei.pi->pawns_score()); - Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); - Trace::add(SPACE, evaluate_space(pos, ei) - , evaluate_space(pos, ei)); - Trace::add(TOTAL, score); + return pos.side_to_move() == WHITE ? v : -v; // Side to move point of view } - return (pos.side_to_move() == WHITE ? v : -v) + Eval::Tempo; // Side to move point of view -} +} // namespace -// Explicit template instantiations -template Value Eval::evaluate(const Position&); -template Value Eval::evaluate(const Position&); +Score Eval::Contempt = SCORE_ZERO; +/// evaluate() is the evaluator for the outer world. It returns a static evaluation +/// of the position from the point of view of the side to move. + +Value Eval::evaluate(const Position& pos) +{ + return Evaluation<>(pos).value() + Eval::Tempo; +} /// trace() is like evaluate(), but instead of returning a value, it returns /// a string (suitable for outputting to stdout) that contains the detailed @@ -898,7 +922,7 @@ std::string Eval::trace(const Position& pos) { std::memset(scores, 0, sizeof(scores)); - Value v = evaluate(pos); + Value v = Evaluation(pos).value() + Eval::Tempo; v = pos.side_to_move() == WHITE ? v : -v; // White's point of view std::stringstream ss; @@ -910,7 +934,7 @@ std::string Eval::trace(const Position& pos) { << " Imbalance | " << Term(IMBALANCE) << " Pawns | " << Term(PAWN) << " Knights | " << Term(KNIGHT) - << " Bishop | " << Term(BISHOP) + << " Bishops | " << Term(BISHOP) << " Rooks | " << Term(ROOK) << " Queens | " << Term(QUEEN) << " Mobility | " << Term(MOBILITY) @@ -918,6 +942,7 @@ std::string Eval::trace(const Position& pos) { << " Threats | " << Term(THREAT) << " Passed pawns | " << Term(PASSED) << " Space | " << Term(SPACE) + << " Initiative | " << Term(INITIATIVE) << "----------------+-------------+-------------+-------------\n" << " Total | " << Term(TOTAL); diff --git a/Engines/Linux64/stockfish/src/evaluate.h b/Engines/Linux64/stockfish/src/evaluate.h index 7f655f6..eef888d 100644 --- a/Engines/Linux64/stockfish/src/evaluate.h +++ b/Engines/Linux64/stockfish/src/evaluate.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,9 +31,10 @@ namespace Eval { const Value Tempo = Value(20); // Must be visible to search +extern Score Contempt; + std::string trace(const Position& pos); -template Value evaluate(const Position& pos); } diff --git a/Engines/Linux64/stockfish/src/main.cpp b/Engines/Linux64/stockfish/src/main.cpp index 7187d30..aad09ce 100644 --- a/Engines/Linux64/stockfish/src/main.cpp +++ b/Engines/Linux64/stockfish/src/main.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -43,12 +43,13 @@ int main(int argc, char* argv[]) { Bitbases::init(); Search::init(); Pawns::init(); - Threads.init(); Tablebases::init(Options["SyzygyPath"]); TT.resize(Options["Hash"]); + Threads.set(Options["Threads"]); + Search::clear(); // After threads are up UCI::loop(argc, argv); - Threads.exit(); + Threads.set(0); return 0; } diff --git a/Engines/Linux64/stockfish/src/material.cpp b/Engines/Linux64/stockfish/src/material.cpp index f6c4e2d..fb39209 100644 --- a/Engines/Linux64/stockfish/src/material.cpp +++ b/Engines/Linux64/stockfish/src/material.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,11 +35,11 @@ namespace { // OUR PIECES // pair pawn knight bishop rook queen {1667 }, // Bishop pair - { 40, 2 }, // Pawn + { 40, 0 }, // Pawn { 32, 255, -3 }, // Knight OUR PIECES { 0, 104, 4, 0 }, // Bishop { -26, -2, 47, 105, -149 }, // Rook - {-185, 24, 122, 137, -134, 0 } // Queen + {-189, 24, 117, 133, -134, -10 } // Queen }; const int QuadraticTheirs[][PIECE_TYPE_NB] = { @@ -50,7 +50,7 @@ namespace { { 9, 63, 0 }, // Knight OUR PIECES { 59, 65, 42, 0 }, // Bishop { 46, 39, 24, -24, 0 }, // Rook - { 101, 100, -37, 141, 268, 0 } // Queen + { 97, 100, -42, 137, 268, 0 } // Queen }; // Endgame evaluation and scaling functions are accessed directly and not through @@ -91,7 +91,7 @@ namespace { int bonus = 0; - // Second-degree polynomial material imbalance by Tord Romstad + // Second-degree polynomial material imbalance, by Tord Romstad for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) { if (!pieceCount[Us][pt1]) @@ -129,7 +129,13 @@ Entry* probe(const Position& pos) { std::memset(e, 0, sizeof(Entry)); e->key = key; e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; - e->gamePhase = pos.game_phase(); + + Value npm_w = pos.non_pawn_material(WHITE); + Value npm_b = pos.non_pawn_material(BLACK); + Value npm = std::max(EndgameLimit, std::min(npm_w + npm_b, MidgameLimit)); + + // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME] + e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); // Let's look if we have a specialized evaluation function for this particular // material configuration. Firstly we look for a fixed configuration one, then @@ -150,7 +156,7 @@ Entry* probe(const Position& pos) { if ((sf = pos.this_thread()->endgames.probe(key)) != nullptr) { - e->scalingFunction[sf->strong_side()] = sf; // Only strong color assigned + e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned return e; } @@ -166,9 +172,6 @@ Entry* probe(const Position& pos) { e->scalingFunction[c] = &ScaleKQKRPs[c]; } - Value npm_w = pos.non_pawn_material(WHITE); - Value npm_b = pos.non_pawn_material(BLACK); - if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board { if (!pos.count(BLACK)) diff --git a/Engines/Linux64/stockfish/src/material.h b/Engines/Linux64/stockfish/src/material.h index bec2d66..7fea5e7 100644 --- a/Engines/Linux64/stockfish/src/material.h +++ b/Engines/Linux64/stockfish/src/material.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -56,11 +56,11 @@ struct Entry { } Key key; - int16_t value; - uint8_t factor[COLOR_NB]; EndgameBase* evaluationFunction; EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each // side (e.g. KPKP, KBPsKs) + int16_t value; + uint8_t factor[COLOR_NB]; Phase gamePhase; }; diff --git a/Engines/Linux64/stockfish/src/misc.cpp b/Engines/Linux64/stockfish/src/misc.cpp index 4c6c254..2eb62f3 100644 --- a/Engines/Linux64/stockfish/src/misc.cpp +++ b/Engines/Linux64/stockfish/src/misc.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,10 +18,29 @@ along with this program. If not, see . */ +#ifdef _WIN32 +#if _WIN32_WINNT < 0x0601 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes +#endif +#include +// The needed Windows API for processor groups could be missed from old Windows +// versions, so instead of calling them directly (forcing the linker to resolve +// the calls at compile time), try to load them at runtime. To do this we need +// first to define the corresponding function pointers. +extern "C" { +typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); +typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY); +typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); +} +#endif + #include #include #include #include +#include #include "misc.h" #include "thread.h" @@ -32,7 +51,7 @@ namespace { /// Version number. If Version is left empty, then compile date in the format /// DD-MM-YY and show in engine_info. -const string Version = "8"; +const string Version = "9"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We @@ -44,10 +63,10 @@ struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {} - int sync() { return logBuf->pubsync(), buf->pubsync(); } - int overflow(int c) { return log(buf->sputc((char)c), "<< "); } - int underflow() { return buf->sgetc(); } - int uflow() { return log(buf->sbumpc(), ">> "); } + int sync() override { return logBuf->pubsync(), buf->pubsync(); } + int overflow(int c) override { return log(buf->sputc((char)c), "<< "); } + int underflow() override { return buf->sgetc(); } + int uflow() override { return log(buf->sbumpc(), ">> "); } streambuf *buf, *logBuf; @@ -185,3 +204,114 @@ void prefetch(void* addr) { } #endif + +void prefetch2(void* addr) { + + prefetch(addr); + prefetch((uint8_t*)addr + 64); +} + +namespace WinProcGroup { + +#ifndef _WIN32 + +void bindThisThread(size_t) {} + +#else + +/// get_group() retrieves logical processor information using Windows specific +/// API and returns the best group id for the thread with index idx. Original +/// code from Texel by Peter Österlund. + +int get_group(size_t idx) { + + int threads = 0; + int nodes = 0; + int cores = 0; + DWORD returnLength = 0; + DWORD byteOffset = 0; + + // Early exit if the needed API is not available at runtime + HMODULE k32 = GetModuleHandle("Kernel32.dll"); + auto fun1 = (fun1_t)GetProcAddress(k32, "GetLogicalProcessorInformationEx"); + if (!fun1) + return -1; + + // First call to get returnLength. We expect it to fail due to null buffer + if (fun1(RelationAll, nullptr, &returnLength)) + return -1; + + // Once we know returnLength, allocate the buffer + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; + ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength); + + // Second call, now we expect to succeed + if (!fun1(RelationAll, buffer, &returnLength)) + { + free(buffer); + return -1; + } + + while (ptr->Size > 0 && byteOffset + ptr->Size <= returnLength) + { + if (ptr->Relationship == RelationNumaNode) + nodes++; + + else if (ptr->Relationship == RelationProcessorCore) + { + cores++; + threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; + } + + byteOffset += ptr->Size; + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); + } + + free(buffer); + + std::vector groups; + + // Run as many threads as possible on the same node until core limit is + // reached, then move on filling the next node. + for (int n = 0; n < nodes; n++) + for (int i = 0; i < cores / nodes; i++) + groups.push_back(n); + + // In case a core has more than one logical processor (we assume 2) and we + // have still threads to allocate, then spread them evenly across available + // nodes. + for (int t = 0; t < threads - cores; t++) + groups.push_back(t % nodes); + + // If we still have more threads than the total number of logical processors + // then return -1 and let the OS to decide what to do. + return idx < groups.size() ? groups[idx] : -1; +} + + +/// bindThisThread() set the group affinity of the current thread + +void bindThisThread(size_t idx) { + + // Use only local variables to be thread-safe + int group = get_group(idx); + + if (group == -1) + return; + + // Early exit if the needed API are not available at runtime + HMODULE k32 = GetModuleHandle("Kernel32.dll"); + auto fun2 = (fun2_t)GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); + auto fun3 = (fun3_t)GetProcAddress(k32, "SetThreadGroupAffinity"); + + if (!fun2 || !fun3) + return; + + GROUP_AFFINITY affinity; + if (fun2(group, &affinity)) + fun3(GetCurrentThread(), &affinity, nullptr); +} + +#endif + +} // namespace WinProcGroup diff --git a/Engines/Linux64/stockfish/src/misc.h b/Engines/Linux64/stockfish/src/misc.h index a2307fe..563a58a 100644 --- a/Engines/Linux64/stockfish/src/misc.h +++ b/Engines/Linux64/stockfish/src/misc.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,6 +31,7 @@ const std::string engine_info(bool to_uci = false); void prefetch(void* addr); +void prefetch2(void* addr); void start_logger(const std::string& fname); void dbg_hit_on(bool b); @@ -97,4 +98,15 @@ class PRNG { { return T(rand64() & rand64() & rand64()); } }; + +/// Under Windows it is not possible for a process to run on more than one +/// logical processor group. This usually means to be limited to use max 64 +/// cores. To overcome this, some special platform specific API should be +/// called to set group affinity for each thread. Original code from Texel by +/// Peter Österlund. + +namespace WinProcGroup { + void bindThisThread(size_t idx); +} + #endif // #ifndef MISC_H_INCLUDED diff --git a/Engines/Linux64/stockfish/src/movegen.cpp b/Engines/Linux64/stockfish/src/movegen.cpp index 6a3cdc2..15de166 100644 --- a/Engines/Linux64/stockfish/src/movegen.cpp +++ b/Engines/Linux64/stockfish/src/movegen.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -42,8 +42,8 @@ namespace { assert(!pos.checkers()); - const Square K = Chess960 ? kto > kfrom ? WEST : EAST - : KingSide ? WEST : EAST; + const Direction K = Chess960 ? kto > kfrom ? WEST : EAST + : KingSide ? WEST : EAST; for (Square s = kto; s != kfrom; s += K) if (pos.attackers_to(s) & enemies) @@ -65,7 +65,7 @@ namespace { } - template + template ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) { if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) @@ -80,7 +80,7 @@ namespace { // Knight promotion is the only promotion that can give a direct check // that's not already included in the queen promotion. - if (Type == QUIET_CHECKS && (StepAttacksBB[W_KNIGHT][to] & ksq)) + if (Type == QUIET_CHECKS && (PseudoAttacks[KNIGHT][to] & ksq)) *moveList++ = make(to - D, to, KNIGHT); else (void)ksq; // Silence a warning under MSVC @@ -94,13 +94,13 @@ namespace { // Compute our parametrized parameters at compile time, named according to // the point of view of white side. - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); - const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); - const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); - const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); + const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Direction Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); Bitboard emptySquares; @@ -346,7 +346,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { if (pt == PAWN) continue; // Will be generated together with direct checks - Bitboard b = pos.attacks_from(Piece(pt), from) & ~pos.pieces(); + Bitboard b = pos.attacks_from(pt, from) & ~pos.pieces(); if (pt == KING) b &= ~PseudoAttacks[QUEEN][pos.square(~us)]; diff --git a/Engines/Linux64/stockfish/src/movegen.h b/Engines/Linux64/stockfish/src/movegen.h index 2721f15..9bf2a46 100644 --- a/Engines/Linux64/stockfish/src/movegen.h +++ b/Engines/Linux64/stockfish/src/movegen.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,6 +21,8 @@ #ifndef MOVEGEN_H_INCLUDED #define MOVEGEN_H_INCLUDED +#include + #include "types.h" class Position; @@ -36,10 +38,14 @@ enum GenType { struct ExtMove { Move move; - Value value; + int value; operator Move() const { return move; } void operator=(Move m) { move = m; } + + // Inhibit unwanted implicit conversions to Move + // with an ambiguity that yields to a compile error. + operator float() const = delete; }; inline bool operator<(const ExtMove& f, const ExtMove& s) { @@ -59,8 +65,7 @@ struct MoveList { const ExtMove* end() const { return last; } size_t size() const { return last - moveList; } bool contains(Move move) const { - for (const auto& m : *this) if (m == move) return true; - return false; + return std::find(begin(), end(), move) != end(); } private: diff --git a/Engines/Linux64/stockfish/src/movepick.cpp b/Engines/Linux64/stockfish/src/movepick.cpp index c187c3a..ab247b7 100644 --- a/Engines/Linux64/stockfish/src/movepick.cpp +++ b/Engines/Linux64/stockfish/src/movepick.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ #include #include "movepick.h" -#include "thread.h" namespace { @@ -34,27 +33,28 @@ namespace { QSEARCH_RECAPTURES, QRECAPTURES }; - // Our insertion sort, which is guaranteed to be stable, as it should be - void insertion_sort(ExtMove* begin, ExtMove* end) - { - ExtMove tmp, *p, *q; - - for (p = begin + 1; p < end; ++p) - { - tmp = *p; - for (q = p; q != begin && *(q-1) < tmp; --q) - *q = *(q-1); - *q = tmp; - } + // partial_insertion_sort() sorts moves in descending order up to and including + // a given limit. The order of moves smaller than the limit is left unspecified. + void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { + + for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) + if (p->value >= limit) + { + ExtMove tmp = *p, *q; + *p = *++sortedEnd; + for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q) + *q = *(q - 1); + *q = tmp; + } } // pick_best() finds the best move in the range (begin, end) and moves it to // the front. It's faster than sorting all the moves in advance when there // are few moves, e.g., the possible captures. - Move pick_best(ExtMove* begin, ExtMove* end) - { - std::swap(*begin, *std::max_element(begin, end)); - return *begin; + Move pick_best(ExtMove* begin, ExtMove* end) { + + std::swap(*begin, *std::max_element(begin, end)); + return *begin; } } // namespace @@ -66,21 +66,22 @@ namespace { /// search captures, promotions, and some checks) and how important good move /// ordering is at the current node. -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Search::Stack* s) - : pos(p), ss(s), depth(d) { +/// MovePicker constructor for the main search +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, + const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, Move* killers_p) + : pos(p), mainHistory(mh), captureHistory(cph), contHistory(ch), countermove(cm), + killers{killers_p[0], killers_p[1]}, depth(d){ assert(d > DEPTH_ZERO); - Square prevSq = to_sq((ss-1)->currentMove); - countermove = pos.this_thread()->counterMoves[pos.piece_on(prevSq)][prevSq]; - stage = pos.checkers() ? EVASION : MAIN_SEARCH; ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE; stage += (ttMove == MOVE_NONE); } -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Square s) - : pos(p) { +/// MovePicker constructor for quiescence search +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const CapturePieceToHistory* cph, Square s) + : pos(p), mainHistory(mh), captureHistory(cph) { assert(d <= DEPTH_ZERO); @@ -104,81 +105,57 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Square s) stage += (ttMove == MOVE_NONE); } -MovePicker::MovePicker(const Position& p, Move ttm, Value th) - : pos(p), threshold(th) { +/// MovePicker constructor for ProbCut: we generate captures with SEE higher +/// than or equal to the given threshold. +MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) + : pos(p), captureHistory(cph), threshold(th) { assert(!pos.checkers()); stage = PROBCUT; - - // In ProbCut we generate captures with SEE higher than the given threshold ttMove = ttm && pos.pseudo_legal(ttm) && pos.capture(ttm) - && pos.see_ge(ttm, threshold + 1)? ttm : MOVE_NONE; + && pos.see_ge(ttm, threshold) ? ttm : MOVE_NONE; stage += (ttMove == MOVE_NONE); } +/// score() assigns a numerical value to each move in a list, used for sorting. +/// Captures are ordered by Most Valuable Victim (MVV), preferring captures +/// with a good history. Quiets are ordered using the histories. +template +void MovePicker::score() { -/// score() assigns a numerical value to each move in a move list. The moves with -/// highest values will be picked first. -template<> -void MovePicker::score() { - // Winning and equal captures in the main search are ordered by MVV, preferring - // captures near our home rank. Surprisingly, this appears to perform slightly - // better than SEE-based move ordering: exchanging big pieces before capturing - // a hanging piece probably helps to reduce the subtree size. - // In the main search we want to push captures with negative SEE values to the - // badCaptures[] array, but instead of doing it now we delay until the move - // has been picked up, saving some SEE calls in case we get a cutoff. - for (auto& m : *this) - m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - - Value(200 * relative_rank(pos.side_to_move(), to_sq(m))); -} - -template<> -void MovePicker::score() { - - const HistoryStats& history = pos.this_thread()->history; - const FromToStats& fromTo = pos.this_thread()->fromTo; - - const CounterMoveStats* cm = (ss-1)->counterMoves; - const CounterMoveStats* fm = (ss-2)->counterMoves; - const CounterMoveStats* f2 = (ss-4)->counterMoves; - - Color c = pos.side_to_move(); + static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); for (auto& m : *this) - m.value = history[pos.moved_piece(m)][to_sq(m)] - + (cm ? (*cm)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) - + (fm ? (*fm)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) - + (f2 ? (*f2)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) - + fromTo.get(c, m); -} + if (Type == CAPTURES) + m.value = PieceValue[MG][pos.piece_on(to_sq(m))] + + Value((*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]); -template<> -void MovePicker::score() { - // Try captures ordered by MVV/LVA, then non-captures ordered by history value - const HistoryStats& history = pos.this_thread()->history; - const FromToStats& fromTo = pos.this_thread()->fromTo; - Color c = pos.side_to_move(); + else if (Type == QUIETS) + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + (*contHistory[0])[pos.moved_piece(m)][to_sq(m)] + + (*contHistory[1])[pos.moved_piece(m)][to_sq(m)] + + (*contHistory[3])[pos.moved_piece(m)][to_sq(m)]; - for (auto& m : *this) - if (pos.capture(m)) - m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - - Value(type_of(pos.moved_piece(m))) + HistoryStats::Max; - else - m.value = history[pos.moved_piece(m)][to_sq(m)] + fromTo.get(c, m); + else // Type == EVASIONS + { + if (pos.capture(m)) + m.value = PieceValue[MG][pos.piece_on(to_sq(m))] + - Value(type_of(pos.moved_piece(m))); + else + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - (1 << 28); + } } - /// next_move() is the most important method of the MovePicker class. It returns /// a new pseudo legal move every time it is called, until there are no more moves /// left. It picks the move with the biggest value from a list of generated moves /// taking care not to return the ttMove if it has already been searched. -Move MovePicker::next_move() { +Move MovePicker::next_move(bool skipQuiets) { Move move; @@ -194,6 +171,7 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); ++stage; + /* fallthrough */ case GOOD_CAPTURES: while (cur < endMoves) @@ -201,7 +179,7 @@ Move MovePicker::next_move() { move = pick_best(cur++, endMoves); if (move != ttMove) { - if (pos.see_ge(move, VALUE_ZERO)) + if (pos.see_ge(move, Value(-55 * (cur-1)->value / 1024))) return move; // Losing capture, move it to the beginning of the array @@ -210,58 +188,59 @@ Move MovePicker::next_move() { } ++stage; - move = ss->killers[0]; // First killer move + move = killers[0]; // First killer move if ( move != MOVE_NONE && move != ttMove && pos.pseudo_legal(move) && !pos.capture(move)) return move; + /* fallthrough */ case KILLERS: ++stage; - move = ss->killers[1]; // Second killer move + move = killers[1]; // Second killer move if ( move != MOVE_NONE && move != ttMove && pos.pseudo_legal(move) && !pos.capture(move)) return move; + /* fallthrough */ case COUNTERMOVE: ++stage; move = countermove; if ( move != MOVE_NONE && move != ttMove - && move != ss->killers[0] - && move != ss->killers[1] + && move != killers[0] + && move != killers[1] && pos.pseudo_legal(move) && !pos.capture(move)) return move; + /* fallthrough */ case QUIET_INIT: cur = endBadCaptures; endMoves = generate(pos, cur); score(); - if (depth < 3 * ONE_PLY) - { - ExtMove* goodQuiet = std::partition(cur, endMoves, [](const ExtMove& m) - { return m.value > VALUE_ZERO; }); - insertion_sort(cur, goodQuiet); - } else - insertion_sort(cur, endMoves); + partial_insertion_sort(cur, endMoves, -4000 * depth / ONE_PLY); ++stage; + /* fallthrough */ case QUIET: - while (cur < endMoves) + while ( cur < endMoves + && (!skipQuiets || cur->value >= VALUE_ZERO)) { move = *cur++; + if ( move != ttMove - && move != ss->killers[0] - && move != ss->killers[1] + && move != killers[0] + && move != killers[1] && move != countermove) return move; } ++stage; cur = moves; // Point to beginning of bad captures + /* fallthrough */ case BAD_CAPTURES: if (cur < endBadCaptures) @@ -273,6 +252,7 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); ++stage; + /* fallthrough */ case ALL_EVASIONS: while (cur < endMoves) @@ -288,13 +268,14 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); ++stage; + /* fallthrough */ case PROBCUT_CAPTURES: while (cur < endMoves) { move = pick_best(cur++, endMoves); if ( move != ttMove - && pos.see_ge(move, threshold + 1)) + && pos.see_ge(move, threshold)) return move; } break; @@ -304,6 +285,7 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); ++stage; + /* fallthrough */ case QCAPTURES_1: case QCAPTURES_2: while (cur < endMoves) @@ -317,6 +299,7 @@ Move MovePicker::next_move() { cur = moves; endMoves = generate(pos, cur); ++stage; + /* fallthrough */ case QCHECKS: while (cur < endMoves) @@ -332,6 +315,7 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); ++stage; + /* fallthrough */ case QRECAPTURES: while (cur < endMoves) diff --git a/Engines/Linux64/stockfish/src/movepick.h b/Engines/Linux64/stockfish/src/movepick.h index 6fbd8be..0aba61d 100644 --- a/Engines/Linux64/stockfish/src/movepick.h +++ b/Engines/Linux64/stockfish/src/movepick.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,68 +21,98 @@ #ifndef MOVEPICK_H_INCLUDED #define MOVEPICK_H_INCLUDED -#include // For std::max -#include // For std::memset +#include +#include #include "movegen.h" #include "position.h" #include "types.h" +/// StatBoards is a generic 2-dimensional array used to store various statistics +template +struct StatBoards : public std::array, Size1> { -/// The Stats struct stores moves statistics. According to the template parameter -/// the class can store History and Countermoves. History records how often -/// different moves have been successful or unsuccessful during the current search -/// and is used for reduction and move ordering decisions. -/// Countermoves store the move that refute a previous one. Entries are stored -/// using only the moving piece and destination square, hence two moves with -/// different origin but same destination and piece will be considered identical. -template -struct Stats { + void fill(const T& v) { + T* p = &(*this)[0][0]; + std::fill(p, p + sizeof(*this) / sizeof(*p), v); + } - static const Value Max = Value(1 << 28); + void update(T& entry, int bonus, const int D) { - const T* operator[](Piece pc) const { return table[pc]; } - T* operator[](Piece pc) { return table[pc]; } - void clear() { std::memset(table, 0, sizeof(table)); } - void update(Piece pc, Square to, Move m) { table[pc][to] = m; } - void update(Piece pc, Square to, Value v) { + assert(abs(bonus) <= D); // Ensure range is [-32 * D, 32 * D] + assert(abs(32 * D) < (std::numeric_limits::max)()); // Ensure we don't overflow - if (abs(int(v)) >= 324) - return; + entry += bonus * 32 - entry * abs(bonus) / D; - table[pc][to] -= table[pc][to] * abs(int(v)) / (CM ? 936 : 324); - table[pc][to] += int(v) * 32; + assert(abs(entry) <= 32 * D); } +}; -private: - T table[PIECE_NB][SQUARE_NB]; +/// StatCubes is a generic 3-dimensional array used to store various statistics +template +struct StatCubes : public std::array, Size2>, Size1> { + + void fill(const T& v) { + T* p = &(*this)[0][0][0]; + std::fill(p, p + sizeof(*this) / sizeof(*p), v); + } + + void update(T& entry, int bonus, const int D, const int W) { + + assert(abs(bonus) <= D); // Ensure range is [-W * D, W * D] + assert(abs(W * D) < (std::numeric_limits::max)()); // Ensure we don't overflow + + entry += bonus * W - entry * abs(bonus) / D; + + assert(abs(entry) <= W * D); + } }; -typedef Stats MoveStats; -typedef Stats HistoryStats; -typedef Stats CounterMoveStats; -typedef Stats CounterMoveHistoryStats; +/// ButterflyBoards are 2 tables (one for each color) indexed by the move's from +/// and to squares, see chessprogramming.wikispaces.com/Butterfly+Boards +typedef StatBoards ButterflyBoards; -struct FromToStats { +/// PieceToBoards are addressed by a move's [piece][to] information +typedef StatBoards PieceToBoards; - Value get(Color c, Move m) const { return table[c][from_sq(m)][to_sq(m)]; } - void clear() { std::memset(table, 0, sizeof(table)); } - void update(Color c, Move m, Value v) { +/// CapturePieceToBoards are addressed by a move's [piece][to][captured piece type] information +typedef StatCubes CapturePieceToBoards; - if (abs(int(v)) >= 324) - return; +/// ButterflyHistory records how often quiet moves have been successful or +/// unsuccessful during the current search, and is used for reduction and move +/// ordering decisions. It uses ButterflyBoards as backing store. +struct ButterflyHistory : public ButterflyBoards { - Square from = from_sq(m); - Square to = to_sq(m); + void update(Color c, Move m, int bonus) { + StatBoards::update((*this)[c][from_to(m)], bonus, 324); + } +}; + +/// PieceToHistory is like ButterflyHistory, but is based on PieceToBoards +struct PieceToHistory : public PieceToBoards { - table[c][from][to] -= table[c][from][to] * abs(int(v)) / 324; - table[c][from][to] += int(v) * 32; + void update(Piece pc, Square to, int bonus) { + StatBoards::update((*this)[pc][to], bonus, 936); } +}; -private: - Value table[COLOR_NB][SQUARE_NB][SQUARE_NB]; +/// CapturePieceToHistory is like PieceToHistory, but is based on CapturePieceToBoards +struct CapturePieceToHistory : public CapturePieceToBoards { + + void update(Piece pc, Square to, PieceType captured, int bonus) { + StatCubes::update((*this)[pc][to][captured], bonus, 324, 2); + } }; +/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous +/// move, see chessprogramming.wikispaces.com/Countermove+Heuristic +typedef StatBoards CounterMoveHistory; + +/// ContinuationHistory is the history of a given pair of moves, usually the +/// current one given a previous one. History table is based on PieceToBoards +/// instead of ButterflyBoards. +typedef StatBoards ContinuationHistory; + /// MovePicker class is used to pick one pseudo legal move at a time from the /// current position. The most important method is next_move(), which returns a @@ -90,18 +120,15 @@ struct FromToStats { /// when MOVE_NONE is returned. In order to improve the efficiency of the alpha /// beta algorithm, MovePicker attempts to return the moves which are most likely /// to get a cut-off first. -namespace Search { struct Stack; } class MovePicker { public: MovePicker(const MovePicker&) = delete; MovePicker& operator=(const MovePicker&) = delete; - - MovePicker(const Position&, Move, Value); - MovePicker(const Position&, Move, Depth, Square); - MovePicker(const Position&, Move, Depth, Search::Stack*); - - Move next_move(); + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + MovePicker(const Position&, Move, Depth, const ButterflyHistory*, const CapturePieceToHistory*, Square); + MovePicker(const Position&, Move, Depth, const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, Move, Move*); + Move next_move(bool skipQuiets = false); private: template void score(); @@ -109,14 +136,15 @@ class MovePicker { ExtMove* end() { return endMoves; } const Position& pos; - const Search::Stack* ss; - Move countermove; - Depth depth; - Move ttMove; + const ButterflyHistory* mainHistory; + const CapturePieceToHistory* captureHistory; + const PieceToHistory** contHistory; + Move ttMove, countermove, killers[2]; + ExtMove *cur, *endMoves, *endBadCaptures; + int stage; Square recaptureSquare; Value threshold; - int stage; - ExtMove *cur, *endMoves, *endBadCaptures; + Depth depth; ExtMove moves[MAX_MOVES]; }; diff --git a/Engines/Linux64/stockfish/src/pawns.cpp b/Engines/Linux64/stockfish/src/pawns.cpp index 80f5e49..ceacca8 100644 --- a/Engines/Linux64/stockfish/src/pawns.cpp +++ b/Engines/Linux64/stockfish/src/pawns.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,53 +31,51 @@ namespace { #define V Value #define S(mg, eg) make_score(mg, eg) - // Isolated pawn penalty by opposed flag - const Score Isolated[2] = { S(45, 40), S(30, 27) }; + // Isolated pawn penalty + const Score Isolated = S(13, 18); - // Backward pawn penalty by opposed flag - const Score Backward[2] = { S(56, 33), S(41, 19) }; + // Backward pawn penalty + const Score Backward = S(24, 12); - // Unsupported pawn penalty for pawns which are neither isolated or backward - const Score Unsupported = S(17, 8); - - // Connected pawn bonus by opposed, phalanx, twice supported and rank - Score Connected[2][2][2][RANK_NB]; + // Connected pawn bonus by opposed, phalanx, #support and rank + Score Connected[2][2][3][RANK_NB]; // Doubled pawn penalty - const Score Doubled = S(18,38); - - // Lever bonus by rank - const Score Lever[RANK_NB] = { - S( 0, 0), S( 0, 0), S(0, 0), S(0, 0), - S(17, 16), S(33, 32), S(0, 0), S(0, 0) - }; - - // Weakness of our pawn shelter in front of the king by [distance from edge][rank] - const Value ShelterWeakness[][RANK_NB] = { - { V( 97), V(21), V(26), V(51), V(87), V( 89), V( 99) }, - { V(120), V( 0), V(28), V(76), V(88), V(103), V(104) }, - { V(101), V( 7), V(54), V(78), V(77), V( 92), V(101) }, - { V( 80), V(11), V(44), V(68), V(87), V( 90), V(119) } + const Score Doubled = S(18, 38); + + // Weakness of our pawn shelter in front of the king by [isKingFile][distance from edge][rank]. + // RANK_1 = 0 is used for files where we have no pawns or our pawn is behind our king. + const Value ShelterWeakness[][int(FILE_NB) / 2][RANK_NB] = { + { { V( 97), V(17), V( 9), V(44), V( 84), V( 87), V( 99) }, // Not On King file + { V(106), V( 6), V(33), V(86), V( 87), V(104), V(112) }, + { V(101), V( 2), V(65), V(98), V( 58), V( 89), V(115) }, + { V( 73), V( 7), V(54), V(73), V( 84), V( 83), V(111) } }, + { { V(104), V(20), V( 6), V(27), V( 86), V( 93), V( 82) }, // On King file + { V(123), V( 9), V(34), V(96), V(112), V( 88), V( 75) }, + { V(120), V(25), V(65), V(91), V( 66), V( 78), V(117) }, + { V( 81), V( 2), V(47), V(63), V( 94), V( 93), V(104) } } }; - // Danger of enemy pawns moving toward our king by [type][distance from edge][rank] + // Danger of enemy pawns moving toward our king by [type][distance from edge][rank]. + // For the unopposed and unblocked cases, RANK_1 = 0 is used when opponent has + // no pawn on the given file, or their pawn is behind our king. const Value StormDanger[][4][RANK_NB] = { - { { V( 0), V( 67), V( 134), V(38), V(32) }, - { V( 0), V( 57), V( 139), V(37), V(22) }, - { V( 0), V( 43), V( 115), V(43), V(27) }, - { V( 0), V( 68), V( 124), V(57), V(32) } }, - { { V(20), V( 43), V( 100), V(56), V(20) }, - { V(23), V( 20), V( 98), V(40), V(15) }, - { V(23), V( 39), V( 103), V(36), V(18) }, - { V(28), V( 19), V( 108), V(42), V(26) } }, - { { V( 0), V( 0), V( 75), V(14), V( 2) }, - { V( 0), V( 0), V( 150), V(30), V( 4) }, - { V( 0), V( 0), V( 160), V(22), V( 5) }, - { V( 0), V( 0), V( 166), V(24), V(13) } }, - { { V( 0), V(-283), V(-281), V(57), V(31) }, - { V( 0), V( 58), V( 141), V(39), V(18) }, - { V( 0), V( 65), V( 142), V(48), V(32) }, - { V( 0), V( 60), V( 126), V(51), V(19) } } + { { V( 0), V(-290), V(-274), V(57), V(41) }, // BlockedByKing + { V( 0), V( 60), V( 144), V(39), V(13) }, + { V( 0), V( 65), V( 141), V(41), V(34) }, + { V( 0), V( 53), V( 127), V(56), V(14) } }, + { { V( 4), V( 73), V( 132), V(46), V(31) }, // Unopposed + { V( 1), V( 64), V( 143), V(26), V(13) }, + { V( 1), V( 47), V( 110), V(44), V(24) }, + { V( 0), V( 72), V( 127), V(50), V(31) } }, + { { V( 0), V( 0), V( 79), V(23), V( 1) }, // BlockedByPawn + { V( 0), V( 0), V( 148), V(27), V( 2) }, + { V( 0), V( 0), V( 161), V(16), V( 1) }, + { V( 0), V( 0), V( 171), V(22), V(15) } }, + { { V(22), V( 45), V( 104), V(62), V( 6) }, // Unblocked + { V(31), V( 30), V( 99), V(39), V(19) }, + { V(23), V( 29), V( 96), V(41), V(15) }, + { V(21), V( 23), V( 116), V(41), V(15) } } }; // Max bonus for king safety. Corresponds to start position with all the pawns @@ -90,22 +88,22 @@ namespace { template Score evaluate(const Position& pos, Pawns::Entry* e) { - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); - const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Direction Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); Bitboard b, neighbours, stoppers, doubled, supported, phalanx; + Bitboard lever, leverPush; Square s; - bool opposed, lever, connected, backward; + bool opposed, backward; Score score = SCORE_ZERO; const Square* pl = pos.squares(Us); - const Bitboard* pawnAttacksBB = StepAttacksBB[make_piece(Us, PAWN)]; - Bitboard ourPawns = pos.pieces(Us , PAWN); + Bitboard ourPawns = pos.pieces( Us, PAWN); Bitboard theirPawns = pos.pieces(Them, PAWN); - e->passedPawns[Us] = e->pawnAttacksSpan[Us] = 0; + e->passedPawns[Us] = e->pawnAttacksSpan[Us] = e->weakUnopposed[Us] = 0; e->semiopenFiles[Us] = 0xFF; e->kingSquares[Us] = SQ_NONE; e->pawnAttacks[Us] = shift(ourPawns) | shift(ourPawns); @@ -123,14 +121,14 @@ namespace { e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s); // Flag the pawn - opposed = theirPawns & forward_bb(Us, s); + opposed = theirPawns & forward_file_bb(Us, s); stoppers = theirPawns & passed_pawn_mask(Us, s); - lever = theirPawns & pawnAttacksBB[s]; - doubled = ourPawns & (s + Up); + lever = theirPawns & PawnAttacks[Us][s]; + leverPush = theirPawns & PawnAttacks[Us][s + Up]; + doubled = ourPawns & (s - Up); neighbours = ourPawns & adjacent_files_bb(f); phalanx = neighbours & rank_bb(s); supported = neighbours & rank_bb(s - Up); - connected = supported | phalanx; // A pawn is backward when it is behind all pawns of the same color on the // adjacent files and cannot be safely advanced. @@ -146,32 +144,40 @@ namespace { // stopper on adjacent file which controls the way to that rank. backward = (b | shift(b & adjacent_files_bb(f))) & stoppers; - assert(!backward || !(pawn_attack_span(Them, s + Up) & neighbours)); + assert(!(backward && (forward_ranks_bb(Them, s + Up) & neighbours))); } // Passed pawns will be properly scored in evaluation because we need - // full attack info to evaluate them. - if (!stoppers && !(ourPawns & forward_bb(Us, s))) + // full attack info to evaluate them. Include also not passed pawns + // which could become passed after one or two pawn pushes when are + // not attacked more times than defended. + if ( !(stoppers ^ lever ^ leverPush) + && !(ourPawns & forward_file_bb(Us, s)) + && popcount(supported) >= popcount(lever) + && popcount(phalanx) >= popcount(leverPush)) e->passedPawns[Us] |= s; - // Score this pawn - if (!neighbours) - score -= Isolated[opposed]; + else if ( stoppers == SquareBB[s + Up] + && relative_rank(Us, s) >= RANK_5) + { + b = shift(supported) & ~theirPawns; + while (b) + if (!more_than_one(theirPawns & PawnAttacks[Us][pop_lsb(&b)])) + e->passedPawns[Us] |= s; + } - else if (backward) - score -= Backward[opposed]; + // Score this pawn + if (supported | phalanx) + score += Connected[opposed][bool(phalanx)][popcount(supported)][relative_rank(Us, s)]; - else if (!supported) - score -= Unsupported; + else if (!neighbours) + score -= Isolated, e->weakUnopposed[Us] += !opposed; - if (connected) - score += Connected[opposed][!!phalanx][more_than_one(supported)][relative_rank(Us, s)]; + else if (backward) + score -= Backward, e->weakUnopposed[Us] += !opposed; - if (doubled) + if (doubled && !supported) score -= Doubled; - - if (lever) - score += Lever[relative_rank(Us, s)]; } return score; @@ -187,16 +193,17 @@ namespace Pawns { void init() { - static const int Seed[RANK_NB] = { 0, 8, 19, 13, 71, 94, 169, 324 }; + static const int Seed[RANK_NB] = { 0, 13, 24, 18, 76, 100, 175, 330 }; for (int opposed = 0; opposed <= 1; ++opposed) for (int phalanx = 0; phalanx <= 1; ++phalanx) - for (int apex = 0; apex <= 1; ++apex) + for (int support = 0; support <= 2; ++support) for (Rank r = RANK_2; r < RANK_8; ++r) { - int v = (Seed[r] + (phalanx ? (Seed[r + 1] - Seed[r]) / 2 : 0)) >> opposed; - v += (apex ? v / 2 : 0); - Connected[opposed][phalanx][apex][r] = make_score(v, v * 5 / 8); + int v = 17 * support; + v += (Seed[r] + (phalanx ? (Seed[r + 1] - Seed[r]) / 2 : 0)) >> opposed; + + Connected[opposed][phalanx][support][r] = make_score(v, v * (r - 2) / 4); } } @@ -223,35 +230,36 @@ Entry* probe(const Position& pos) { /// Entry::shelter_storm() calculates shelter and storm penalties for the file -/// the king is on, as well as the two adjacent files. +/// the king is on, as well as the two closest files. template Value Entry::shelter_storm(const Position& pos, Square ksq) { const Color Them = (Us == WHITE ? BLACK : WHITE); - enum { NoFriendlyPawn, Unblocked, BlockedByPawn, BlockedByKing }; + enum { BlockedByKing, Unopposed, BlockedByPawn, Unblocked }; - Bitboard b = pos.pieces(PAWN) & (in_front_bb(Us, rank_of(ksq)) | rank_bb(ksq)); + Bitboard b = pos.pieces(PAWN) & (forward_ranks_bb(Us, ksq) | rank_bb(ksq)); Bitboard ourPawns = b & pos.pieces(Us); Bitboard theirPawns = b & pos.pieces(Them); Value safety = MaxSafetyBonus; File center = std::max(FILE_B, std::min(FILE_G, file_of(ksq))); - for (File f = center - File(1); f <= center + File(1); ++f) + for (File f = File(center - 1); f <= File(center + 1); ++f) { b = ourPawns & file_bb(f); Rank rkUs = b ? relative_rank(Us, backmost_sq(Us, b)) : RANK_1; - b = theirPawns & file_bb(f); + b = theirPawns & file_bb(f); Rank rkThem = b ? relative_rank(Us, frontmost_sq(Them, b)) : RANK_1; - safety -= ShelterWeakness[std::min(f, FILE_H - f)][rkUs] + int d = std::min(f, ~f); + safety -= ShelterWeakness[f == file_of(ksq)][d][rkUs] + StormDanger [f == file_of(ksq) && rkThem == relative_rank(Us, ksq) + 1 ? BlockedByKing : - rkUs == RANK_1 ? NoFriendlyPawn : + rkUs == RANK_1 ? Unopposed : rkThem == rkUs + 1 ? BlockedByPawn : Unblocked] - [std::min(f, FILE_H - f)][rkThem]; + [d][rkThem]; } return safety; diff --git a/Engines/Linux64/stockfish/src/pawns.h b/Engines/Linux64/stockfish/src/pawns.h index e26ae67..a9c21ff 100644 --- a/Engines/Linux64/stockfish/src/pawns.h +++ b/Engines/Linux64/stockfish/src/pawns.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -37,6 +37,7 @@ struct Entry { Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } Bitboard passed_pawns(Color c) const { return passedPawns[c]; } Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; } + int weak_unopposed(Color c) const { return weakUnopposed[c]; } int pawn_asymmetry() const { return asymmetry; } int open_files() const { return openFiles; } @@ -49,7 +50,7 @@ struct Entry { } int pawns_on_same_color_squares(Color c, Square s) const { - return pawnsOnSquares[c][!!(DarkSquares & s)]; + return pawnsOnSquares[c][bool(DarkSquares & s)]; } template @@ -71,6 +72,7 @@ struct Entry { Bitboard pawnAttacksSpan[COLOR_NB]; Square kingSquares[COLOR_NB]; Score kingSafety[COLOR_NB]; + int weakUnopposed[COLOR_NB]; int castlingRights[COLOR_NB]; int semiopenFiles[COLOR_NB]; int pawnsOnSquares[COLOR_NB][COLOR_NB]; // [color][light/dark squares] diff --git a/Engines/Linux64/stockfish/src/position.cpp b/Engines/Linux64/stockfish/src/position.cpp index c09a953..7e12fdc 100644 --- a/Engines/Linux64/stockfish/src/position.cpp +++ b/Engines/Linux64/stockfish/src/position.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,6 +32,7 @@ #include "thread.h" #include "tt.h" #include "uci.h" +#include "syzygy/tbprobe.h" using std::string; @@ -44,14 +45,17 @@ namespace Zobrist { Key psq[PIECE_NB][SQUARE_NB]; Key enpassant[FILE_NB]; Key castling[CASTLING_RIGHT_NB]; - Key side; + Key side, noPawns; } namespace { const string PieceToChar(" PNBRQK pnbrqk"); -// min_attacker() is a helper function used by see() to locate the least +const Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; + +// min_attacker() is a helper function used by see_ge() to locate the least // valuable attacker for the side to move, remove the attacker we just found // from the bitboards and scan for new X-ray attacks behind it. @@ -61,7 +65,7 @@ PieceType min_attacker(const Bitboard* bb, Square to, Bitboard stmAttackers, Bitboard b = stmAttackers & bb[Pt]; if (!b) - return min_attacker(bb, to, stmAttackers, occupied, attackers); + return min_attacker(bb, to, stmAttackers, occupied, attackers); occupied ^= b & ~(b - 1); @@ -98,11 +102,25 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { } os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase - << std::setfill('0') << std::setw(16) << pos.key() << std::dec << "\nCheckers: "; + << std::setfill('0') << std::setw(16) << pos.key() + << std::setfill(' ') << std::dec << "\nCheckers: "; for (Bitboard b = pos.checkers(); b; ) os << UCI::square(pop_lsb(&b)) << " "; + if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) + && !pos.can_castle(ANY_CASTLING)) + { + StateInfo st; + Position p; + p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); + Tablebases::ProbeState s1, s2; + Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); + int dtz = Tablebases::probe_dtz(p, &s2); + os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" + << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; + } + return os; } @@ -133,6 +151,7 @@ void Position::init() { } Zobrist::side = rng.rand(); + Zobrist::noPawns = rng.rand(); } @@ -164,8 +183,9 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th 4) En passant target square (in algebraic notation). If there's no en passant target square, this is "-". If a pawn has just made a 2-square move, this - is the position "behind" the pawn. This is recorded regardless of whether - there is a pawn in position to make an en passant capture. + is the position "behind" the pawn. This is recorded only if there is a pawn + in position to make an en passant capture, and if there really is a pawn + that might have advanced two squares. 5) Halfmove clock. This is the number of halfmoves since the last pawn advance or capture. This is used to determine if a draw can be claimed under the @@ -191,10 +211,10 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th while ((ss >> token) && !isspace(token)) { if (isdigit(token)) - sq += Square(token - '0'); // Advance the given number of files + sq += (token - '0') * EAST; // Advance the given number of files else if (token == '/') - sq -= Square(16); + sq += 2 * SOUTH; else if ((idx = PieceToChar.find(token)) != string::npos) { @@ -242,7 +262,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th { st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); - if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))) + if ( !(attackers_to(st->epSquare) & pieces(sideToMove, PAWN)) + || !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))) st->epSquare = SQ_NONE; } else @@ -251,7 +272,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th // 5-6. Halfmove clock and fullmove number ss >> std::skipws >> st->rule50 >> gamePly; - // Convert from fullmove starting from 1 to ply starting from 0, + // Convert from fullmove starting from 1 to gamePly starting from 0, // handle also common incorrect FEN with fullmove = 0. gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); @@ -317,7 +338,8 @@ void Position::set_check_info(StateInfo* si) const { void Position::set_state(StateInfo* si) const { - si->key = si->pawnKey = si->materialKey = 0; + si->key = si->materialKey = 0; + si->pawnKey = Zobrist::noPawns; si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; si->psq = SCORE_ZERO; si->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); @@ -357,6 +379,27 @@ void Position::set_state(StateInfo* si) const { } +/// Position::set() is an overload to initialize the position object with +/// the given endgame code string like "KBPKN". It is mainly a helper to +/// get the material key out of an endgame code. + +Position& Position::set(const string& code, Color c, StateInfo* si) { + + assert(code.length() > 0 && code.length() < 8); + assert(code[0] == 'K'); + + string sides[] = { code.substr(code.find('K', 1)), // Weak + code.substr(0, code.find('K', 1)) }; // Strong + + std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); + + string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; + + return set(fenStr, false, si, nullptr); +} + + /// Position::fen() returns a FEN representation of the position. In case of /// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. @@ -407,19 +450,6 @@ const string Position::fen() const { } -/// Position::game_phase() calculates the game phase interpolating total non-pawn -/// material between endgame and midgame limits. - -Phase Position::game_phase() const { - - Value npm = st->nonPawnMaterial[WHITE] + st->nonPawnMaterial[BLACK]; - - npm = std::max(EndgameLimit, std::min(npm, MidgameLimit)); - - return Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); -} - - /// Position::slider_blockers() returns a bitboard of all the pieces (both colors) /// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a /// slider if removing that piece from the board would result in a position where @@ -433,7 +463,7 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners pinners = 0; // Snipers are sliders that attack 's' when a piece is removed - Bitboard snipers = ( (PseudoAttacks[ROOK ][s] & pieces(QUEEN, ROOK)) + Bitboard snipers = ( (PseudoAttacks[ ROOK][s] & pieces(QUEEN, ROOK)) | (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders; while (snipers) @@ -460,7 +490,7 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return (attacks_from(s, BLACK) & pieces(WHITE, PAWN)) | (attacks_from(s, WHITE) & pieces(BLACK, PAWN)) | (attacks_from(s) & pieces(KNIGHT)) - | (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) + | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN)) | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) | (attacks_from(s) & pieces(KING)); } @@ -554,7 +584,7 @@ bool Position::pseudo_legal(const Move m) const { && empty(to - pawn_push(us)))) return false; } - else if (!(attacks_from(pc, from) & to)) + else if (!(attacks_from(type_of(pc), from) & to)) return false; // Evasions generator already takes care to avoid some kind of illegal moves @@ -607,7 +637,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION: - return attacks_bb(Piece(promotion_type(m)), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); // En passant capture with check? We have already handled the case // of direct checks and ordinary discovered check, so the only case we @@ -647,7 +677,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(is_ok(m)); assert(&newSt != st); - ++nodes; + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); Key k = st->key ^ Zobrist::side; // Copy some fields of the old state to our new StateInfo object except the @@ -757,7 +787,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if ( (int(to) ^ int(from)) == 16 && (attacks_from(to - pawn_push(us), us) & pieces(them, PAWN))) { - st->epSquare = (from + to) / 2; + st->epSquare = to - pawn_push(us); k ^= Zobrist::enpassant[file_of(st->epSquare)]; } @@ -786,7 +816,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update pawn hash key and prefetch access to pawnsTable st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - prefetch(thisThread->pawnsTable[st->pawnKey]); + prefetch2(thisThread->pawnsTable[st->pawnKey]); // Reset rule 50 draw counter st->rule50 = 0; @@ -956,18 +986,16 @@ Key Position::key_after(Move m) const { /// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the -/// SEE value of move is greater or equal to the given value. We'll use an +/// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Value v) const { +bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); - // Castling moves are implemented as king capturing the rook so cannot be - // handled correctly. Simply assume the SEE value is VALUE_ZERO that is always - // correct unless in the rare case the rook ends up under attack. - if (type_of(m) == CASTLING) - return VALUE_ZERO >= v; + // Only deal with normal moves, assume others pass a simple see + if (type_of(m) != NORMAL) + return VALUE_ZERO >= threshold; Square from = from_sq(m), to = to_sq(m); PieceType nextVictim = type_of(piece_on(from)); @@ -975,30 +1003,22 @@ bool Position::see_ge(Move m, Value v) const { Value balance; // Values of the pieces taken by us minus opponent's ones Bitboard occupied, stmAttackers; - if (type_of(m) == ENPASSANT) - { - occupied = SquareBB[to - pawn_push(~stm)]; // Remove the captured pawn - balance = PieceValue[MG][PAWN]; - } - else - { - balance = PieceValue[MG][piece_on(to)]; - occupied = 0; - } + // The opponent may be able to recapture so this is the best result + // we can hope for. + balance = PieceValue[MG][piece_on(to)] - threshold; - if (balance < v) + if (balance < VALUE_ZERO) return false; - if (nextVictim == KING) - return true; - + // Now assume the worst possible result: that the opponent can + // capture our piece for free. balance -= PieceValue[MG][nextVictim]; - if (balance >= v) + if (balance >= VALUE_ZERO) // Always true if nextVictim == KING return true; - bool relativeStm = true; // True if the opponent is to move - occupied ^= pieces() ^ from ^ to; + bool opponentToMove = true; + occupied = pieces() ^ from ^ to; // Find all attackers to the destination square, with the moving piece removed, // but possibly an X-ray attacker added behind it. @@ -1006,6 +1026,12 @@ bool Position::see_ge(Move m, Value v) const { while (true) { + // The balance is negative only because we assumed we could win + // the last piece for free. We are truly winning only if we can + // win the last piece _cheaply enough_. Test if we can actually + // do this otherwise "give up". + assert(balance < VALUE_ZERO); + stmAttackers = attackers & pieces(stm); // Don't allow pinned pieces to attack pieces except the king as long all @@ -1013,43 +1039,68 @@ bool Position::see_ge(Move m, Value v) const { if (!(st->pinnersForKing[stm] & ~occupied)) stmAttackers &= ~st->blockersForKing[stm]; + // If we have no more attackers we must give up if (!stmAttackers) - return relativeStm; + break; // Locate and remove the next least valuable attacker nextVictim = min_attacker(byTypeBB, to, stmAttackers, occupied, attackers); if (nextVictim == KING) - return relativeStm == bool(attackers & pieces(~stm)); - - balance += relativeStm ? PieceValue[MG][nextVictim] - : -PieceValue[MG][nextVictim]; + { + // Our only attacker is the king. If the opponent still has + // attackers we must give up. Otherwise we make the move and + // (having no more attackers) the opponent must give up. + if (!(attackers & pieces(~stm))) + opponentToMove = !opponentToMove; + break; + } - relativeStm = !relativeStm; + // Assume the opponent can win the next piece for free and switch sides + balance += PieceValue[MG][nextVictim]; + opponentToMove = !opponentToMove; - if (relativeStm == (balance >= v)) - return relativeStm; + // If balance is negative after receiving a free piece then give up + if (balance < VALUE_ZERO) + break; + // Complete the process of switching sides. The first line swaps + // all negative numbers with non-negative numbers. The compiler + // probably knows that it is just the bitwise negation ~balance. + balance = -balance-1; stm = ~stm; } + + // If the opponent gave up we win, otherwise we lose. + return opponentToMove; } /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. -bool Position::is_draw() const { +bool Position::is_draw(int ply) const { if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) return true; - StateInfo* stp = st; - for (int i = 2, e = std::min(st->rule50, st->pliesFromNull); i <= e; i += 2) + int end = std::min(st->rule50, st->pliesFromNull); + + if (end < 4) + return false; + + StateInfo* stp = st->previous->previous; + int cnt = 0; + + for (int i = 4; i <= end; i += 2) { stp = stp->previous->previous; - if (stp->key == st->key) - return true; // Draw at first repetition + // Return a draw score if a position repeats once earlier but strictly + // after the root, or repeats twice before or at the root. + if ( stp->key == st->key + && ++cnt + (ply > i) == 2) + return true; } return false; @@ -1091,78 +1142,72 @@ void Position::flip() { } -/// Position::pos_is_ok() performs some consistency checks for the position object. +/// Position::pos_is_ok() performs some consistency checks for the +/// position object and raises an asserts if something wrong is detected. /// This is meant to be helpful when debugging. -bool Position::pos_is_ok(int* failedStep) const { +bool Position::pos_is_ok() const { const bool Fast = true; // Quick (default) or full check? - enum { Default, King, Bitboards, State, Lists, Castling }; + if ( (sideToMove != WHITE && sideToMove != BLACK) + || piece_on(square(WHITE)) != W_KING + || piece_on(square(BLACK)) != B_KING + || ( ep_square() != SQ_NONE + && relative_rank(sideToMove, ep_square()) != RANK_6)) + assert(0 && "pos_is_ok: Default"); - for (int step = Default; step <= (Fast ? Default : Castling); step++) - { - if (failedStep) - *failedStep = step; - - if (step == Default) - if ( (sideToMove != WHITE && sideToMove != BLACK) - || piece_on(square(WHITE)) != W_KING - || piece_on(square(BLACK)) != B_KING - || ( ep_square() != SQ_NONE - && relative_rank(sideToMove, ep_square()) != RANK_6)) - return false; + if (Fast) + return true; - if (step == King) - if ( std::count(board, board + SQUARE_NB, W_KING) != 1 - || std::count(board, board + SQUARE_NB, B_KING) != 1 - || attackers_to(square(~sideToMove)) & pieces(sideToMove)) - return false; + if ( pieceCount[W_KING] != 1 + || pieceCount[B_KING] != 1 + || attackers_to(square(~sideToMove)) & pieces(sideToMove)) + assert(0 && "pos_is_ok: Kings"); - if (step == Bitboards) - { - if ( (pieces(WHITE) & pieces(BLACK)) - ||(pieces(WHITE) | pieces(BLACK)) != pieces()) - return false; + if ( (pieces(PAWN) & (Rank1BB | Rank8BB)) + || pieceCount[W_PAWN] > 8 + || pieceCount[B_PAWN] > 8) + assert(0 && "pos_is_ok: Pawns"); - for (PieceType p1 = PAWN; p1 <= KING; ++p1) - for (PieceType p2 = PAWN; p2 <= KING; ++p2) - if (p1 != p2 && (pieces(p1) & pieces(p2))) - return false; - } + if ( (pieces(WHITE) & pieces(BLACK)) + || (pieces(WHITE) | pieces(BLACK)) != pieces() + || popcount(pieces(WHITE)) > 16 + || popcount(pieces(BLACK)) > 16) + assert(0 && "pos_is_ok: Bitboards"); - if (step == State) - { - StateInfo si = *st; - set_state(&si); - if (std::memcmp(&si, st, sizeof(StateInfo))) - return false; - } + for (PieceType p1 = PAWN; p1 <= KING; ++p1) + for (PieceType p2 = PAWN; p2 <= KING; ++p2) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + assert(0 && "pos_is_ok: Bitboards"); - if (step == Lists) - for (Piece pc : Pieces) - { - if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))) - return false; + StateInfo si = *st; + set_state(&si); + if (std::memcmp(&si, st, sizeof(StateInfo))) + assert(0 && "pos_is_ok: State"); - for (int i = 0; i < pieceCount[pc]; ++i) - if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i) - return false; - } + for (Piece pc : Pieces) + { + if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) + || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + assert(0 && "pos_is_ok: Pieces"); - if (step == Castling) - for (Color c = WHITE; c <= BLACK; ++c) - for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1)) - { - if (!can_castle(c | s)) - continue; - - if ( piece_on(castlingRookSquare[c | s]) != make_piece(c, ROOK) - || castlingRightsMask[castlingRookSquare[c | s]] != (c | s) - ||(castlingRightsMask[square(c)] & (c | s)) != (c | s)) - return false; - } + for (int i = 0; i < pieceCount[pc]; ++i) + if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i) + assert(0 && "pos_is_ok: Index"); } + for (Color c = WHITE; c <= BLACK; ++c) + for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1)) + { + if (!can_castle(c | s)) + continue; + + if ( piece_on(castlingRookSquare[c | s]) != make_piece(c, ROOK) + || castlingRightsMask[castlingRookSquare[c | s]] != (c | s) + || (castlingRightsMask[square(c)] & (c | s)) != (c | s)) + assert(0 && "pos_is_ok: Castling"); + } + return true; } diff --git a/Engines/Linux64/stockfish/src/position.h b/Engines/Linux64/stockfish/src/position.h index 9aa4c44..34e2f7e 100644 --- a/Engines/Linux64/stockfish/src/position.h +++ b/Engines/Linux64/stockfish/src/position.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -56,7 +56,10 @@ struct StateInfo { Bitboard checkSquares[PIECE_TYPE_NB]; }; -// In a std::deque references to elements are unaffected upon resizing +/// A list to keep track of the position states along the setup moves (from the +/// start position to the position just before the search starts). Needed by +/// 'draw by repetition' detection. Use a std::deque because pointers to +/// elements are not invalidated upon list resizing. typedef std::unique_ptr> StateListPtr; @@ -76,6 +79,7 @@ class Position { // FEN string input/output Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); + Position& set(const std::string& code, Color c, StateInfo* si); const std::string fen() const; // Position representation @@ -89,6 +93,7 @@ class Position { Square ep_square() const; bool empty(Square s) const; template int count(Color c) const; + template int count() const; template const Square* squares(Color c) const; template Square square(Color c) const; @@ -107,7 +112,7 @@ class Position { // Attacks to/from a given square Bitboard attackers_to(Square s) const; Bitboard attackers_to(Square s, Bitboard occupied) const; - Bitboard attacks_from(Piece pc, Square s) const; + Bitboard attacks_from(PieceType pt, Square s) const; template Bitboard attacks_from(Square s) const; template Bitboard attacks_from(Square s, Color c) const; Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const; @@ -127,13 +132,14 @@ class Position { bool opposite_bishops() const; // Doing and undoing moves - void do_move(Move m, StateInfo& st, bool givesCheck); + void do_move(Move m, StateInfo& newSt); + void do_move(Move m, StateInfo& newSt, bool givesCheck); void undo_move(Move m); - void do_null_move(StateInfo& st); + void do_null_move(StateInfo& newSt); void undo_null_move(); // Static Exchange Evaluation - bool see_ge(Move m, Value value) const; + bool see_ge(Move m, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; @@ -143,18 +149,17 @@ class Position { // Other properties of the position Color side_to_move() const; - Phase game_phase() const; int game_ply() const; bool is_chess960() const; Thread* this_thread() const; - uint64_t nodes_searched() const; - bool is_draw() const; + bool is_draw(int ply) const; int rule50_count() const; Score psq_score() const; Value non_pawn_material(Color c) const; + Value non_pawn_material() const; // Position consistency check, for debugging - bool pos_is_ok(int* failedStep = nullptr) const; + bool pos_is_ok() const; void flip(); private: @@ -180,7 +185,6 @@ class Position { int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB]; - uint64_t nodes; int gamePly; Color sideToMove; Thread* thisThread; @@ -234,6 +238,10 @@ template inline int Position::count(Color c) const { return pieceCount[make_piece(c, Pt)]; } +template inline int Position::count() const { + return pieceCount[make_piece(WHITE, Pt)] + pieceCount[make_piece(BLACK, Pt)]; +} + template inline const Square* Position::squares(Color c) const { return pieceList[make_piece(c, Pt)]; } @@ -265,18 +273,19 @@ inline Square Position::castling_rook_square(CastlingRight cr) const { template inline Bitboard Position::attacks_from(Square s) const { + assert(Pt != PAWN); return Pt == BISHOP || Pt == ROOK ? attacks_bb(s, byTypeBB[ALL_PIECES]) : Pt == QUEEN ? attacks_from(s) | attacks_from(s) - : StepAttacksBB[Pt][s]; + : PseudoAttacks[Pt][s]; } template<> inline Bitboard Position::attacks_from(Square s, Color c) const { - return StepAttacksBB[make_piece(c, PAWN)][s]; + return PawnAttacks[c][s]; } -inline Bitboard Position::attacks_from(Piece pc, Square s) const { - return attacks_bb(pc, s, byTypeBB[ALL_PIECES]); +inline Bitboard Position::attacks_from(PieceType pt, Square s) const { + return attacks_bb(pt, s, byTypeBB[ALL_PIECES]); } inline Bitboard Position::attackers_to(Square s) const { @@ -328,6 +337,10 @@ inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } +inline Value Position::non_pawn_material() const { + return st->nonPawnMaterial[WHITE] + st->nonPawnMaterial[BLACK]; +} + inline int Position::game_ply() const { return gamePly; } @@ -336,10 +349,6 @@ inline int Position::rule50_count() const { return st->rule50; } -inline uint64_t Position::nodes_searched() const { - return nodes; -} - inline bool Position::opposite_bishops() const { return pieceCount[W_BISHOP] == 1 && pieceCount[B_BISHOP] == 1 @@ -411,4 +420,8 @@ inline void Position::move_piece(Piece pc, Square from, Square to) { pieceList[pc][index[to]] = to; } +inline void Position::do_move(Move m, StateInfo& newSt) { + do_move(m, newSt, gives_check(m)); +} + #endif // #ifndef POSITION_H_INCLUDED diff --git a/Engines/Linux64/stockfish/src/psqt.cpp b/Engines/Linux64/stockfish/src/psqt.cpp index 41a9b49..3f447dc 100644 --- a/Engines/Linux64/stockfish/src/psqt.cpp +++ b/Engines/Linux64/stockfish/src/psqt.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,32 +39,32 @@ const Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { { }, { // Pawn { S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }, - { S(-16, 7), S( 1,-4), S( 7, 8), S( 3,-2) }, - { S(-23,-4), S( -7,-5), S( 19, 5), S(24, 4) }, - { S(-22, 3), S(-14, 3), S( 20,-8), S(35,-3) }, - { S(-11, 8), S( 0, 9), S( 3, 7), S(21,-6) }, - { S(-11, 8), S(-13,-5), S( -6, 2), S(-2, 4) }, - { S( -9, 3), S( 15,-9), S( -8, 1), S(-4,18) } + { S(-11, 7), S( 6,-4), S( 7, 8), S( 3,-2) }, + { S(-18,-4), S( -2,-5), S( 19, 5), S(24, 4) }, + { S(-17, 3), S( -9, 3), S( 20,-8), S(35,-3) }, + { S( -6, 8), S( 5, 9), S( 3, 7), S(21,-6) }, + { S( -6, 8), S( -8,-5), S( -6, 2), S(-2, 4) }, + { S( -4, 3), S( 20,-9), S( -8, 1), S(-4,18) } }, { // Knight - { S(-143, -97), S(-96,-82), S(-80,-46), S(-73,-14) }, - { S( -83, -69), S(-43,-55), S(-21,-17), S(-10, 9) }, - { S( -71, -50), S(-22,-39), S( 0, -8), S( 9, 28) }, - { S( -25, -41), S( 18,-25), S( 43, 7), S( 47, 38) }, - { S( -26, -46), S( 16,-25), S( 38, 2), S( 50, 41) }, - { S( -11, -55), S( 37,-38), S( 56, -8), S( 71, 27) }, - { S( -62, -64), S(-17,-50), S( 5,-24), S( 14, 13) }, - { S(-195,-110), S(-66,-90), S(-42,-50), S(-29,-13) } + { S(-161,-105), S(-96,-82), S(-80,-46), S(-73,-14) }, + { S( -83, -69), S(-43,-54), S(-21,-17), S(-10, 9) }, + { S( -71, -50), S(-22,-39), S( 0, -7), S( 9, 28) }, + { S( -25, -41), S( 18,-25), S( 43, 6), S( 47, 38) }, + { S( -26, -46), S( 16,-25), S( 38, 3), S( 50, 40) }, + { S( -11, -54), S( 37,-38), S( 56, -7), S( 65, 27) }, + { S( -63, -65), S(-19,-50), S( 5,-24), S( 14, 13) }, + { S(-195,-109), S(-67,-89), S(-42,-50), S(-29,-13) } }, { // Bishop - { S(-54,-68), S(-23,-40), S(-35,-46), S(-44,-28) }, - { S(-30,-43), S( 10,-17), S( 2,-23), S( -9, -5) }, - { S(-19,-32), S( 17, -9), S( 11,-13), S( 1, 8) }, - { S(-21,-36), S( 18,-13), S( 11,-15), S( 0, 7) }, - { S(-21,-36), S( 14,-14), S( 6,-17), S( -1, 3) }, - { S(-27,-35), S( 6,-13), S( 2,-10), S( -8, 1) }, - { S(-33,-44), S( 7,-21), S( -4,-22), S(-12, -4) }, - { S(-45,-65), S(-21,-42), S(-29,-46), S(-39,-27) } + { S(-44,-58), S(-13,-31), S(-25,-37), S(-34,-19) }, + { S(-20,-34), S( 20, -9), S( 12,-14), S( 1, 4) }, + { S( -9,-23), S( 27, 0), S( 21, -3), S( 11, 16) }, + { S(-11,-26), S( 28, -3), S( 21, -5), S( 10, 16) }, + { S(-11,-26), S( 27, -4), S( 16, -7), S( 9, 14) }, + { S(-17,-24), S( 16, -2), S( 12, 0), S( 2, 13) }, + { S(-23,-34), S( 17,-10), S( 6,-12), S( -2, 6) }, + { S(-35,-55), S(-11,-32), S(-19,-36), S(-29,-17) } }, { // Rook { S(-25, 0), S(-16, 0), S(-16, 0), S(-9, 0) }, @@ -77,24 +77,24 @@ const Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { { S(-23, 0), S(-15, 0), S(-11, 0), S(-5, 0) } }, { // Queen - { S( 0,-70), S(-3,-57), S(-4,-41), S(-1,-29) }, - { S(-4,-58), S( 6,-30), S( 9,-21), S( 8, -4) }, - { S(-2,-39), S( 6,-17), S( 9, -7), S( 9, 5) }, - { S(-1,-29), S( 8, -5), S(10, 9), S( 7, 17) }, - { S(-3,-27), S( 9, -5), S( 8, 10), S( 7, 23) }, - { S(-2,-40), S( 6,-16), S( 8,-11), S(10, 3) }, - { S(-2,-54), S( 7,-30), S( 7,-21), S( 6, -7) }, - { S(-1,-75), S(-4,-54), S(-1,-44), S( 0,-30) } + { S( 0,-71), S(-4,-56), S(-3,-42), S(-1,-29) }, + { S(-4,-56), S( 6,-30), S( 9,-21), S( 8, -5) }, + { S(-2,-39), S( 6,-17), S( 9, -8), S( 9, 5) }, + { S(-1,-29), S( 8, -5), S(10, 9), S( 7, 19) }, + { S(-3,-27), S( 9, -5), S( 8, 10), S( 7, 21) }, + { S(-2,-40), S( 6,-16), S( 8,-10), S(10, 3) }, + { S(-2,-55), S( 7,-30), S( 7,-21), S( 6, -6) }, + { S(-1,-74), S(-4,-55), S(-1,-43), S( 0,-30) } }, { // King - { S(291, 28), S(344, 76), S(294,103), S(219,112) }, - { S(289, 70), S(329,119), S(263,170), S(205,159) }, - { S(226,109), S(271,164), S(202,195), S(136,191) }, - { S(204,131), S(212,194), S(175,194), S(137,204) }, - { S(177,132), S(205,187), S(143,224), S( 94,227) }, - { S(147,118), S(188,178), S(113,199), S( 70,197) }, - { S(116, 72), S(158,121), S( 93,142), S( 48,161) }, - { S( 94, 30), S(120, 76), S( 78,101), S( 31,111) } + { S(267, 0), S(320, 48), S(270, 75), S(195, 84) }, + { S(264, 43), S(304, 92), S(238,143), S(180,132) }, + { S(200, 83), S(245,138), S(176,167), S(110,165) }, + { S(177,106), S(185,169), S(148,169), S(110,179) }, + { S(149,108), S(177,163), S(115,200), S( 66,203) }, + { S(118, 95), S(159,155), S( 84,176), S( 41,174) }, + { S( 87, 50), S(128, 99), S( 63,122), S( 20,139) }, + { S( 63, 9), S( 88, 55), S( 47, 80), S( 0, 90) } } }; @@ -116,7 +116,7 @@ void init() { for (Square s = SQ_A1; s <= SQ_H8; ++s) { - File f = std::min(file_of(s), FILE_H - file_of(s)); + File f = std::min(file_of(s), ~file_of(s)); psq[ pc][ s] = v + Bonus[pc][rank_of(s)][f]; psq[~pc][~s] = -psq[pc][s]; } diff --git a/Engines/Linux64/stockfish/src/search.cpp b/Engines/Linux64/stockfish/src/search.cpp index 19749b6..b9ce9a6 100644 --- a/Engines/Linux64/stockfish/src/search.cpp +++ b/Engines/Linux64/stockfish/src/search.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,7 +39,6 @@ namespace Search { - SignalsType Signals; LimitsType Limits; } @@ -63,8 +62,12 @@ namespace { // Different node types, used as a template parameter enum NodeType { NonPV, PV }; + // Sizes and phases of the skip-blocks, used for distributing search depths across the threads + const int skipSize[] = { 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4 }; + const int skipPhase[] = { 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7 }; + // Razoring and futility margin based on depth - const int razor_margin[4] = { 483, 570, 603, 554 }; + const int razor_margin = 600; Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); } // Futility and reductions lookup tables, initialized at startup @@ -75,101 +78,62 @@ namespace { return Reductions[PvNode][i][std::min(d / ONE_PLY, 63)][std::min(mn, 63)] * ONE_PLY; } + // History and stats update bonus, based on depth + int stat_bonus(Depth depth) { + int d = depth / ONE_PLY; + return d > 17 ? 0 : d * d + 2 * d - 2; + } + // Skill structure is used to implement strength limit struct Skill { - Skill(int l) : level(l) {} + explicit Skill(int l) : level(l) {} bool enabled() const { return level < 20; } bool time_to_pick(Depth depth) const { return depth / ONE_PLY == 1 + level; } - Move best_move(size_t multiPV) { return best ? best : pick_best(multiPV); } Move pick_best(size_t multiPV); int level; Move best = MOVE_NONE; }; - // EasyMoveManager structure is used to detect an 'easy move'. When the PV is - // stable across multiple search iterations, we can quickly return the best move. - struct EasyMoveManager { - - void clear() { - stableCnt = 0; - expectedPosKey = 0; - pv[0] = pv[1] = pv[2] = MOVE_NONE; - } - - Move get(Key key) const { - return expectedPosKey == key ? pv[2] : MOVE_NONE; - } - - void update(Position& pos, const std::vector& newPv) { - - assert(newPv.size() >= 3); - - // Keep track of how many times in a row the 3rd ply remains stable - stableCnt = (newPv[2] == pv[2]) ? stableCnt + 1 : 0; - - if (!std::equal(newPv.begin(), newPv.begin() + 3, pv)) - { - std::copy(newPv.begin(), newPv.begin() + 3, pv); - - StateInfo st[2]; - pos.do_move(newPv[0], st[0], pos.gives_check(newPv[0])); - pos.do_move(newPv[1], st[1], pos.gives_check(newPv[1])); - expectedPosKey = pos.key(); - pos.undo_move(newPv[1]); - pos.undo_move(newPv[0]); - } - } - - int stableCnt; - Key expectedPosKey; - Move pv[3]; - }; - - // Set of rows with half bits set to 1 and half to 0. It is used to allocate - // the search depths across the threads. - typedef std::vector Row; - - const Row HalfDensity[] = { - {0, 1}, - {1, 0}, - {0, 0, 1, 1}, - {0, 1, 1, 0}, - {1, 1, 0, 0}, - {1, 0, 0, 1}, - {0, 0, 0, 1, 1, 1}, - {0, 0, 1, 1, 1, 0}, - {0, 1, 1, 1, 0, 0}, - {1, 1, 1, 0, 0, 0}, - {1, 1, 0, 0, 0, 1}, - {1, 0, 0, 0, 1, 1}, - {0, 0, 0, 0, 1, 1, 1, 1}, - {0, 0, 0, 1, 1, 1, 1, 0}, - {0, 0, 1, 1, 1, 1, 0 ,0}, - {0, 1, 1, 1, 1, 0, 0 ,0}, - {1, 1, 1, 1, 0, 0, 0 ,0}, - {1, 1, 1, 0, 0, 0, 0 ,1}, - {1, 1, 0, 0, 0, 0, 1 ,1}, - {1, 0, 0, 0, 0, 1, 1 ,1}, - }; - - const size_t HalfDensitySize = std::extent::value; - - EasyMoveManager EasyMove; - Value DrawValue[COLOR_NB]; - template - Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning); template - Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth); + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = DEPTH_ZERO); Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply); void update_pv(Move* pv, Move move, Move* childPv); - void update_cm_stats(Stack* ss, Piece pc, Square s, Value bonus); - void update_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietsCnt, Value bonus); - void check_time(); + void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); + void update_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietsCnt, int bonus); + void update_capture_stats(const Position& pos, Move move, Move* captures, int captureCnt, int bonus); + bool pv_is_draw(Position& pos); + + // perft() is our utility to verify move generation. All the leaf nodes up + // to the given depth are generated and counted, and the sum is returned. + template + uint64_t perft(Position& pos, Depth depth) { + + StateInfo st; + uint64_t cnt, nodes = 0; + const bool leaf = (depth == 2 * ONE_PLY); + + for (const auto& m : MoveList(pos)) + { + if (Root && depth <= ONE_PLY) + cnt = 1, nodes++; + else + { + pos.do_move(m, st); + cnt = leaf ? MoveList(pos).size() : perft(pos, depth - ONE_PLY); + nodes += cnt; + pos.undo_move(m); + } + if (Root) + sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + } + return nodes; + } } // namespace @@ -182,9 +146,7 @@ void Search::init() { for (int d = 1; d < 64; ++d) for (int mc = 1; mc < 64; ++mc) { - double r = log(d) * log(mc) / 2; - if (r < 0.80) - continue; + double r = log(d) * log(mc) / 1.95; Reductions[NonPV][imp][d][mc] = int(std::round(r)); Reductions[PV][imp][d][mc] = std::max(Reductions[NonPV][imp][d][mc] - 1, 0); @@ -196,74 +158,48 @@ void Search::init() { for (int d = 0; d < 16; ++d) { - FutilityMoveCounts[0][d] = int(2.4 + 0.773 * pow(d + 0.00, 1.8)); - FutilityMoveCounts[1][d] = int(2.9 + 1.045 * pow(d + 0.49, 1.8)); + FutilityMoveCounts[0][d] = int(2.4 + 0.74 * pow(d, 1.78)); + FutilityMoveCounts[1][d] = int(5.0 + 1.00 * pow(d, 2.00)); } } -/// Search::clear() resets search state to zero, to obtain reproducible results +/// Search::clear() resets search state to its initial value void Search::clear() { - TT.clear(); - - for (Thread* th : Threads) - { - th->history.clear(); - th->counterMoves.clear(); - th->fromTo.clear(); - th->counterMoveHistory.clear(); - } - - Threads.main()->previousScore = VALUE_INFINITE; -} - - -/// Search::perft() is our utility to verify move generation. All the leaf nodes -/// up to the given depth are generated and counted, and the sum is returned. -template -uint64_t Search::perft(Position& pos, Depth depth) { - - StateInfo st; - uint64_t cnt, nodes = 0; - const bool leaf = (depth == 2 * ONE_PLY); + Threads.main()->wait_for_search_finished(); - for (const auto& m : MoveList(pos)) - { - if (Root && depth <= ONE_PLY) - cnt = 1, nodes++; - else - { - pos.do_move(m, st, pos.gives_check(m)); - cnt = leaf ? MoveList(pos).size() : perft(pos, depth - ONE_PLY); - nodes += cnt; - pos.undo_move(m); - } - if (Root) - sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; - } - return nodes; + Time.availableNodes = 0; + TT.clear(); + Threads.clear(); } -template uint64_t Search::perft(Position&, Depth); - /// MainThread::search() is called by the main thread when the program receives /// the UCI 'go' command. It searches from the root position and outputs the "bestmove". void MainThread::search() { + if (Limits.perft) + { + nodes = perft(rootPos, Limits.perft * ONE_PLY); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + return; + } + Color us = rootPos.side_to_move(); Time.init(Limits, us, rootPos.game_ply()); + TT.new_search(); int contempt = Options["Contempt"] * PawnValueEg / 100; // From centipawns - DrawValue[ us] = VALUE_DRAW - Value(contempt); - DrawValue[~us] = VALUE_DRAW + Value(contempt); + + Eval::Contempt = (us == WHITE ? make_score(contempt, contempt / 2) + : -make_score(contempt, contempt / 2)); if (rootMoves.empty()) { - rootMoves.push_back(RootMove(MOVE_NONE)); + rootMoves.emplace_back(MOVE_NONE); sync_cout << "info depth 0 score " << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; @@ -277,42 +213,47 @@ void MainThread::search() { Thread::search(); // Let's start searching! } - // When playing in 'nodes as time' mode, subtract the searched nodes from - // the available ones before exiting. - if (Limits.npmsec) - Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); - // When we reach the maximum depth, we can arrive here without a raise of - // Signals.stop. However, if we are pondering or in an infinite search, + // Threads.stop. However, if we are pondering or in an infinite search, // the UCI protocol states that we shouldn't print the best move before the // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here - // until the GUI sends one of those commands (which also raises Signals.stop). - if (!Signals.stop && (Limits.ponder || Limits.infinite)) - { - Signals.stopOnPonderhit = true; - wait(Signals.stop); - } + // until the GUI sends one of those commands (which also raises Threads.stop). + Threads.stopOnPonderhit = true; + + while (!Threads.stop && (Threads.ponder || Limits.infinite)) + {} // Busy wait for a stop or a ponder reset - // Stop the threads if not already stopped - Signals.stop = true; + // Stop the threads if not already stopped (also raise the stop if + // "ponderhit" just reset Threads.ponder). + Threads.stop = true; // Wait until all threads have finished for (Thread* th : Threads) if (th != this) th->wait_for_search_finished(); + // When playing in 'nodes as time' mode, subtract the searched nodes from + // the available ones before exiting. + if (Limits.npmsec) + Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); + // Check if there are threads with a better score than main thread Thread* bestThread = this; - if ( !this->easyMovePlayed - && Options["MultiPV"] == 1 + if ( Options["MultiPV"] == 1 && !Limits.depth && !Skill(Options["Skill Level"]).enabled() && rootMoves[0].pv[0] != MOVE_NONE) { for (Thread* th : Threads) - if ( th->completedDepth > bestThread->completedDepth - && th->rootMoves[0].score > bestThread->rootMoves[0].score) + { + Depth depthDiff = th->completedDepth - bestThread->completedDepth; + Value scoreDiff = th->rootMoves[0].score - bestThread->rootMoves[0].score; + + // Select the thread with the best score, always if it is a mate + if ( scoreDiff > 0 + && (depthDiff >= 0 || th->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY)) bestThread = th; + } } previousScore = bestThread->rootMoves[0].score; @@ -330,30 +271,30 @@ void MainThread::search() { } -// Thread::search() is the main iterative deepening loop. It calls search() -// repeatedly with increasing depth until the allocated thinking time has been -// consumed, the user stops the search, or the maximum search depth is reached. +/// Thread::search() is the main iterative deepening loop. It calls search() +/// repeatedly with increasing depth until the allocated thinking time has been +/// consumed, the user stops the search, or the maximum search depth is reached. void Thread::search() { - Stack stack[MAX_PLY+7], *ss = stack+5; // To allow referencing (ss-5) and (ss+2) + Stack stack[MAX_PLY+7], *ss = stack+4; // To reference from (ss-4) to (ss+2) Value bestValue, alpha, beta, delta; - Move easyMove = MOVE_NONE; + Move lastBestMove = MOVE_NONE; + Depth lastBestMoveDepth = DEPTH_ZERO; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); + double timeReduction = 1.0; - std::memset(ss-5, 0, 8 * sizeof(Stack)); + std::memset(ss-4, 0, 7 * sizeof(Stack)); + for (int i = 4; i > 0; i--) + (ss-i)->contHistory = &this->contHistory[NO_PIECE][0]; // Use as sentinel bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; - completedDepth = DEPTH_ZERO; if (mainThread) { - easyMove = EasyMove.get(rootPos.key()); - EasyMove.clear(); - mainThread->easyMovePlayed = mainThread->failedLow = false; + mainThread->failedLow = false; mainThread->bestMoveChanges = 0; - TT.new_search(); } size_t multiPV = Options["MultiPV"]; @@ -368,16 +309,15 @@ void Thread::search() { // Iterative deepening loop until requested to stop or the target depth is reached while ( (rootDepth += ONE_PLY) < DEPTH_MAX - && !Signals.stop - && (!Limits.depth || Threads.main()->rootDepth / ONE_PLY <= Limits.depth)) + && !Threads.stop + && !(Limits.depth && mainThread && rootDepth / ONE_PLY > Limits.depth)) { - // Set up the new depths for the helper threads skipping on average every - // 2nd ply (using a half-density matrix). - if (!mainThread) + // Distribute search depths across the threads + if (idx) { - const Row& row = HalfDensity[(idx - 1) % HalfDensitySize]; - if (row[(rootDepth / ONE_PLY + rootPos.game_ply()) % row.size()]) - continue; + int i = (idx - 1) % 20; + if (((rootDepth / ONE_PLY + rootPos.game_ply() + skipPhase[i]) / skipSize[i]) % 2) + continue; } // Age out PV variability metric @@ -390,8 +330,11 @@ void Thread::search() { rm.previousScore = rm.score; // MultiPV loop. We perform a full root search for each PV line - for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx) + for (PVIdx = 0; PVIdx < multiPV && !Threads.stop; ++PVIdx) { + // Reset UCI info selDepth for each depth and each PV line + selDepth = 0; + // Reset aspiration window starting size if (rootDepth >= 5 * ONE_PLY) { @@ -405,7 +348,7 @@ void Thread::search() { // high/low anymore. while (true) { - bestValue = ::search(rootPos, ss, alpha, beta, rootDepth, false); + bestValue = ::search(rootPos, ss, alpha, beta, rootDepth, false, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the @@ -415,10 +358,10 @@ void Thread::search() { // search the already searched PV lines are preserved. std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end()); - // If search has been stopped, break immediately. Sorting and + // If search has been stopped, we break immediately. Sorting and // writing PV back to TT is safe because RootMoves is still // valid, although it refers to the previous iteration. - if (Signals.stop) + if (Threads.stop) break; // When failing high/low give some update (without cluttering @@ -439,14 +382,11 @@ void Thread::search() { if (mainThread) { mainThread->failedLow = true; - Signals.stopOnPonderhit = false; + Threads.stopOnPonderhit = false; } } else if (bestValue >= beta) - { - alpha = (alpha + beta) / 2; beta = std::min(bestValue + delta, VALUE_INFINITE); - } else break; @@ -458,16 +398,25 @@ void Thread::search() { // Sort the PV lines searched so far and update the GUI std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1); - if (!mainThread) - continue; - - if (Signals.stop || PVIdx + 1 == multiPV || Time.elapsed() > 3000) + if ( mainThread + && (Threads.stop || PVIdx + 1 == multiPV || Time.elapsed() > 3000)) sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; } - if (!Signals.stop) + if (!Threads.stop) completedDepth = rootDepth; + if (rootMoves[0].pv[0] != lastBestMove) { + lastBestMove = rootMoves[0].pv[0]; + lastBestMoveDepth = rootDepth; + } + + // Have we found a "mate in x"? + if ( Limits.mate + && bestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - bestValue <= 2 * Limits.mate) + Threads.stop = true; + if (!mainThread) continue; @@ -475,62 +424,56 @@ void Thread::search() { if (skill.enabled() && skill.time_to_pick(rootDepth)) skill.pick_best(multiPV); - // Have we found a "mate in x"? - if ( Limits.mate - && bestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - bestValue <= 2 * Limits.mate) - Signals.stop = true; - // Do we have time for the next iteration? Can we stop searching now? if (Limits.use_time_management()) { - if (!Signals.stop && !Signals.stopOnPonderhit) + if (!Threads.stop && !Threads.stopOnPonderhit) { // Stop the search if only one legal move is available, or if all - // of the available time has been used, or if we matched an easyMove - // from the previous search and just did a fast verification. + // of the available time has been used const int F[] = { mainThread->failedLow, bestValue - mainThread->previousScore }; - int improvingFactor = std::max(229, std::min(715, 357 + 119 * F[0] - 6 * F[1])); - double unstablePvFactor = 1 + mainThread->bestMoveChanges; - bool doEasyMove = rootMoves[0].pv[0] == easyMove - && mainThread->bestMoveChanges < 0.03 - && Time.elapsed() > Time.optimum() * 5 / 42; + Color us = rootPos.side_to_move(); + bool thinkHard = bestValue == VALUE_DRAW + && Limits.time[us] - Time.elapsed() > Limits.time[~us] + && ::pv_is_draw(rootPos); + + double unstablePvFactor = 1 + mainThread->bestMoveChanges + thinkHard; + + // if the bestMove is stable over several iterations, reduce time for this move, + // the longer the move has been stable, the more. + // Use part of the gained time from a previous stable move for the current move. + timeReduction = 1; + for (int i : {3, 4, 5}) + if (lastBestMoveDepth * i < completedDepth && !thinkHard) + timeReduction *= 1.3; + unstablePvFactor *= std::pow(mainThread->previousTimeReduction, 0.51) / timeReduction; if ( rootMoves.size() == 1 - || Time.elapsed() > Time.optimum() * unstablePvFactor * improvingFactor / 628 - || (mainThread->easyMovePlayed = doEasyMove, doEasyMove)) + || Time.elapsed() > Time.optimum() * unstablePvFactor * improvingFactor / 628) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". - if (Limits.ponder) - Signals.stopOnPonderhit = true; + if (Threads.ponder) + Threads.stopOnPonderhit = true; else - Signals.stop = true; + Threads.stop = true; } } - - if (rootMoves[0].pv.size() >= 3) - EasyMove.update(rootPos, rootMoves[0].pv); - else - EasyMove.clear(); } } if (!mainThread) return; - // Clear any candidate easy move that wasn't stable for the last search - // iterations; the second condition prevents consecutive fast moves. - if (EasyMove.stableCnt < 6 || mainThread->easyMovePlayed) - EasyMove.clear(); + mainThread->previousTimeReduction = timeReduction; // If skill level is enabled, swap best PV line with the sub-optimal one if (skill.enabled()) - std::swap(rootMoves[0], *std::find(rootMoves.begin(), - rootMoves.end(), skill.best_move(multiPV))); + std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(multiPV))); } @@ -539,10 +482,10 @@ namespace { // search<>() is the main search function for both PV and non-PV nodes template - Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning) { const bool PvNode = NT == PV; - const bool rootNode = PvNode && (ss-1)->ply == 0; + const bool rootNode = PvNode && ss->ply == 0; assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); @@ -550,50 +493,39 @@ namespace { assert(!(PvNode && cutNode)); assert(depth / ONE_PLY * ONE_PLY == depth); - Move pv[MAX_PLY+1], quietsSearched[64]; + Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; StateInfo st; TTEntry* tte; Key posKey; Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; - Value bestValue, value, ttValue, eval, nullValue; + Value bestValue, value, ttValue, eval, maxValue; bool ttHit, inCheck, givesCheck, singularExtensionNode, improving; - bool captureOrPromotion, doFullDepthSearch, moveCountPruning; - Piece moved_piece; - int moveCount, quietCount; + bool captureOrPromotion, doFullDepthSearch, moveCountPruning, skipQuiets, ttCapture, pvExact; + Piece movedPiece; + int moveCount, captureCount, quietCount; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); inCheck = pos.checkers(); - moveCount = quietCount = ss->moveCount = 0; - ss->history = VALUE_ZERO; + moveCount = captureCount = quietCount = ss->moveCount = 0; + ss->statScore = 0; bestValue = -VALUE_INFINITE; - ss->ply = (ss-1)->ply + 1; + maxValue = VALUE_INFINITE; // Check for the available remaining time - if (thisThread->resetCalls.load(std::memory_order_relaxed)) - { - thisThread->resetCalls = false; - thisThread->callsCnt = 0; - } - if (++thisThread->callsCnt > 4096) - { - for (Thread* th : Threads) - th->resetCalls = true; - - check_time(); - } + if (thisThread == Threads.main()) + static_cast(thisThread)->check_time(); - // Used to send selDepth info to GUI - if (PvNode && thisThread->maxPly < ss->ply) - thisThread->maxPly = ss->ply; + // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) + if (PvNode && thisThread->selDepth < ss->ply + 1) + thisThread->selDepth = ss->ply + 1; if (!rootNode) { // Step 2. Check for aborted search and immediate draw - if (Signals.stop.load(std::memory_order_relaxed) || pos.is_draw() || ss->ply >= MAX_PLY) - return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) - : DrawValue[pos.side_to_move()]; + if (Threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) + return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) : VALUE_DRAW; // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply+1), but if alpha is already bigger because @@ -609,16 +541,17 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); + (ss+1)->ply = ss->ply + 1; ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; - ss->counterMoves = nullptr; - (ss+1)->skipEarlyPruning = false; + ss->contHistory = &thisThread->contHistory[NO_PIECE][0]; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + Square prevSq = to_sq((ss-1)->currentMove); // Step 4. Transposition table lookup. We don't want the score of a partial // search to overwrite a previous full search TT value, so we use a different // position key in case of an excluded move. excludedMove = ss->excludedMove; - posKey = pos.key() ^ Key(excludedMove); + posKey = pos.key() ^ Key(excludedMove << 16); // isn't a very good hash tte = TT.probe(posKey, ttHit); ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0] @@ -632,23 +565,24 @@ namespace { && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) : (tte->bound() & BOUND_UPPER))) { - // If ttMove is quiet, update killers, history, counter move on TT hit - if (ttValue >= beta && ttMove) + // If ttMove is quiet, update move sorting heuristics on TT hit + if (ttMove) { - int d = depth / ONE_PLY; - - if (!pos.capture_or_promotion(ttMove)) + if (ttValue >= beta) { - Value bonus = Value(d * d + 2 * d - 2); - update_stats(pos, ss, ttMove, nullptr, 0, bonus); - } + if (!pos.capture_or_promotion(ttMove)) + update_stats(pos, ss, ttMove, nullptr, 0, stat_bonus(depth)); - // Extra penalty for a quiet TT move in previous ply when it gets refuted - if ((ss-1)->moveCount == 1 && !pos.captured_piece()) + // Extra penalty for a quiet TT move in previous ply when it gets refuted + if ((ss-1)->moveCount == 1 && !pos.captured_piece()) + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); + } + // Penalty for a quiet ttMove that fails low + else if (!pos.capture_or_promotion(ttMove)) { - Value penalty = Value(d * d + 4 * d + 1); - Square prevSq = to_sq((ss-1)->currentMove); - update_cm_stats(ss-1, pos.piece_on(prevSq), prevSq, -penalty); + int penalty = -stat_bonus(depth); + thisThread->mainHistory.update(pos.side_to_move(), ttMove, penalty); + update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); } } return ttValue; @@ -657,30 +591,46 @@ namespace { // Step 4a. Tablebase probe if (!rootNode && TB::Cardinality) { - int piecesCnt = pos.count(WHITE) + pos.count(BLACK); + int piecesCount = pos.count(); - if ( piecesCnt <= TB::Cardinality - && (piecesCnt < TB::Cardinality || depth >= TB::ProbeDepth) + if ( piecesCount <= TB::Cardinality + && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) && pos.rule50_count() == 0 && !pos.can_castle(ANY_CASTLING)) { - int found, v = Tablebases::probe_wdl(pos, &found); + TB::ProbeState err; + TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); - if (found) + if (err != TB::ProbeState::FAIL) { - thisThread->tbHits++; + thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); int drawScore = TB::UseRule50 ? 1 : 0; - value = v < -drawScore ? -VALUE_MATE + MAX_PLY + ss->ply - : v > drawScore ? VALUE_MATE - MAX_PLY - ss->ply - : VALUE_DRAW + 2 * v * drawScore; + value = wdl < -drawScore ? -VALUE_MATE + MAX_PLY + ss->ply + 1 + : wdl > drawScore ? VALUE_MATE - MAX_PLY - ss->ply - 1 + : VALUE_DRAW + 2 * wdl * drawScore; + + Bound b = wdl < -drawScore ? BOUND_UPPER + : wdl > drawScore ? BOUND_LOWER : BOUND_EXACT; - tte->save(posKey, value_to_tt(value, ss->ply), BOUND_EXACT, - std::min(DEPTH_MAX - ONE_PLY, depth + 6 * ONE_PLY), - MOVE_NONE, VALUE_NONE, TT.generation()); + if ( b == BOUND_EXACT + || (b == BOUND_LOWER ? value >= beta : value <= alpha)) + { + tte->save(posKey, value_to_tt(value, ss->ply), b, + std::min(DEPTH_MAX - ONE_PLY, depth + 6 * ONE_PLY), + MOVE_NONE, VALUE_NONE, TT.generation()); - return value; + return value; + } + + if (PvNode) + { + if (b == BOUND_LOWER) + bestValue = value, alpha = std::max(alpha, bestValue); + else + maxValue = value; + } } } } @@ -699,9 +649,9 @@ namespace { eval = ss->staticEval = evaluate(pos); // Can ttValue be used as a better position evaluation? - if (ttValue != VALUE_NONE) - if (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)) - eval = ttValue; + if ( ttValue != VALUE_NONE + && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) + eval = ttValue; } else { @@ -713,20 +663,19 @@ namespace { ss->staticEval, TT.generation()); } - if (ss->skipEarlyPruning) + if (skipEarlyPruning || !pos.non_pawn_material(pos.side_to_move())) goto moves_loop; // Step 6. Razoring (skipped when in check) if ( !PvNode && depth < 4 * ONE_PLY - && ttMove == MOVE_NONE - && eval + razor_margin[depth / ONE_PLY] <= alpha) + && eval + razor_margin <= alpha) { if (depth <= ONE_PLY) - return qsearch(pos, ss, alpha, beta, DEPTH_ZERO); + return qsearch(pos, ss, alpha, alpha+1); - Value ralpha = alpha - razor_margin[depth / ONE_PLY]; - Value v = qsearch(pos, ss, ralpha, ralpha+1, DEPTH_ZERO); + Value ralpha = alpha - razor_margin; + Value v = qsearch(pos, ss, ralpha, ralpha+1); if (v <= ralpha) return v; } @@ -735,29 +684,27 @@ namespace { if ( !rootNode && depth < 7 * ONE_PLY && eval - futility_margin(depth) >= beta - && eval < VALUE_KNOWN_WIN // Do not return unproven wins - && pos.non_pawn_material(pos.side_to_move())) + && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; // Step 8. Null move search with verification search (is omitted in PV nodes) if ( !PvNode && eval >= beta - && (ss->staticEval >= beta - 35 * (depth / ONE_PLY - 6) || depth >= 13 * ONE_PLY) - && pos.non_pawn_material(pos.side_to_move())) + && ss->staticEval >= beta - 36 * depth / ONE_PLY + 225 + && (ss->ply >= thisThread->nmp_ply || ss->ply % 2 != thisThread->nmp_odd)) { - ss->currentMove = MOVE_NULL; - ss->counterMoves = nullptr; assert(eval - beta >= 0); // Null move dynamic reduction based on depth and value Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min((eval - beta) / PawnValueMg, 3)) * ONE_PLY; + ss->currentMove = MOVE_NULL; + ss->contHistory = &thisThread->contHistory[NO_PIECE][0]; + pos.do_null_move(st); - (ss+1)->skipEarlyPruning = true; - nullValue = depth-R < ONE_PLY ? -qsearch(pos, ss+1, -beta, -beta+1, DEPTH_ZERO) - : - search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); - (ss+1)->skipEarlyPruning = false; + Value nullValue = depth-R < ONE_PLY ? -qsearch(pos, ss+1, -beta, -beta+1) + : - search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode, true); pos.undo_null_move(); if (nullValue >= beta) @@ -766,14 +713,18 @@ namespace { if (nullValue >= VALUE_MATE_IN_MAX_PLY) nullValue = beta; - if (depth < 12 * ONE_PLY && abs(beta) < VALUE_KNOWN_WIN) + if (abs(beta) < VALUE_KNOWN_WIN && (depth < 12 * ONE_PLY || thisThread->nmp_ply)) return nullValue; // Do verification search at high depths - ss->skipEarlyPruning = true; - Value v = depth-R < ONE_PLY ? qsearch(pos, ss, beta-1, beta, DEPTH_ZERO) - : search(pos, ss, beta-1, beta, depth-R, false); - ss->skipEarlyPruning = false; + // disable null move pruning for side to move for the first part of the remaining search tree + thisThread->nmp_ply = ss->ply + 3 * (depth-R) / 4; + thisThread->nmp_odd = ss->ply % 2; + + Value v = depth-R < ONE_PLY ? qsearch(pos, ss, beta-1, beta) + : search(pos, ss, beta-1, beta, depth-R, false, true); + + thisThread->nmp_odd = thisThread->nmp_ply = 0; if (v >= beta) return nullValue; @@ -788,21 +739,20 @@ namespace { && abs(beta) < VALUE_MATE_IN_MAX_PLY) { Value rbeta = std::min(beta + 200, VALUE_INFINITE); - Depth rdepth = depth - 4 * ONE_PLY; - assert(rdepth >= ONE_PLY); - assert((ss-1)->currentMove != MOVE_NONE); - assert((ss-1)->currentMove != MOVE_NULL); + assert(is_ok((ss-1)->currentMove)); - MovePicker mp(pos, ttMove, rbeta - ss->staticEval); + MovePicker mp(pos, ttMove, rbeta - ss->staticEval, &thisThread->captureHistory); while ((move = mp.next_move()) != MOVE_NONE) if (pos.legal(move)) { ss->currentMove = move; - ss->counterMoves = &thisThread->counterMoveHistory[pos.moved_piece(move)][to_sq(move)]; - pos.do_move(move, st, pos.gives_check(move)); - value = -search(pos, ss+1, -rbeta, -rbeta+1, rdepth, !cutNode); + ss->contHistory = &thisThread->contHistory[pos.moved_piece(move)][to_sq(move)]; + + assert(depth >= 5 * ONE_PLY); + pos.do_move(move, st); + value = -search(pos, ss+1, -rbeta, -rbeta+1, depth - 4 * ONE_PLY, !cutNode, false); pos.undo_move(move); if (value >= rbeta) return value; @@ -815,9 +765,7 @@ namespace { && (PvNode || ss->staticEval + 256 >= beta)) { Depth d = (3 * depth / (4 * ONE_PLY) - 2) * ONE_PLY; - ss->skipEarlyPruning = true; - search(pos, ss, alpha, beta, d, cutNode); - ss->skipEarlyPruning = false; + search(pos, ss, alpha, beta, d, cutNode, true); tte = TT.probe(posKey, ttHit); ttMove = ttHit ? tte->move() : MOVE_NONE; @@ -825,11 +773,10 @@ namespace { moves_loop: // When in check search starts from here - const CounterMoveStats* cmh = (ss-1)->counterMoves; - const CounterMoveStats* fmh = (ss-2)->counterMoves; - const CounterMoveStats* fmh2 = (ss-4)->counterMoves; + const PieceToHistory* contHist[] = { (ss-1)->contHistory, (ss-2)->contHistory, nullptr, (ss-4)->contHistory }; + Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; - MovePicker mp(pos, ttMove, depth, ss); + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, countermove, ss->killers); value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc improving = ss->staticEval >= (ss-2)->staticEval /* || ss->staticEval == VALUE_NONE Already implicit in the previous condition */ @@ -842,10 +789,13 @@ namespace { && !excludedMove // Recursive singular search is not allowed && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3 * ONE_PLY; + skipQuiets = false; + ttCapture = false; + pvExact = PvNode && ttHit && tte->bound() == BOUND_EXACT; // Step 11. Loop through moves // Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move(skipQuiets)) != MOVE_NONE) { assert(is_ok(move)); @@ -871,66 +821,66 @@ namespace { extension = DEPTH_ZERO; captureOrPromotion = pos.capture_or_promotion(move); - moved_piece = pos.moved_piece(move); + movedPiece = pos.moved_piece(move); givesCheck = type_of(move) == NORMAL && !pos.discovered_check_candidates() - ? pos.check_squares(type_of(pos.piece_on(from_sq(move)))) & to_sq(move) + ? pos.check_squares(type_of(movedPiece)) & to_sq(move) : pos.gives_check(move); moveCountPruning = depth < 16 * ONE_PLY && moveCount >= FutilityMoveCounts[improving][depth / ONE_PLY]; - // Step 12. Extend checks - if ( givesCheck - && !moveCountPruning - && pos.see_ge(move, VALUE_ZERO)) - extension = ONE_PLY; + // Step 12. Singular and Gives Check Extensions // Singular extension search. If all moves but one fail low on a search of // (alpha-s, beta-s), and just one fails high on (alpha, beta), then that move // is singular and should be extended. To verify this we do a reduced search // on all the other moves but the ttMove and if the result is lower than - // ttValue minus a margin then we extend the ttMove. + // ttValue minus a margin then we will extend the ttMove. if ( singularExtensionNode && move == ttMove - && !extension && pos.legal(move)) { Value rBeta = std::max(ttValue - 2 * depth / ONE_PLY, -VALUE_MATE); Depth d = (depth / (2 * ONE_PLY)) * ONE_PLY; ss->excludedMove = move; - ss->skipEarlyPruning = true; - value = search(pos, ss, rBeta - 1, rBeta, d, cutNode); - ss->skipEarlyPruning = false; + value = search(pos, ss, rBeta - 1, rBeta, d, cutNode, true); ss->excludedMove = MOVE_NONE; if (value < rBeta) extension = ONE_PLY; } + else if ( givesCheck + && !moveCountPruning + && pos.see_ge(move)) + extension = ONE_PLY; - // Update the current move (this must be done after singular extension search) + // Calculate new depth for this move newDepth = depth - ONE_PLY + extension; // Step 13. Pruning at shallow depth if ( !rootNode + && pos.non_pawn_material(pos.side_to_move()) && bestValue > VALUE_MATED_IN_MAX_PLY) { if ( !captureOrPromotion && !givesCheck - && !pos.advanced_pawn_push(move)) + && (!pos.advanced_pawn_push(move) || pos.non_pawn_material() >= Value(5000))) { // Move count based pruning if (moveCountPruning) + { + skipQuiets = true; continue; + } // Reduced depth of the next LMR search int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), DEPTH_ZERO) / ONE_PLY; // Countermoves based pruning if ( lmrDepth < 3 - && (!cmh || (*cmh )[moved_piece][to_sq(move)] < VALUE_ZERO) - && (!fmh || (*fmh )[moved_piece][to_sq(move)] < VALUE_ZERO) - && (!fmh2 || (*fmh2)[moved_piece][to_sq(move)] < VALUE_ZERO || (cmh && fmh))) + && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold + && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold) continue; // Futility pruning: parent node @@ -944,9 +894,9 @@ namespace { && !pos.see_ge(move, Value(-35 * lmrDepth * lmrDepth))) continue; } - else if ( depth < 7 * ONE_PLY + else if ( depth < 7 * ONE_PLY && !extension - && !pos.see_ge(move, Value(-35 * depth / ONE_PLY * depth / ONE_PLY))) + && !pos.see_ge(move, -PawnValueEg * (depth / ONE_PLY))) continue; } @@ -960,8 +910,12 @@ namespace { continue; } + if (move == ttMove && captureOrPromotion) + ttCapture = true; + + // Update the current move (this must be done after singular extension search) ss->currentMove = move; - ss->counterMoves = &thisThread->counterMoveHistory[moved_piece][to_sq(move)]; + ss->contHistory = &thisThread->contHistory[movedPiece][to_sq(move)]; // Step 14. Make the move pos.do_move(move, st, givesCheck); @@ -978,40 +932,49 @@ namespace { r -= r ? ONE_PLY : DEPTH_ZERO; else { + // Decrease reduction if opponent's move count is high + if ((ss-1)->moveCount > 15) + r -= ONE_PLY; + + // Decrease reduction for exact PV nodes + if (pvExact) + r -= ONE_PLY; + + // Increase reduction if ttMove is a capture + if (ttCapture) + r += ONE_PLY; + // Increase reduction for cut nodes if (cutNode) r += 2 * ONE_PLY; // Decrease reduction for moves that escape a capture. Filter out // castling moves, because they are coded as "king captures rook" and - // hence break make_move(). Also use see() instead of see_sign(), - // because the destination square is empty. - else if ( type_of(move) == NORMAL - && type_of(pos.piece_on(to_sq(move))) != PAWN - && !pos.see_ge(make_move(to_sq(move), from_sq(move)), VALUE_ZERO)) + // hence break make_move(). + else if ( type_of(move) == NORMAL + && !pos.see_ge(make_move(to_sq(move), from_sq(move)))) r -= 2 * ONE_PLY; - ss->history = thisThread->history[moved_piece][to_sq(move)] - + (cmh ? (*cmh )[moved_piece][to_sq(move)] : VALUE_ZERO) - + (fmh ? (*fmh )[moved_piece][to_sq(move)] : VALUE_ZERO) - + (fmh2 ? (*fmh2)[moved_piece][to_sq(move)] : VALUE_ZERO) - + thisThread->fromTo.get(~pos.side_to_move(), move) - - 8000; // Correction factor + ss->statScore = thisThread->mainHistory[~pos.side_to_move()][from_to(move)] + + (*contHist[0])[movedPiece][to_sq(move)] + + (*contHist[1])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] + - 4000; // Decrease/increase reduction by comparing opponent's stat score - if (ss->history > VALUE_ZERO && (ss-1)->history < VALUE_ZERO) + if (ss->statScore >= 0 && (ss-1)->statScore < 0) r -= ONE_PLY; - else if (ss->history < VALUE_ZERO && (ss-1)->history > VALUE_ZERO) + else if ((ss-1)->statScore >= 0 && ss->statScore < 0) r += ONE_PLY; // Decrease/increase reduction for moves with a good/bad history - r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->history / 20000) * ONE_PLY); + r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->statScore / 20000) * ONE_PLY); } Depth d = std::max(newDepth - r, ONE_PLY); - value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); + value = -search(pos, ss+1, -(alpha+1), -alpha, d, true, false); doFullDepthSearch = (value > alpha && d != newDepth); } @@ -1021,9 +984,9 @@ namespace { // Step 16. Full depth search when LMR is skipped or fails high if (doFullDepthSearch) value = newDepth < ONE_PLY ? - givesCheck ? -qsearch(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO) - : -qsearch(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO) - : - search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); + givesCheck ? -qsearch(pos, ss+1, -(alpha+1), -alpha) + : -qsearch(pos, ss+1, -(alpha+1), -alpha) + : - search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode, false); // For PV nodes only, do a full PV search on the first move or after a fail // high (in the latter case search only if value < beta), otherwise let the @@ -1034,9 +997,9 @@ namespace { (ss+1)->pv[0] = MOVE_NONE; value = newDepth < ONE_PLY ? - givesCheck ? -qsearch(pos, ss+1, -beta, -alpha, DEPTH_ZERO) - : -qsearch(pos, ss+1, -beta, -alpha, DEPTH_ZERO) - : - search(pos, ss+1, -beta, -alpha, newDepth, false); + givesCheck ? -qsearch(pos, ss+1, -beta, -alpha) + : -qsearch(pos, ss+1, -beta, -alpha) + : - search(pos, ss+1, -beta, -alpha, newDepth, false, false); } // Step 17. Undo move @@ -1048,7 +1011,7 @@ namespace { // Finished searching the move. If a stop occurred, the return value of // the search cannot be trusted, and we return immediately without // updating best move, PV and TT. - if (Signals.stop.load(std::memory_order_relaxed)) + if (Threads.stop.load(std::memory_order_relaxed)) return VALUE_ZERO; if (rootNode) @@ -1060,6 +1023,7 @@ namespace { if (moveCount == 1 || value > alpha) { rm.score = value; + rm.selDepth = thisThread->selDepth; rm.pv.resize(1); assert((ss+1)->pv); @@ -1074,8 +1038,8 @@ namespace { ++static_cast(thisThread)->bestMoveChanges; } else - // All other moves but the PV are set to the lowest value: this is - // not a problem when sorting because the sort is stable and the + // All other moves but the PV are set to the lowest value: this + // is not a problem when sorting because the sort is stable and the // move position in the list is preserved - just the PV is pushed up. rm.score = -VALUE_INFINITE; } @@ -1086,13 +1050,6 @@ namespace { if (value > alpha) { - // If there is an easy move for this position, clear it if unstable - if ( PvNode - && thisThread == Threads.main() - && EasyMove.get(pos.key()) - && (move != EasyMove.get(pos.key()) || moveCount > 1)) - EasyMove.clear(); - bestMove = move; if (PvNode && !rootNode) // Update pv even in fail-high case @@ -1110,13 +1067,15 @@ namespace { if (!captureOrPromotion && move != bestMove && quietCount < 64) quietsSearched[quietCount++] = move; + else if (captureOrPromotion && move != bestMove && captureCount < 32) + capturesSearched[captureCount++] = move; } // The following condition would detect a stop only after move loop has been // completed. But in this case bestValue is valid because we have fully // searched our subtree, and we can anyhow save the result in TT. /* - if (Signals.stop) + if (Threads.stop) return VALUE_DRAW; */ @@ -1129,41 +1088,33 @@ namespace { if (!moveCount) bestValue = excludedMove ? alpha - : inCheck ? mated_in(ss->ply) : DrawValue[pos.side_to_move()]; + : inCheck ? mated_in(ss->ply) : VALUE_DRAW; else if (bestMove) { - int d = depth / ONE_PLY; - - // Quiet best move: update killers, history and countermoves + // Quiet best move: update move sorting heuristics if (!pos.capture_or_promotion(bestMove)) - { - Value bonus = Value(d * d + 2 * d - 2); - update_stats(pos, ss, bestMove, quietsSearched, quietCount, bonus); - } + update_stats(pos, ss, bestMove, quietsSearched, quietCount, stat_bonus(depth)); + else + update_capture_stats(pos, bestMove, capturesSearched, captureCount, stat_bonus(depth)); // Extra penalty for a quiet TT move in previous ply when it gets refuted if ((ss-1)->moveCount == 1 && !pos.captured_piece()) - { - Value penalty = Value(d * d + 4 * d + 1); - Square prevSq = to_sq((ss-1)->currentMove); - update_cm_stats(ss-1, pos.piece_on(prevSq), prevSq, -penalty); - } + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); } // Bonus for prior countermove that caused the fail low else if ( depth >= 3 * ONE_PLY && !pos.captured_piece() && is_ok((ss-1)->currentMove)) - { - int d = depth / ONE_PLY; - Value bonus = Value(d * d + 2 * d - 2); - Square prevSq = to_sq((ss-1)->currentMove); - update_cm_stats(ss-1, pos.piece_on(prevSq), prevSq, bonus); - } + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth)); - tte->save(posKey, value_to_tt(bestValue, ss->ply), - bestValue >= beta ? BOUND_LOWER : - PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, ss->staticEval, TT.generation()); + if (PvNode) + bestValue = std::min(bestValue, maxValue); + + if (!excludedMove) + tte->save(posKey, value_to_tt(bestValue, ss->ply), + bestValue >= beta ? BOUND_LOWER : + PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, + depth, bestMove, ss->staticEval, TT.generation()); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1172,15 +1123,14 @@ namespace { // qsearch() is the quiescence search function, which is called by the main - // search function when the remaining depth is zero (or, to be more precise, - // less than ONE_PLY). + // search function with depth zero, or recursively with depth less than ONE_PLY. template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { const bool PvNode = NT == PV; - assert(InCheck == !!pos.checkers()); + assert(InCheck == bool(pos.checkers())); assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(depth <= DEPTH_ZERO); @@ -1194,6 +1144,7 @@ namespace { Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; bool ttHit, givesCheck, evasionPrunable; Depth ttDepth; + int moveCount; if (PvNode) { @@ -1203,12 +1154,12 @@ namespace { } ss->currentMove = bestMove = MOVE_NONE; - ss->ply = (ss-1)->ply + 1; + (ss+1)->ply = ss->ply + 1; + moveCount = 0; // Check for an instant draw or if the maximum ply has been reached - if (pos.is_draw() || ss->ply >= MAX_PLY) - return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) - : DrawValue[pos.side_to_move()]; + if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) + return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1217,7 +1168,6 @@ namespace { // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. ttDepth = InCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; - // Transposition table lookup posKey = pos.key(); tte = TT.probe(posKey, ttHit); @@ -1247,9 +1197,9 @@ namespace { ss->staticEval = bestValue = evaluate(pos); // Can ttValue be used as a better position evaluation? - if (ttValue != VALUE_NONE) - if (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)) - bestValue = ttValue; + if ( ttValue != VALUE_NONE + && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) + bestValue = ttValue; } else ss->staticEval = bestValue = @@ -1260,7 +1210,7 @@ namespace { if (bestValue >= beta) { if (!ttHit) - tte->save(pos.key(), value_to_tt(bestValue, ss->ply), BOUND_LOWER, + tte->save(posKey, value_to_tt(bestValue, ss->ply), BOUND_LOWER, DEPTH_NONE, MOVE_NONE, ss->staticEval, TT.generation()); return bestValue; @@ -1276,7 +1226,7 @@ namespace { // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will // be generated. - MovePicker mp(pos, ttMove, depth, to_sq((ss-1)->currentMove)); + MovePicker mp(pos, ttMove, depth, &pos.this_thread()->mainHistory, &pos.this_thread()->captureHistory, to_sq((ss-1)->currentMove)); // Loop through the moves until no moves remain or a beta cutoff occurs while ((move = mp.next_move()) != MOVE_NONE) @@ -1284,9 +1234,11 @@ namespace { assert(is_ok(move)); givesCheck = type_of(move) == NORMAL && !pos.discovered_check_candidates() - ? pos.check_squares(type_of(pos.piece_on(from_sq(move)))) & to_sq(move) + ? pos.check_squares(type_of(pos.moved_piece(move))) & to_sq(move) : pos.gives_check(move); + moveCount++; + // Futility pruning if ( !InCheck && !givesCheck @@ -1312,13 +1264,13 @@ namespace { // Detect non-capture evasions that are candidates to be pruned evasionPrunable = InCheck + && (depth != DEPTH_ZERO || moveCount > 2) && bestValue > VALUE_MATED_IN_MAX_PLY && !pos.capture(move); // Don't search moves with negative SEE values if ( (!InCheck || evasionPrunable) - && type_of(move) != PROMOTION - && !pos.see_ge(move, VALUE_ZERO)) + && !pos.see_ge(move)) continue; // Speculative prefetch as early as possible @@ -1326,7 +1278,10 @@ namespace { // Check for legality just before making the move if (!pos.legal(move)) + { + moveCount--; continue; + } ss->currentMove = move; @@ -1414,30 +1369,41 @@ namespace { } - // update_cm_stats() updates countermove and follow-up move history + // update_continuation_histories() updates histories of the move pairs formed + // by moves at ply -1, -2, and -4 with current move. + + void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { + + for (int i : {1, 2, 4}) + if (is_ok((ss-i)->currentMove)) + (ss-i)->contHistory->update(pc, to, bonus); + } - void update_cm_stats(Stack* ss, Piece pc, Square s, Value bonus) { - CounterMoveStats* cmh = (ss-1)->counterMoves; - CounterMoveStats* fmh1 = (ss-2)->counterMoves; - CounterMoveStats* fmh2 = (ss-4)->counterMoves; + // update_capture_stats() updates move sorting heuristics when a new capture best move is found - if (cmh) - cmh->update(pc, s, bonus); + void update_capture_stats(const Position& pos, Move move, + Move* captures, int captureCnt, int bonus) { - if (fmh1) - fmh1->update(pc, s, bonus); + CapturePieceToHistory& captureHistory = pos.this_thread()->captureHistory; + Piece moved_piece = pos.moved_piece(move); + PieceType captured = type_of(pos.piece_on(to_sq(move))); + captureHistory.update(moved_piece, to_sq(move), captured, bonus); - if (fmh2) - fmh2->update(pc, s, bonus); + // Decrease all the other played capture moves + for (int i = 0; i < captureCnt; ++i) + { + moved_piece = pos.moved_piece(captures[i]); + captured = type_of(pos.piece_on(to_sq(captures[i]))); + captureHistory.update(moved_piece, to_sq(captures[i]), captured, -bonus); + } } - // update_stats() updates killers, history, countermove and countermove plus - // follow-up move history when a new quiet best move is found. + // update_stats() updates move sorting heuristics when a new quiet best move is found void update_stats(const Position& pos, Stack* ss, Move move, - Move* quiets, int quietsCnt, Value bonus) { + Move* quiets, int quietsCnt, int bonus) { if (ss->killers[0] != move) { @@ -1447,26 +1413,42 @@ namespace { Color c = pos.side_to_move(); Thread* thisThread = pos.this_thread(); - thisThread->fromTo.update(c, move, bonus); - thisThread->history.update(pos.moved_piece(move), to_sq(move), bonus); - update_cm_stats(ss, pos.moved_piece(move), to_sq(move), bonus); + thisThread->mainHistory.update(c, move, bonus); + update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); - if ((ss-1)->counterMoves) + if (is_ok((ss-1)->currentMove)) { Square prevSq = to_sq((ss-1)->currentMove); - thisThread->counterMoves.update(pos.piece_on(prevSq), prevSq, move); + thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; } // Decrease all the other played quiet moves for (int i = 0; i < quietsCnt; ++i) { - thisThread->fromTo.update(c, quiets[i], -bonus); - thisThread->history.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); - update_cm_stats(ss, pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); + thisThread->mainHistory.update(c, quiets[i], -bonus); + update_continuation_histories(ss, pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); } } + // Is the PV leading to a draw position? Assumes all pv moves are legal + bool pv_is_draw(Position& pos) { + + StateInfo st[MAX_PLY]; + auto& pv = pos.this_thread()->rootMoves[0].pv; + + for (size_t i = 0; i < pv.size(); ++i) + pos.do_move(pv[i], st[i]); + + bool isDraw = pos.is_draw(pv.size()); + + for (size_t i = pv.size(); i > 0; --i) + pos.undo_move(pv[i-1]); + + return isDraw; + } + + // When playing with strength handicap, choose best move among a set of RootMoves // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. @@ -1490,7 +1472,7 @@ namespace { int push = ( weakness * int(topScore - rootMoves[i].score) + delta * (rng.rand() % weakness)) / 128; - if (rootMoves[i].score + push > maxScore) + if (rootMoves[i].score + push >= maxScore) { maxScore = rootMoves[i].score + push; best = rootMoves[i].pv[0]; @@ -1500,11 +1482,19 @@ namespace { return best; } +} // namespace // check_time() is used to print debug info and, more importantly, to detect // when we are out of available time and thus stop the search. - void check_time() { + void MainThread::check_time() { + + if (--callsCnt > 0) + return; + + // At low node count increase the checking rate to about 0.1% of nodes + // otherwise use a default value. + callsCnt = Limits.nodes ? std::min(4096, int(Limits.nodes / 1024)) : 4096; static TimePoint lastInfoTime = now(); @@ -1518,17 +1508,15 @@ namespace { } // An engine may not stop pondering until told so by the GUI - if (Limits.ponder) + if (Threads.ponder) return; if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10) || (Limits.movetime && elapsed >= Limits.movetime) || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) - Signals.stop = true; + Threads.stop = true; } -} // namespace - /// UCI::pv() formats PV information according to the UCI protocol. UCI requires /// that all (if any) unsearched PV lines are sent using a previous search score. @@ -1545,7 +1533,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { for (size_t i = 0; i < multiPV; ++i) { - bool updated = (i <= PVIdx); + bool updated = (i <= PVIdx && rootMoves[i].score != -VALUE_INFINITE); if (depth == ONE_PLY && !updated) continue; @@ -1561,7 +1549,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { ss << "info" << " depth " << d / ONE_PLY - << " seldepth " << pos.this_thread()->maxPly + << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 << " score " << UCI::value(v); @@ -1601,7 +1589,7 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { if (!pv[0]) return false; - pos.do_move(pv[0], st, pos.gives_check(pv[0])); + pos.do_move(pv[0], st); TTEntry* tte = TT.probe(pos.key(), ttHit); if (ttHit) @@ -1632,6 +1620,10 @@ void Tablebases::filter_root_moves(Position& pos, Search::RootMoves& rootMoves) if (Cardinality < popcount(pos.pieces()) || pos.can_castle(ANY_CASTLING)) return; + // Don't filter any moves if the user requested analysis on multiple + if (Options["MultiPV"] != 1) + return; + // If the current root position is in the tablebases, then RootMoves // contains only moves that preserve the draw or the win. RootInTB = root_probe(pos, rootMoves, TB::Score); @@ -1653,4 +1645,9 @@ void Tablebases::filter_root_moves(Position& pos, Search::RootMoves& rootMoves) TB::Score = TB::Score > VALUE_DRAW ? VALUE_MATE - MAX_PLY - 1 : TB::Score < VALUE_DRAW ? -VALUE_MATE + MAX_PLY + 1 : VALUE_DRAW; + + // Since root_probe() and root_probe_wdl() dirty the root move scores, + // we reset them to -VALUE_INFINITE + for (RootMove& rm : rootMoves) + rm.score = -VALUE_INFINITE; } diff --git a/Engines/Linux64/stockfish/src/search.h b/Engines/Linux64/stockfish/src/search.h index d8051ec..1274b96 100644 --- a/Engines/Linux64/stockfish/src/search.h +++ b/Engines/Linux64/stockfish/src/search.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED -#include #include #include "misc.h" @@ -32,21 +31,24 @@ class Position; namespace Search { +/// Threshold used for countermoves based pruning +const int CounterMovePruneThreshold = 0; + + /// Stack struct keeps track of the information we need to remember from nodes /// shallower and deeper in the tree during the search. Each search thread has /// its own array of Stack objects, indexed by the current ply. struct Stack { Move* pv; + PieceToHistory* contHistory; int ply; Move currentMove; Move excludedMove; Move killers[2]; Value staticEval; - Value history; - bool skipEarlyPruning; + int statScore; int moveCount; - CounterMoveStats* counterMoves; }; @@ -57,13 +59,16 @@ struct Stack { struct RootMove { explicit RootMove(Move m) : pv(1, m) {} - - bool operator<(const RootMove& m) const { return m.score < score; } // Descending sort - bool operator==(const Move& m) const { return pv[0] == m; } bool extract_ponder_from_tt(Position& pos); + bool operator==(const Move& m) const { return pv[0] == m; } + bool operator<(const RootMove& m) const { // Sort in descending order + return m.score != score ? m.score < score + : m.previousScore < previousScore; + } Value score = -VALUE_INFINITE; Value previousScore = -VALUE_INFINITE; + int selDepth = 0; std::vector pv; }; @@ -71,40 +76,30 @@ typedef std::vector RootMoves; /// LimitsType struct stores information sent by GUI about available time to -/// search the current move, maximum depth/time, if we are in analysis mode or -/// if we have to ponder while it's our opponent's turn to move. +/// search the current move, maximum depth/time, or if we are in analysis mode. struct LimitsType { LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = - npmsec = movestogo = depth = movetime = mate = infinite = ponder = 0; + npmsec = movestogo = depth = movetime = mate = perft = infinite = 0; } bool use_time_management() const { - return !(mate | movetime | depth | nodes | infinite); + return !(mate | movetime | depth | nodes | perft | infinite); } std::vector searchmoves; - int time[COLOR_NB], inc[COLOR_NB], npmsec, movestogo, depth, movetime, mate, infinite, ponder; + int time[COLOR_NB], inc[COLOR_NB], npmsec, movestogo, depth, + movetime, mate, perft, infinite; int64_t nodes; TimePoint startTime; }; - -/// SignalsType struct stores atomic flags updated during the search, typically -/// in an async fashion e.g. to stop the search by the GUI. - -struct SignalsType { - std::atomic_bool stop, stopOnPonderhit; -}; - -extern SignalsType Signals; extern LimitsType Limits; void init(); void clear(); -template uint64_t perft(Position& pos, Depth depth); } // namespace Search diff --git a/Engines/Linux64/stockfish/src/syzygy/tbprobe.cpp b/Engines/Linux64/stockfish/src/syzygy/tbprobe.cpp index 0281ccc..b50275e 100644 --- a/Engines/Linux64/stockfish/src/syzygy/tbprobe.cpp +++ b/Engines/Linux64/stockfish/src/syzygy/tbprobe.cpp @@ -1,560 +1,1416 @@ /* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (c) 2013 Ronald de Man - This file may be redistributed and/or modified without restrictions. + Copyright (C) 2016-2018 Marco Costalba, Lucas Braesch - tbprobe.cpp contains the Stockfish-specific routines of the - tablebase probing code. It should be relatively easy to adapt - this code to other chess engines. -*/ + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. -#define NOMINMAX + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ #include +#include +#include +#include // For std::memset +#include +#include +#include +#include +#include +#include -#include "../position.h" -#include "../movegen.h" #include "../bitboard.h" +#include "../movegen.h" +#include "../position.h" #include "../search.h" +#include "../thread_win32.h" +#include "../types.h" #include "tbprobe.h" -#include "tbcore.h" -#include "tbcore.cpp" +#ifndef _WIN32 +#include +#include +#include +#include +#else +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#endif -namespace Zobrist { - extern Key psq[PIECE_NB][SQUARE_NB]; -} +using namespace Tablebases; -int Tablebases::MaxCardinality = 0; +int Tablebases::MaxCardinality; -// Given a position with 6 or fewer pieces, produce a text string -// of the form KQPvKRP, where "KQP" represents the white pieces if -// mirror == 0 and the black pieces if mirror == 1. -static void prt_str(Position& pos, char *str, int mirror) -{ - Color color; - PieceType pt; - int i; - - color = !mirror ? WHITE : BLACK; - for (pt = KING; pt >= PAWN; --pt) - for (i = popcount(pos.pieces(color, pt)); i > 0; i--) - *str++ = pchr[6 - pt]; - *str++ = 'v'; - color = ~color; - for (pt = KING; pt >= PAWN; --pt) - for (i = popcount(pos.pieces(color, pt)); i > 0; i--) - *str++ = pchr[6 - pt]; - *str++ = 0; -} +namespace { -// Given a position, produce a 64-bit material signature key. -// If the engine supports such a key, it should equal the engine's key. -static uint64 calc_key(Position& pos, int mirror) -{ - Color color; - PieceType pt; - int i; - uint64 key = 0; - - color = !mirror ? WHITE : BLACK; - for (pt = PAWN; pt <= KING; ++pt) - for (i = popcount(pos.pieces(color, pt)); i > 0; i--) - key ^= Zobrist::psq[make_piece(WHITE, pt)][i - 1]; - color = ~color; - for (pt = PAWN; pt <= KING; ++pt) - for (i = popcount(pos.pieces(color, pt)); i > 0; i--) - key ^= Zobrist::psq[make_piece(BLACK, pt)][i - 1]; - - return key; -} +// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables +enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, SingleValue = 128 }; -// Produce a 64-bit material key corresponding to the material combination -// defined by pcs[16], where pcs[1], ..., pcs[6] is the number of white -// pawns, ..., kings and pcs[9], ..., pcs[14] is the number of black -// pawns, ..., kings. -static uint64 calc_key_from_pcs(int *pcs, int mirror) -{ - int color; - PieceType pt; - int i; - uint64 key = 0; - - color = !mirror ? 0 : 8; - for (pt = PAWN; pt <= KING; ++pt) - for (i = 0; i < pcs[color + pt]; i++) - key ^= Zobrist::psq[make_piece(WHITE, pt)][i]; - color ^= 8; - for (pt = PAWN; pt <= KING; ++pt) - for (i = 0; i < pcs[color + pt]; i++) - key ^= Zobrist::psq[make_piece(BLACK, pt)][i]; - - return key; +inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); } +inline Square operator^=(Square& s, int i) { return s = Square(int(s) ^ i); } +inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } + +// DTZ tables don't store valid scores for moves that reset the rule50 counter +// like captures and pawn moves but we can easily recover the correct dtz of the +// previous move if we know the position's WDL score. +int dtz_before_zeroing(WDLScore wdl) { + return wdl == WDLWin ? 1 : + wdl == WDLCursedWin ? 101 : + wdl == WDLBlessedLoss ? -101 : + wdl == WDLLoss ? -1 : 0; } -bool is_little_endian() { - union { - int i; - char c[sizeof(int)]; - } x; - x.i = 1; - return x.c[0] == 1; +// Return the sign of a number (-1, 0, 1) +template int sign_of(T val) { + return (T(0) < val) - (val < T(0)); } -static ubyte decompress_pairs(struct PairsData *d, uint64 idx) +// Numbers in little endian used by sparseIndex[] to point into blockLength[] +struct SparseEntry { + char block[4]; // Number of block + char offset[2]; // Offset within the block +}; + +static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); + +typedef uint16_t Sym; // Huffman symbol + +struct LR { + enum Side { Left, Right, Value }; + + uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 + // bits is the right-hand symbol. If symbol has length 1, + // then the first byte is the stored value. + template + Sym get() { + return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] : + S == Right ? (lr[2] << 4) | (lr[1] >> 4) : + S == Value ? lr[0] : (assert(false), Sym(-1)); + } +}; + +static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes"); + +const int TBPIECES = 6; + +struct PairsData { + int flags; + size_t sizeofBlock; // Block size in bytes + size_t span; // About every span values there is a SparseIndex[] entry + int blocksNum; // Number of blocks in the TB file + int maxSymLen; // Maximum length in bits of the Huffman symbols + int minSymLen; // Minimum length in bits of the Huffman symbols + Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value + LR* btree; // btree[sym] stores the left and right symbols that expand sym + uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 + int blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum + SparseEntry* sparseIndex; // Partial indices into blockLength[] + size_t sparseIndexSize; // Size of SparseIndex[] table + uint8_t* data; // Start of Huffman compressed data + std::vector base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l + std::vector symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 + Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups + uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces + int groupLen[TBPIECES+1]; // Number of pieces in a given group: KRKN -> (3, 1) +}; + +// Helper struct to avoid manually defining entry copy constructor as we +// should because the default one is not compatible with std::atomic_bool. +struct Atomic { + Atomic() = default; + Atomic(const Atomic& e) { ready = e.ready.load(); } // MSVC 2013 wants assignment within body + std::atomic_bool ready; +}; + +// We define types for the different parts of the WDLEntry and DTZEntry with +// corresponding specializations for pieces or pawns. + +struct WDLEntryPiece { + PairsData* precomp; +}; + +struct WDLEntryPawn { + uint8_t pawnCount[2]; // [Lead color / other color] + WDLEntryPiece file[2][4]; // [wtm / btm][FILE_A..FILE_D] +}; + +struct DTZEntryPiece { + PairsData* precomp; + uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss + uint8_t* map; +}; + +struct DTZEntryPawn { + uint8_t pawnCount[2]; + DTZEntryPiece file[4]; + uint8_t* map; +}; + +struct TBEntry : public Atomic { + void* baseAddress; + uint64_t mapping; + Key key; + Key key2; + int pieceCount; + bool hasPawns; + bool hasUniquePieces; +}; + +// Now the main types: WDLEntry and DTZEntry +struct WDLEntry : public TBEntry { + WDLEntry(const std::string& code); + ~WDLEntry(); + union { + WDLEntryPiece pieceTable[2]; // [wtm / btm] + WDLEntryPawn pawnTable; + }; +}; + +struct DTZEntry : public TBEntry { + DTZEntry(const WDLEntry& wdl); + ~DTZEntry(); + union { + DTZEntryPiece pieceTable; + DTZEntryPawn pawnTable; + }; +}; + +typedef decltype(WDLEntry::pieceTable) WDLPieceTable; +typedef decltype(DTZEntry::pieceTable) DTZPieceTable; +typedef decltype(WDLEntry::pawnTable ) WDLPawnTable; +typedef decltype(DTZEntry::pawnTable ) DTZPawnTable; + +auto item(WDLPieceTable& e, int stm, int ) -> decltype(e[stm])& { return e[stm]; } +auto item(DTZPieceTable& e, int , int ) -> decltype(e)& { return e; } +auto item(WDLPawnTable& e, int stm, int f) -> decltype(e.file[stm][f])& { return e.file[stm][f]; } +auto item(DTZPawnTable& e, int , int f) -> decltype(e.file[f])& { return e.file[f]; } + +template struct Ret { typedef int type; }; +template<> struct Ret { typedef WDLScore type; }; + +int MapPawns[SQUARE_NB]; +int MapB1H1H7[SQUARE_NB]; +int MapA1D1D4[SQUARE_NB]; +int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] + +// Comparison function to sort leading pawns in ascending MapPawns[] order +bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; } +int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } + +const Value WDL_to_value[] = { + -VALUE_MATE + MAX_PLY + 1, + VALUE_DRAW - 2, + VALUE_DRAW, + VALUE_DRAW + 2, + VALUE_MATE - MAX_PLY - 1 +}; + +const std::string PieceToChar = " PNBRQK pnbrqk"; + +int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements +int LeadPawnIdx[5][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] +int LeadPawnsSize[5][4]; // [leadPawnsCnt][FILE_A..FILE_D] + +enum { BigEndian, LittleEndian }; + +template +inline void swap_byte(T& x) { - static const bool isLittleEndian = is_little_endian(); - return isLittleEndian ? decompress_pairs(d, idx) - : decompress_pairs(d, idx); + char tmp, *c = (char*)&x; + for (int i = 0; i < Half; ++i) + tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; } +template<> inline void swap_byte(uint8_t&) {} -// probe_wdl_table and probe_dtz_table require similar adaptations. -static int probe_wdl_table(Position& pos, int *success) +template T number(void* addr) { - struct TBEntry *ptr; - struct TBHashEntry *ptr2; - uint64 idx; - uint64 key; - int i; - ubyte res; - int p[TBPIECES]; - - // Obtain the position's material signature key. - key = pos.material_key(); - - // Test for KvK. - if (key == (Zobrist::psq[W_KING][0] ^ Zobrist::psq[B_KING][0])) - return 0; - - ptr2 = TB_hash[key >> (64 - TBHASHBITS)]; - for (i = 0; i < HSHMAX; i++) - if (ptr2[i].key == key) break; - if (i == HSHMAX) { - *success = 0; - return 0; + const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; + const bool IsLittleEndian = (Le.c[0] == 4); + + T v; + + if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) + std::memcpy(&v, addr, sizeof(T)); + else + v = *((T*)addr); + + if (LE != IsLittleEndian) + swap_byte(v); + return v; +} + +class HashTable { + + typedef std::pair EntryPair; + typedef std::pair Entry; + + static const int TBHASHBITS = 10; + static const int HSHMAX = 5; + + Entry hashTable[1 << TBHASHBITS][HSHMAX]; + + std::deque wdlTable; + std::deque dtzTable; + + void insert(Key key, WDLEntry* wdl, DTZEntry* dtz) { + Entry* entry = hashTable[key >> (64 - TBHASHBITS)]; + + for (int i = 0; i < HSHMAX; ++i, ++entry) + if (!entry->second.first || entry->first == key) { + *entry = std::make_pair(key, std::make_pair(wdl, dtz)); + return; + } + + std::cerr << "HSHMAX too low!" << std::endl; + exit(1); + } + +public: + template::value ? 0 : 1> + E* get(Key key) { + Entry* entry = hashTable[key >> (64 - TBHASHBITS)]; + + for (int i = 0; i < HSHMAX; ++i, ++entry) + if (entry->first == key) + return std::get(entry->second); + + return nullptr; } - ptr = ptr2[i].ptr; - if (!ptr->ready) { - LOCK(TB_mutex); - if (!ptr->ready) { - char str[16]; - prt_str(pos, str, ptr->key != key); - if (!init_table_wdl(ptr, str)) { - ptr2[i].key = 0ULL; - *success = 0; - UNLOCK(TB_mutex); - return 0; - } - // Memory barrier to ensure ptr->ready = 1 is not reordered. -#ifdef _MSC_VER - _ReadWriteBarrier(); + void clear() { + std::memset(hashTable, 0, sizeof(hashTable)); + wdlTable.clear(); + dtzTable.clear(); + } + size_t size() const { return wdlTable.size(); } + void insert(const std::vector& pieces); +}; + +HashTable EntryTable; + +class TBFile : public std::ifstream { + + std::string fname; + +public: + // Look for and open the file among the Paths directories where the .rtbw + // and .rtbz files can be found. Multiple directories are separated by ";" + // on Windows and by ":" on Unix-based operating systems. + // + // Example: + // C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6 + static std::string Paths; + + TBFile(const std::string& f) { + +#ifndef _WIN32 + const char SepChar = ':'; #else - __asm__ __volatile__ ("" ::: "memory"); + const char SepChar = ';'; #endif - ptr->ready = 1; + std::stringstream ss(Paths); + std::string path; + + while (std::getline(ss, path, SepChar)) { + fname = path + "/" + f; + std::ifstream::open(fname); + if (is_open()) + return; + } } - UNLOCK(TB_mutex); - } - int bside, mirror, cmirror; - if (!ptr->symmetric) { - if (key != ptr->key) { - cmirror = 8; - mirror = 0x38; - bside = (pos.side_to_move() == WHITE); - } else { - cmirror = mirror = 0; - bside = !(pos.side_to_move() == WHITE); + // Memory map the file and check it. File should be already open and will be + // closed after mapping. + uint8_t* map(void** baseAddress, uint64_t* mapping, const uint8_t* TB_MAGIC) { + + assert(is_open()); + + close(); // Need to re-open to get native file descriptor + +#ifndef _WIN32 + struct stat statbuf; + int fd = ::open(fname.c_str(), O_RDONLY); + + if (fd == -1) + return *baseAddress = nullptr, nullptr; + + fstat(fd, &statbuf); + *mapping = statbuf.st_size; + *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); + ::close(fd); + + if (*baseAddress == MAP_FAILED) { + std::cerr << "Could not mmap() " << fname << std::endl; + exit(1); + } +#else + HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (fd == INVALID_HANDLE_VALUE) + return *baseAddress = nullptr, nullptr; + + DWORD size_high; + DWORD size_low = GetFileSize(fd, &size_high); + HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr); + CloseHandle(fd); + + if (!mmap) { + std::cerr << "CreateFileMapping() failed" << std::endl; + exit(1); + } + + *mapping = (uint64_t)mmap; + *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); + + if (!*baseAddress) { + std::cerr << "MapViewOfFile() failed, name = " << fname + << ", error = " << GetLastError() << std::endl; + exit(1); + } +#endif + uint8_t* data = (uint8_t*)*baseAddress; + + if ( *data++ != *TB_MAGIC++ + || *data++ != *TB_MAGIC++ + || *data++ != *TB_MAGIC++ + || *data++ != *TB_MAGIC) { + std::cerr << "Corrupted table in file " << fname << std::endl; + unmap(*baseAddress, *mapping); + return *baseAddress = nullptr, nullptr; + } + + return data; } - } else { - cmirror = pos.side_to_move() == WHITE ? 0 : 8; - mirror = pos.side_to_move() == WHITE ? 0 : 0x38; - bside = 0; - } - // p[i] is to contain the square 0-63 (A1-H8) for a piece of type - // pc[i] ^ cmirror, where 1 = white pawn, ..., 14 = black king. - // Pieces of the same type are guaranteed to be consecutive. - if (!ptr->has_pawns) { - struct TBEntry_piece *entry = (struct TBEntry_piece *)ptr; - ubyte *pc = entry->pieces[bside]; - for (i = 0; i < entry->num;) { - Bitboard bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), - (PieceType)(pc[i] & 0x07)); - do { - p[i++] = pop_lsb(&bb); - } while (bb); + static void unmap(void* baseAddress, uint64_t mapping) { + +#ifndef _WIN32 + munmap(baseAddress, mapping); +#else + UnmapViewOfFile(baseAddress); + CloseHandle((HANDLE)mapping); +#endif } - idx = encode_piece(entry, entry->norm[bside], p, entry->factor[bside]); - res = decompress_pairs(entry->precomp[bside], idx); - } else { - struct TBEntry_pawn *entry = (struct TBEntry_pawn *)ptr; - int k = entry->file[0].pieces[0][0] ^ cmirror; - Bitboard bb = pos.pieces((Color)(k >> 3), (PieceType)(k & 0x07)); - i = 0; - do { - p[i++] = pop_lsb(&bb) ^ mirror; - } while (bb); - int f = pawn_file(entry, p); - ubyte *pc = entry->file[f].pieces[bside]; - for (; i < entry->num;) { - bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), - (PieceType)(pc[i] & 0x07)); - do { - p[i++] = pop_lsb(&bb) ^ mirror; - } while (bb); +}; + +std::string TBFile::Paths; + +WDLEntry::WDLEntry(const std::string& code) { + + StateInfo st; + Position pos; + + memset(this, 0, sizeof(WDLEntry)); + + ready = false; + key = pos.set(code, WHITE, &st).material_key(); + pieceCount = popcount(pos.pieces()); + hasPawns = pos.pieces(PAWN); + + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt < KING; ++pt) + if (popcount(pos.pieces(c, pt)) == 1) + hasUniquePieces = true; + + if (hasPawns) { + // Set the leading color. In case both sides have pawns the leading color + // is the side with less pawns because this leads to better compression. + bool c = !pos.count(BLACK) + || ( pos.count(WHITE) + && pos.count(BLACK) >= pos.count(WHITE)); + + pawnTable.pawnCount[0] = pos.count(c ? WHITE : BLACK); + pawnTable.pawnCount[1] = pos.count(c ? BLACK : WHITE); } - idx = encode_pawn(entry, entry->file[f].norm[bside], p, entry->file[f].factor[bside]); - res = decompress_pairs(entry->file[f].precomp[bside], idx); - } - return ((int)res) - 2; + key2 = pos.set(code, BLACK, &st).material_key(); } -static int probe_dtz_table(Position& pos, int wdl, int *success) -{ - struct TBEntry *ptr; - uint64 idx; - int i, res; - int p[TBPIECES]; - - // Obtain the position's material signature key. - uint64 key = pos.material_key(); - - if (DTZ_table[0].key1 != key && DTZ_table[0].key2 != key) { - for (i = 1; i < DTZ_ENTRIES; i++) - if (DTZ_table[i].key1 == key) break; - if (i < DTZ_ENTRIES) { - struct DTZTableEntry table_entry = DTZ_table[i]; - for (; i > 0; i--) - DTZ_table[i] = DTZ_table[i - 1]; - DTZ_table[0] = table_entry; - } else { - struct TBHashEntry *ptr2 = TB_hash[key >> (64 - TBHASHBITS)]; - for (i = 0; i < HSHMAX; i++) - if (ptr2[i].key == key) break; - if (i == HSHMAX) { - *success = 0; - return 0; - } - ptr = ptr2[i].ptr; - char str[16]; - int mirror = (ptr->key != key); - prt_str(pos, str, mirror); - if (DTZ_table[DTZ_ENTRIES - 1].entry) - free_dtz_entry(DTZ_table[DTZ_ENTRIES-1].entry); - for (i = DTZ_ENTRIES - 1; i > 0; i--) - DTZ_table[i] = DTZ_table[i - 1]; - load_dtz_table(str, calc_key(pos, mirror), calc_key(pos, !mirror)); - } - } +WDLEntry::~WDLEntry() { - ptr = DTZ_table[0].entry; - if (!ptr) { - *success = 0; - return 0; - } + if (baseAddress) + TBFile::unmap(baseAddress, mapping); + + for (int i = 0; i < 2; ++i) + if (hasPawns) + for (File f = FILE_A; f <= FILE_D; ++f) + delete pawnTable.file[i][f].precomp; + else + delete pieceTable[i].precomp; +} + +DTZEntry::DTZEntry(const WDLEntry& wdl) { + + memset(this, 0, sizeof(DTZEntry)); - int bside, mirror, cmirror; - if (!ptr->symmetric) { - if (key != ptr->key) { - cmirror = 8; - mirror = 0x38; - bside = (pos.side_to_move() == WHITE); - } else { - cmirror = mirror = 0; - bside = !(pos.side_to_move() == WHITE); + ready = false; + key = wdl.key; + key2 = wdl.key2; + pieceCount = wdl.pieceCount; + hasPawns = wdl.hasPawns; + hasUniquePieces = wdl.hasUniquePieces; + + if (hasPawns) { + pawnTable.pawnCount[0] = wdl.pawnTable.pawnCount[0]; + pawnTable.pawnCount[1] = wdl.pawnTable.pawnCount[1]; } - } else { - cmirror = pos.side_to_move() == WHITE ? 0 : 8; - mirror = pos.side_to_move() == WHITE ? 0 : 0x38; - bside = 0; - } +} + +DTZEntry::~DTZEntry() { + + if (baseAddress) + TBFile::unmap(baseAddress, mapping); + + if (hasPawns) + for (File f = FILE_A; f <= FILE_D; ++f) + delete pawnTable.file[f].precomp; + else + delete pieceTable.precomp; +} + +void HashTable::insert(const std::vector& pieces) { + + std::string code; + + for (PieceType pt : pieces) + code += PieceToChar[pt]; + + TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK + + if (!file.is_open()) // Only WDL file is checked + return; + + file.close(); + + MaxCardinality = std::max((int)pieces.size(), MaxCardinality); + + wdlTable.emplace_back(code); + dtzTable.emplace_back(wdlTable.back()); + + insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back()); + insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back()); +} + +// TB tables are compressed with canonical Huffman code. The compressed data is divided into +// blocks of size d->sizeofBlock, and each block stores a variable number of symbols. +// Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols +// (recursively). If you keep expanding the symbols in a block, you end up with up to 65536 +// WDL or DTZ values. Each symbol represents up to 256 values and will correspond after +// Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most +// 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly +// of draws or mostly of wins, but such tables are actually quite common. In principle, the +// blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for +// mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so +// in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long. +// The generator picks the size that leads to the smallest table. The "book" of symbols and +// Huffman codes is the same for all blocks in the table. A non-symmetric pawnless TB file +// will have one table for wtm and one for btm, a TB file with pawns will have tables per +// file a,b,c,d also in this case one set for wtm and one for btm. +int decompress_pairs(PairsData* d, uint64_t idx) { + + // Special case where all table positions store the same value + if (d->flags & TBFlag::SingleValue) + return d->minSymLen; + + // First we need to locate the right block that stores the value at index "idx". + // Because each block n stores blockLength[n] + 1 values, the index i of the block + // that contains the value at position idx is: + // + // for (i = -1, sum = 0; sum <= idx; i++) + // sum += blockLength[i + 1] + 1; + // + // This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that + // point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry + // that stores the blockLength[] index and the offset within that block of the value + // with index I(k), where: + // + // I(k) = k * d->span + d->span / 2 (1) + + // First step is to get the 'k' of the I(k) nearest to our idx, using definition (1) + uint32_t k = idx / d->span; + + // Then we read the corresponding SparseIndex[] entry + uint32_t block = number(&d->sparseIndex[k].block); + int offset = number(&d->sparseIndex[k].offset); + + // Now compute the difference idx - I(k). From definition of k we know that + // + // idx = k * d->span + idx % d->span (2) + // + // So from (1) and (2) we can compute idx - I(K): + int diff = idx % d->span - d->span / 2; + + // Sum the above to offset to find the offset corresponding to our idx + offset += diff; + + // Move to previous/next block, until we reach the correct block that contains idx, + // that is when 0 <= offset <= d->blockLength[block] + while (offset < 0) + offset += d->blockLength[--block] + 1; + + while (offset > d->blockLength[block]) + offset -= d->blockLength[block++] + 1; + + // Finally, we find the start address of our block of canonical Huffman symbols + uint32_t* ptr = (uint32_t*)(d->data + block * d->sizeofBlock); + + // Read the first 64 bits in our block, this is a (truncated) sequence of + // unknown number of symbols of unknown length but we know the first one + // is at the beginning of this 64 bits sequence. + uint64_t buf64 = number(ptr); ptr += 2; + int buf64Size = 64; + Sym sym; + + while (true) { + int len = 0; // This is the symbol length - d->min_sym_len + + // Now get the symbol length. For any symbol s64 of length l right-padded + // to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we + // can find the symbol length iterating through base64[]. + while (buf64 < d->base64[len]) + ++len; + + // All the symbols of a given length are consecutive integers (numerical + // sequence property), so we can compute the offset of our symbol of + // length len, stored at the beginning of buf64. + sym = (buf64 - d->base64[len]) >> (64 - len - d->minSymLen); + + // Now add the value of the lowest symbol of length len to get our symbol + sym += number(&d->lowestSym[len]); + + // If our offset is within the number of values represented by symbol sym + // we are done... + if (offset < d->symlen[sym] + 1) + break; - if (!ptr->has_pawns) { - struct DTZEntry_piece *entry = (struct DTZEntry_piece *)ptr; - if ((entry->flags & 1) != bside && !entry->symmetric) { - *success = -1; - return 0; + // ...otherwise update the offset and continue to iterate + offset -= d->symlen[sym] + 1; + len += d->minSymLen; // Get the real length + buf64 <<= len; // Consume the just processed symbol + buf64Size -= len; + + if (buf64Size <= 32) { // Refill the buffer + buf64Size += 32; + buf64 |= (uint64_t)number(ptr++) << (64 - buf64Size); + } } - ubyte *pc = entry->pieces; - for (i = 0; i < entry->num;) { - Bitboard bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), - (PieceType)(pc[i] & 0x07)); - do { - p[i++] = pop_lsb(&bb); - } while (bb); + + // Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols. + // We binary-search for our value recursively expanding into the left and + // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 + // that will store the value we need. + while (d->symlen[sym]) { + + Sym left = d->btree[sym].get(); + + // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and + // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then + // we know that, for instance the ten-th value (offset = 10) will be on + // the left side because in Recursive Pairing child symbols are adjacent. + if (offset < d->symlen[left] + 1) + sym = left; + else { + offset -= d->symlen[left] + 1; + sym = d->btree[sym].get(); + } } - idx = encode_piece((struct TBEntry_piece *)entry, entry->norm, p, entry->factor); - res = decompress_pairs(entry->precomp, idx); - - if (entry->flags & 2) - res = entry->map[entry->map_idx[wdl_to_map[wdl + 2]] + res]; - - if (!(entry->flags & pa_flags[wdl + 2]) || (wdl & 1)) - res *= 2; - } else { - struct DTZEntry_pawn *entry = (struct DTZEntry_pawn *)ptr; - int k = entry->file[0].pieces[0] ^ cmirror; - Bitboard bb = pos.pieces((Color)(k >> 3), (PieceType)(k & 0x07)); - i = 0; + + return d->btree[sym].get(); +} + +bool check_dtz_stm(WDLEntry*, int, File) { return true; } + +bool check_dtz_stm(DTZEntry* entry, int stm, File f) { + + int flags = entry->hasPawns ? entry->pawnTable.file[f].precomp->flags + : entry->pieceTable.precomp->flags; + + return (flags & TBFlag::STM) == stm + || ((entry->key == entry->key2) && !entry->hasPawns); +} + +// DTZ scores are sorted by frequency of occurrence and then assigned the +// values 0, 1, 2, ... in order of decreasing frequency. This is done for each +// of the four WDLScore values. The mapping information necessary to reconstruct +// the original values is stored in the TB file and read during map[] init. +WDLScore map_score(WDLEntry*, File, int value, WDLScore) { return WDLScore(value - 2); } + +int map_score(DTZEntry* entry, File f, int value, WDLScore wdl) { + + const int WDLMap[] = { 1, 3, 0, 2, 0 }; + + int flags = entry->hasPawns ? entry->pawnTable.file[f].precomp->flags + : entry->pieceTable.precomp->flags; + + uint8_t* map = entry->hasPawns ? entry->pawnTable.map + : entry->pieceTable.map; + + uint16_t* idx = entry->hasPawns ? entry->pawnTable.file[f].map_idx + : entry->pieceTable.map_idx; + if (flags & TBFlag::Mapped) + value = map[idx[WDLMap[wdl + 2]] + value]; + + // DTZ tables store distance to zero in number of moves or plies. We + // want to return plies, so we have convert to plies when needed. + if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies)) + || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) + || wdl == WDLCursedWin + || wdl == WDLBlessedLoss) + value *= 2; + + return value + 1; +} + +// Compute a unique index out of a position and use it to probe the TB file. To +// encode k pieces of same type and color, first sort the pieces by square in +// ascending order s1 <= s2 <= ... <= sk then compute the unique index as: +// +// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] +// +template::type> +T do_probe_table(const Position& pos, Entry* entry, WDLScore wdl, ProbeState* result) { + + const bool IsWDL = std::is_same::value; + + Square squares[TBPIECES]; + Piece pieces[TBPIECES]; + uint64_t idx; + int next = 0, size = 0, leadPawnsCnt = 0; + PairsData* d; + Bitboard b, leadPawns = 0; + File tbFile = FILE_A; + + // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. + // If both sides have the same pieces keys are equal. In this case TB tables + // only store the 'white to move' case, so if the position to lookup has black + // to move, we need to switch the color and flip the squares before to lookup. + bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move()); + + // TB files are calculated for white as stronger side. For instance we have + // KRvK, not KvKR. A position where stronger side is white will have its + // material key == entry->key, otherwise we have to switch the color and + // flip the squares before to lookup. + bool blackStronger = (pos.material_key() != entry->key); + + int flipColor = (symmetricBlackToMove || blackStronger) * 8; + int flipSquares = (symmetricBlackToMove || blackStronger) * 070; + int stm = (symmetricBlackToMove || blackStronger) ^ pos.side_to_move(); + + // For pawns, TB files store 4 separate tables according if leading pawn is on + // file a, b, c or d after reordering. The leading pawn is the one with maximum + // MapPawns[] value, that is the one most toward the edges and with lowest rank. + if (entry->hasPawns) { + + // In all the 4 tables, pawns are at the beginning of the piece sequence and + // their color is the reference one. So we just pick the first one. + Piece pc = Piece(item(entry->pawnTable, 0, 0).precomp->pieces[0] ^ flipColor); + + assert(type_of(pc) == PAWN); + + leadPawns = b = pos.pieces(color_of(pc), PAWN); + do + squares[size++] = pop_lsb(&b) ^ flipSquares; + while (b); + + leadPawnsCnt = size; + + std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp)); + + tbFile = file_of(squares[0]); + if (tbFile > FILE_D) + tbFile = file_of(squares[0] ^ 7); // Horizontal flip: SQ_H1 -> SQ_A1 + + d = item(entry->pawnTable , stm, tbFile).precomp; + } else + d = item(entry->pieceTable, stm, tbFile).precomp; + + // DTZ tables are one-sided, i.e. they store positions only for white to + // move or only for black to move, so check for side to move to be stm, + // early exit otherwise. + if (!IsWDL && !check_dtz_stm(entry, stm, tbFile)) + return *result = CHANGE_STM, T(); + + // Now we are ready to get all the position pieces (but the lead pawns) and + // directly map them to the correct color and square. + b = pos.pieces() ^ leadPawns; do { - p[i++] = pop_lsb(&bb) ^ mirror; - } while (bb); - int f = pawn_file((struct TBEntry_pawn *)entry, p); - if ((entry->flags[f] & 1) != bside) { - *success = -1; - return 0; + Square s = pop_lsb(&b); + squares[size] = s ^ flipSquares; + pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); + } while (b); + + assert(size >= 2); + + // Then we reorder the pieces to have the same sequence as the one stored + // in precomp->pieces[i]: the sequence that ensures the best compression. + for (int i = leadPawnsCnt; i < size; ++i) + for (int j = i; j < size; ++j) + if (d->pieces[i] == pieces[j]) + { + std::swap(pieces[i], pieces[j]); + std::swap(squares[i], squares[j]); + break; + } + + // Now we map again the squares so that the square of the lead piece is in + // the triangle A1-D1-D4. + if (file_of(squares[0]) > FILE_D) + for (int i = 0; i < size; ++i) + squares[i] ^= 7; // Horizontal flip: SQ_H1 -> SQ_A1 + + // Encode leading pawns starting with the one with minimum MapPawns[] and + // proceeding in ascending order. + if (entry->hasPawns) { + idx = LeadPawnIdx[leadPawnsCnt][squares[0]]; + + std::sort(squares + 1, squares + leadPawnsCnt, pawns_comp); + + for (int i = 1; i < leadPawnsCnt; ++i) + idx += Binomial[i][MapPawns[squares[i]]]; + + goto encode_remaining; // With pawns we have finished special treatments } - ubyte *pc = entry->file[f].pieces; - for (; i < entry->num;) { - bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), - (PieceType)(pc[i] & 0x07)); - do { - p[i++] = pop_lsb(&bb) ^ mirror; - } while (bb); + + // In positions withouth pawns, we further flip the squares to ensure leading + // piece is below RANK_5. + if (rank_of(squares[0]) > RANK_4) + for (int i = 0; i < size; ++i) + squares[i] ^= 070; // Vertical flip: SQ_A8 -> SQ_A1 + + // Look for the first piece of the leading group not on the A1-D4 diagonal + // and ensure it is mapped below the diagonal. + for (int i = 0; i < d->groupLen[0]; ++i) { + if (!off_A1H8(squares[i])) + continue; + + if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C3 + for (int j = i; j < size; ++j) + squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63); + break; } - idx = encode_pawn((struct TBEntry_pawn *)entry, entry->file[f].norm, p, entry->file[f].factor); - res = decompress_pairs(entry->file[f].precomp, idx); - if (entry->flags[f] & 2) - res = entry->map[entry->map_idx[f][wdl_to_map[wdl + 2]] + res]; + // Encode the leading group. + // + // Suppose we have KRvK. Let's say the pieces are on square numbers wK, wR + // and bK (each 0...63). The simplest way to map this position to an index + // is like this: + // + // index = wK * 64 * 64 + wR * 64 + bK; + // + // But this way the TB is going to have 64*64*64 = 262144 positions, with + // lots of positions being equivalent (because they are mirrors of each + // other) and lots of positions being invalid (two pieces on one square, + // adjacent kings, etc.). + // Usually the first step is to take the wK and bK together. There are just + // 462 ways legal and not-mirrored ways to place the wK and bK on the board. + // Once we have placed the wK and bK, there are 62 squares left for the wR + // Mapping its square from 0..63 to available squares 0..61 can be done like: + // + // wR -= (wR > wK) + (wR > bK); + // + // In words: if wR "comes later" than wK, we deduct 1, and the same if wR + // "comes later" than bK. In case of two same pieces like KRRvK we want to + // place the two Rs "together". If we have 62 squares left, we can place two + // Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be + // swapped and still get the same position.) + // + // In case we have at least 3 unique pieces (inlcuded kings) we encode them + // together. + if (entry->hasUniquePieces) { + + int adjust1 = squares[1] > squares[0]; + int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]); + + // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3 + // triangle to 0...5. There are 63 squares for second piece and and 62 + // (mapped to 0...61) for the third. + if (off_A1H8(squares[0])) + idx = ( MapA1D1D4[squares[0]] * 63 + + (squares[1] - adjust1)) * 62 + + squares[2] - adjust2; + + // First piece is on a1-h8 diagonal, second below: map this occurence to + // 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal + // to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27. + else if (off_A1H8(squares[1])) + idx = ( 6 * 63 + rank_of(squares[0]) * 28 + + MapB1H1H7[squares[1]]) * 62 + + squares[2] - adjust2; + + // First two pieces are on a1-h8 diagonal, third below + else if (off_A1H8(squares[2])) + idx = 6 * 63 * 62 + 4 * 28 * 62 + + rank_of(squares[0]) * 7 * 28 + + (rank_of(squares[1]) - adjust1) * 28 + + MapB1H1H7[squares[2]]; + + // All 3 pieces on the diagonal a1-h8 + else + idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + + rank_of(squares[0]) * 7 * 6 + + (rank_of(squares[1]) - adjust1) * 6 + + (rank_of(squares[2]) - adjust2); + } else + // We don't have at least 3 unique pieces, like in KRRvKBB, just map + // the kings. + idx = MapKK[MapA1D1D4[squares[0]]][squares[1]]; + +encode_remaining: + idx *= d->groupIdx[0]; + Square* groupSq = squares + d->groupLen[0]; + + // Encode remainig pawns then pieces according to square, in ascending order + bool remainingPawns = entry->hasPawns && entry->pawnTable.pawnCount[1]; + + while (d->groupLen[++next]) + { + std::sort(groupSq, groupSq + d->groupLen[next]); + uint64_t n = 0; + + // Map down a square if "comes later" than a square in the previous + // groups (similar to what done earlier for leading group pieces). + for (int i = 0; i < d->groupLen[next]; ++i) + { + auto f = [&](Square s) { return groupSq[i] > s; }; + auto adjust = std::count_if(squares, groupSq, f); + n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns]; + } - if (!(entry->flags[f] & pa_flags[wdl + 2]) || (wdl & 1)) - res *= 2; - } + remainingPawns = false; + idx += n * d->groupIdx[next]; + groupSq += d->groupLen[next]; + } - return res; + // Now that we have the index, decompress the pair and get the score + return map_score(entry, tbFile, decompress_pairs(d, idx), wdl); } -// Add underpromotion captures to list of captures. -static ExtMove *add_underprom_caps(Position& pos, ExtMove *stack, ExtMove *end) -{ - ExtMove *moves, *extra = end; - - for (moves = stack; moves < end; moves++) { - Move move = moves->move; - if (type_of(move) == PROMOTION && !pos.empty(to_sq(move))) { - (*extra++).move = (Move)(move - (1 << 12)); - (*extra++).move = (Move)(move - (2 << 12)); - (*extra++).move = (Move)(move - (3 << 12)); +// Group together pieces that will be encoded together. The general rule is that +// a group contains pieces of same type and color. The exception is the leading +// group that, in case of positions withouth pawns, can be formed by 3 different +// pieces (default) or by the king pair when there is not a unique piece apart +// from the kings. When there are pawns, pawns are always first in pieces[]. +// +// As example KRKN -> KRK + N, KNNK -> KK + NN, KPPKP -> P + PP + K + K +// +// The actual grouping depends on the TB generator and can be inferred from the +// sequence of pieces in piece[] array. +template +void set_groups(T& e, PairsData* d, int order[], File f) { + + int n = 0, firstLen = e.hasPawns ? 0 : e.hasUniquePieces ? 3 : 2; + d->groupLen[n] = 1; + + // Number of pieces per group is stored in groupLen[], for instance in KRKN + // the encoder will default on '111', so groupLen[] will be (3, 1). + for (int i = 1; i < e.pieceCount; ++i) + if (--firstLen > 0 || d->pieces[i] == d->pieces[i - 1]) + d->groupLen[n]++; + else + d->groupLen[++n] = 1; + + d->groupLen[++n] = 0; // Zero-terminated + + // The sequence in pieces[] defines the groups, but not the order in which + // they are encoded. If the pieces in a group g can be combined on the board + // in N(g) different ways, then the position encoding will be of the form: + // + // g1 * N(g2) * N(g3) + g2 * N(g3) + g3 + // + // This ensures unique encoding for the whole position. The order of the + // groups is a per-table parameter and could not follow the canonical leading + // pawns/pieces -> remainig pawns -> remaining pieces. In particular the + // first group is at order[0] position and the remaining pawns, when present, + // are at order[1] position. + bool pp = e.hasPawns && e.pawnTable.pawnCount[1]; // Pawns on both sides + int next = pp ? 2 : 1; + int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); + uint64_t idx = 1; + + for (int k = 0; next < n || k == order[0] || k == order[1]; ++k) + if (k == order[0]) // Leading pawns or pieces + { + d->groupIdx[0] = idx; + idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] + : e.hasUniquePieces ? 31332 : 462; + } + else if (k == order[1]) // Remaining pawns + { + d->groupIdx[1] = idx; + idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]]; + } + else // Remainig pieces + { + d->groupIdx[next] = idx; + idx *= Binomial[d->groupLen[next]][freeSquares]; + freeSquares -= d->groupLen[next++]; + } + + d->groupIdx[n] = idx; +} + +// In Recursive Pairing each symbol represents a pair of childern symbols. So +// read d->btree[] symbols data and expand each one in his left and right child +// symbol until reaching the leafs that represent the symbol value. +uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) { + + visited[s] = true; // We can set it now because tree is acyclic + Sym sr = d->btree[s].get(); + + if (sr == 0xFFF) + return 0; + + Sym sl = d->btree[s].get(); + + if (!visited[sl]) + d->symlen[sl] = set_symlen(d, sl, visited); + + if (!visited[sr]) + d->symlen[sr] = set_symlen(d, sr, visited); + + return d->symlen[sl] + d->symlen[sr] + 1; +} + +uint8_t* set_sizes(PairsData* d, uint8_t* data) { + + d->flags = *data++; + + if (d->flags & TBFlag::SingleValue) { + d->blocksNum = d->blockLengthSize = 0; + d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init + d->minSymLen = *data++; // Here we store the single value + return data; + } + + // groupLen[] is a zero-terminated list of group lengths, the last groupIdx[] + // element stores the biggest index that is the tb size. + uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen]; + + d->sizeofBlock = 1ULL << *data++; + d->span = 1ULL << *data++; + d->sparseIndexSize = (tbSize + d->span - 1) / d->span; // Round up + int padding = number(data++); + d->blocksNum = number(data); data += sizeof(uint32_t); + d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] + // does not point out of range. + d->maxSymLen = *data++; + d->minSymLen = *data++; + d->lowestSym = (Sym*)data; + d->base64.resize(d->maxSymLen - d->minSymLen + 1); + + // The canonical code is ordered such that longer symbols (in terms of + // the number of bits of their Huffman code) have lower numeric value, + // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). + // Starting from this we compute a base64[] table indexed by symbol length + // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. + // See http://www.eecs.harvard.edu/~michaelm/E210/huffman.pdf + for (int i = d->base64.size() - 2; i >= 0; --i) { + d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) + - number(&d->lowestSym[i + 1])) / 2; + + assert(d->base64[i] * 2 >= d->base64[i+1]); } - } - return extra; + // Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more + // than d->base64[i+1] and given the above assert condition, we ensure that + // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i + // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. + for (size_t i = 0; i < d->base64.size(); ++i) + d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits + + data += d->base64.size() * sizeof(Sym); + d->symlen.resize(number(data)); data += sizeof(uint16_t); + d->btree = (LR*)data; + + // The comrpession scheme used is "Recursive Pairing", that replaces the most + // frequent adjacent pair of symbols in the source message by a new symbol, + // reevaluating the frequencies of all of the symbol pairs with respect to + // the extended alphabet, and then repeating the process. + // See http://www.larsson.dogma.net/dcc99.pdf + std::vector visited(d->symlen.size()); + + for (Sym sym = 0; sym < d->symlen.size(); ++sym) + if (!visited[sym]) + d->symlen[sym] = set_symlen(d, sym, visited); + + return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1); } -static int probe_ab(Position& pos, int alpha, int beta, int *success) -{ - int v; - ExtMove stack[64]; - ExtMove *moves, *end; - StateInfo st; - - // Generate (at least) all legal non-ep captures including (under)promotions. - // It is OK to generate more, as long as they are filtered out below. - if (!pos.checkers()) { - end = generate(pos, stack); - // Since underpromotion captures are not included, we need to add them. - end = add_underprom_caps(pos, stack, end); - } else - end = generate(pos, stack); - - for (moves = stack; moves < end; moves++) { - Move capture = moves->move; - if (!pos.capture(capture) || type_of(capture) == ENPASSANT - || !pos.legal(capture)) - continue; - pos.do_move(capture, st, pos.gives_check(capture)); - v = -probe_ab(pos, -beta, -alpha, success); - pos.undo_move(capture); - if (*success == 0) return 0; - if (v > alpha) { - if (v >= beta) { - *success = 2; - return v; - } - alpha = v; +template +uint8_t* set_dtz_map(WDLEntry&, T&, uint8_t*, File) { return nullptr; } + +template +uint8_t* set_dtz_map(DTZEntry&, T& p, uint8_t* data, File maxFile) { + + p.map = data; + + for (File f = FILE_A; f <= maxFile; ++f) { + if (item(p, 0, f).precomp->flags & TBFlag::Mapped) + for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x + item(p, 0, f).map_idx[i] = (uint16_t)(data - p.map + 1); + data += *data + 1; + } } - } - v = probe_wdl_table(pos, success); - if (*success == 0) return 0; - if (alpha >= v) { - *success = 1 + (alpha > 0); - return alpha; - } else { - *success = 1; - return v; - } + return data += (uintptr_t)data & 1; // Word alignment } -// Probe the WDL table for a particular position. -// If *success != 0, the probe was successful. -// The return value is from the point of view of the side to move: -// -2 : loss -// -1 : loss, but draw under 50-move rule -// 0 : draw -// 1 : win, but draw under 50-move rule -// 2 : win -int Tablebases::probe_wdl(Position& pos, int *success) -{ - int v; +template +void do_init(Entry& e, T& p, uint8_t* data) { - *success = 1; - v = probe_ab(pos, -2, 2, success); + const bool IsWDL = std::is_same::value; - // If en passant is not possible, we are done. - if (pos.ep_square() == SQ_NONE) - return v; - if (!(*success)) return 0; - - // Now handle en passant. - int v1 = -3; - // Generate (at least) all legal en passant captures. - ExtMove stack[192]; - ExtMove *moves, *end; - StateInfo st; - - if (!pos.checkers()) - end = generate(pos, stack); - else - end = generate(pos, stack); - - for (moves = stack; moves < end; moves++) { - Move capture = moves->move; - if (type_of(capture) != ENPASSANT - || !pos.legal(capture)) - continue; - pos.do_move(capture, st, pos.gives_check(capture)); - int v0 = -probe_ab(pos, -2, 2, success); - pos.undo_move(capture); - if (*success == 0) return 0; - if (v0 > v1) v1 = v0; - } - if (v1 > -3) { - if (v1 >= v) v = v1; - else if (v == 0) { - // Check whether there is at least one legal non-ep move. - for (moves = stack; moves < end; moves++) { - Move capture = moves->move; - if (type_of(capture) == ENPASSANT) continue; - if (pos.legal(capture)) break; - } - if (moves == end && !pos.checkers()) { - end = generate(pos, end); - for (; moves < end; moves++) { - Move move = moves->move; - if (pos.legal(move)) - break; - } - } - // If not, then we are forced to play the losing ep capture. - if (moves == end) - v = v1; + PairsData* d; + + enum { Split = 1, HasPawns = 2 }; + + assert(e.hasPawns == !!(*data & HasPawns)); + assert((e.key != e.key2) == !!(*data & Split)); + + data++; // First byte stores flags + + const int Sides = IsWDL && (e.key != e.key2) ? 2 : 1; + const File MaxFile = e.hasPawns ? FILE_D : FILE_A; + + bool pp = e.hasPawns && e.pawnTable.pawnCount[1]; // Pawns on both sides + + assert(!pp || e.pawnTable.pawnCount[0]); + + for (File f = FILE_A; f <= MaxFile; ++f) { + + for (int i = 0; i < Sides; i++) + item(p, i, f).precomp = new PairsData(); + + int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF }, + { *data >> 4, pp ? *(data + 1) >> 4 : 0xF } }; + data += 1 + pp; + + for (int k = 0; k < e.pieceCount; ++k, ++data) + for (int i = 0; i < Sides; i++) + item(p, i, f).precomp->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF); + + for (int i = 0; i < Sides; ++i) + set_groups(e, item(p, i, f).precomp, order[i], f); } - } - return v; + data += (uintptr_t)data & 1; // Word alignment + + for (File f = FILE_A; f <= MaxFile; ++f) + for (int i = 0; i < Sides; i++) + data = set_sizes(item(p, i, f).precomp, data); + + if (!IsWDL) + data = set_dtz_map(e, p, data, MaxFile); + + for (File f = FILE_A; f <= MaxFile; ++f) + for (int i = 0; i < Sides; i++) { + (d = item(p, i, f).precomp)->sparseIndex = (SparseEntry*)data; + data += d->sparseIndexSize * sizeof(SparseEntry); + } + + for (File f = FILE_A; f <= MaxFile; ++f) + for (int i = 0; i < Sides; i++) { + (d = item(p, i, f).precomp)->blockLength = (uint16_t*)data; + data += d->blockLengthSize * sizeof(uint16_t); + } + + for (File f = FILE_A; f <= MaxFile; ++f) + for (int i = 0; i < Sides; i++) { + data = (uint8_t*)(((uintptr_t)data + 0x3F) & ~0x3F); // 64 byte alignment + (d = item(p, i, f).precomp)->data = data; + data += d->blocksNum * d->sizeofBlock; + } } -// This routine treats a position with en passant captures as one without. -static int probe_dtz_no_ep(Position& pos, int *success) -{ - int wdl, dtz; +template +void* init(Entry& e, const Position& pos) { - wdl = probe_ab(pos, -2, 2, success); - if (*success == 0) return 0; + const bool IsWDL = std::is_same::value; - if (wdl == 0) return 0; + static Mutex mutex; - if (*success == 2) - return wdl == 2 ? 1 : 101; + // Avoid a thread reads 'ready' == true while another is still in do_init(), + // this could happen due to compiler reordering. + if (e.ready.load(std::memory_order_acquire)) + return e.baseAddress; - ExtMove stack[192]; - ExtMove *moves, *end = NULL; - StateInfo st; + std::unique_lock lk(mutex); - if (wdl > 0) { - // Generate at least all legal non-capturing pawn moves - // including non-capturing promotions. - if (!pos.checkers()) - end = generate(pos, stack); - else - end = generate(pos, stack); - - for (moves = stack; moves < end; moves++) { - Move move = moves->move; - if (type_of(pos.moved_piece(move)) != PAWN || pos.capture(move) - || !pos.legal(move)) - continue; - pos.do_move(move, st, pos.gives_check(move)); - int v = -Tablebases::probe_wdl(pos, success); - pos.undo_move(move); - if (*success == 0) return 0; - if (v == wdl) - return v == 2 ? 1 : 101; + if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock + return e.baseAddress; + + // Pieces strings in decreasing order for each color, like ("KPP","KR") + std::string fname, w, b; + for (PieceType pt = KING; pt >= PAWN; --pt) { + w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]); + b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]); } - } - dtz = 1 + probe_dtz_table(pos, wdl, success); - if (*success >= 0) { - if (wdl & 1) dtz += 100; - return wdl >= 0 ? dtz : -dtz; - } + const uint8_t TB_MAGIC[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 }, + { 0x71, 0xE8, 0x23, 0x5D } }; + + fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + + (IsWDL ? ".rtbw" : ".rtbz"); + + uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, TB_MAGIC[IsWDL]); + if (data) + e.hasPawns ? do_init(e, e.pawnTable, data) : do_init(e, e.pieceTable, data); + + e.ready.store(true, std::memory_order_release); + return e.baseAddress; +} - if (wdl > 0) { - int best = 0xffff; - for (moves = stack; moves < end; moves++) { - Move move = moves->move; - if (pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN - || !pos.legal(move)) - continue; - pos.do_move(move, st, pos.gives_check(move)); - int v = -Tablebases::probe_dtz(pos, success); - pos.undo_move(move); - if (*success == 0) return 0; - if (v > 0 && v + 1 < best) - best = v + 1; +template::type> +T probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) { + + if (!(pos.pieces() ^ pos.pieces(KING))) + return T(WDLDraw); // KvK + + E* entry = EntryTable.get(pos.material_key()); + + if (!entry || !init(*entry, pos)) + return *result = FAIL, T(); + + return do_probe_table(pos, entry, wdl, result); +} + +// For a position where the side to move has a winning capture it is not necessary +// to store a winning value so the generator treats such positions as "don't cares" +// and tries to assign to it a value that improves the compression ratio. Similarly, +// if the side to move has a drawing capture, then the position is at least drawn. +// If the position is won, then the TB needs to store a win value. But if the +// position is drawn, the TB may store a loss value if that is better for compression. +// All of this means that during probing, the engine must look at captures and probe +// their results and must probe the position itself. The "best" result of these +// probes is the correct result for the position. +// DTZ table don't store values when a following move is a zeroing winning move +// (winning capture or winning pawn move). Also DTZ store wrong values for positions +// where the best move is an ep-move (even if losing). So in all these cases set +// the state to ZEROING_BEST_MOVE. +template +WDLScore search(Position& pos, ProbeState* result) { + + WDLScore value, bestValue = WDLLoss; + StateInfo st; + + auto moveList = MoveList(pos); + size_t totalCount = moveList.size(), moveCount = 0; + + for (const Move& move : moveList) + { + if ( !pos.capture(move) + && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) + continue; + + moveCount++; + + pos.do_move(move, st); + value = -search(pos, result); + pos.undo_move(move); + + if (*result == FAIL) + return WDLDraw; + + if (value > bestValue) + { + bestValue = value; + + if (value >= WDLWin) + { + *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move + return value; + } + } } - return best; - } else { - int best = -1; - if (!pos.checkers()) - end = generate(pos, stack); + + // In case we have already searched all the legal moves we don't have to probe + // the TB because the stored score could be wrong. For instance TB tables + // do not contain information on position with ep rights, so in this case + // the result of probe_wdl_table is wrong. Also in case of only capture + // moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to + // return with ZEROING_BEST_MOVE set. + bool noMoreMoves = (moveCount && moveCount == totalCount); + + if (noMoreMoves) + value = bestValue; else - end = generate(pos, stack); - for (moves = stack; moves < end; moves++) { - int v; - Move move = moves->move; - if (!pos.legal(move)) - continue; - pos.do_move(move, st, pos.gives_check(move)); - if (st.rule50 == 0) { - if (wdl == -2) v = -1; - else { - v = probe_ab(pos, 1, 2, success); - v = (v == 2) ? 0 : -101; + { + value = probe_table(pos, result); + + if (*result == FAIL) + return WDLDraw; + } + + // DTZ stores a "don't care" value if bestValue is a win + if (bestValue >= value) + return *result = ( bestValue > WDLDraw + || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; + + return *result = OK, value; +} + +} // namespace + +void Tablebases::init(const std::string& paths) { + + EntryTable.clear(); + MaxCardinality = 0; + TBFile::Paths = paths; + + if (paths.empty() || paths == "") + return; + + // MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27 + int code = 0; + for (Square s = SQ_A1; s <= SQ_H8; ++s) + if (off_A1H8(s) < 0) + MapB1H1H7[s] = code++; + + // MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..9 + std::vector diagonal; + code = 0; + for (Square s = SQ_A1; s <= SQ_D4; ++s) + if (off_A1H8(s) < 0 && file_of(s) <= FILE_D) + MapA1D1D4[s] = code++; + + else if (!off_A1H8(s) && file_of(s) <= FILE_D) + diagonal.push_back(s); + + // Diagonal squares are encoded as last ones + for (auto s : diagonal) + MapA1D1D4[s] = code++; + + // MapKK[] encodes all the 461 possible legal positions of two kings where + // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4 + // diagonal, the other one shall not to be above the a1-h8 diagonal. + std::vector> bothOnDiagonal; + code = 0; + for (int idx = 0; idx < 10; idx++) + for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1) + if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 + { + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + if ((PseudoAttacks[KING][s1] | s1) & s2) + continue; // Illegal position + + else if (!off_A1H8(s1) && off_A1H8(s2) > 0) + continue; // First on diagonal, second above + + else if (!off_A1H8(s1) && !off_A1H8(s2)) + bothOnDiagonal.push_back(std::make_pair(idx, s2)); + + else + MapKK[idx][s2] = code++; + } + + // Legal positions with both kings on diagonal are encoded as last ones + for (auto p : bothOnDiagonal) + MapKK[p.first][p.second] = code++; + + // Binomial[] stores the Binomial Coefficents using Pascal rule. There + // are Binomial[k][n] ways to choose k elements from a set of n elements. + Binomial[0][0] = 1; + + for (int n = 1; n < 64; n++) // Squares + for (int k = 0; k < 6 && k <= n; ++k) // Pieces + Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0) + + (k < n ? Binomial[k ][n - 1] : 0); + + // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible + // available squares when the leading one is in 's'. Moreover the pawn with + // highest MapPawns[] is the leading pawn, the one nearest the edge and, + // among pawns with same file, the one with lowest rank. + int availableSquares = 47; // Available squares when lead pawn is in a2 + + // Init the tables for the encoding of leading pawns group: with 6-men TB we + // can have up to 4 leading pawns (KPPPPK). + for (int leadPawnsCnt = 1; leadPawnsCnt <= 4; ++leadPawnsCnt) + for (File f = FILE_A; f <= FILE_D; ++f) + { + // Restart the index at every file because TB table is splitted + // by file, so we can reuse the same index for different files. + int idx = 0; + + // Sum all possible combinations for a given file, starting with + // the leading pawn on rank 2 and increasing the rank. + for (Rank r = RANK_2; r <= RANK_7; ++r) + { + Square sq = make_square(f, r); + + // Compute MapPawns[] at first pass. + // If sq is the leading pawn square, any other pawn cannot be + // below or more toward the edge of sq. There are 47 available + // squares when sq = a2 and reduced by 2 for any rank increase + // due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45 + if (leadPawnsCnt == 1) + { + MapPawns[sq] = availableSquares--; + MapPawns[sq ^ 7] = availableSquares--; // Horizontal flip + } + LeadPawnIdx[leadPawnsCnt][sq] = idx; + idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]]; + } + // After a file is traversed, store the cumulated per-file index + LeadPawnsSize[leadPawnsCnt][f] = idx; + } + + for (PieceType p1 = PAWN; p1 < KING; ++p1) { + EntryTable.insert({KING, p1, KING}); + + for (PieceType p2 = PAWN; p2 <= p1; ++p2) { + EntryTable.insert({KING, p1, p2, KING}); + EntryTable.insert({KING, p1, KING, p2}); + + for (PieceType p3 = PAWN; p3 < KING; ++p3) + EntryTable.insert({KING, p1, p2, KING, p3}); + + for (PieceType p3 = PAWN; p3 <= p2; ++p3) { + EntryTable.insert({KING, p1, p2, p3, KING}); + + for (PieceType p4 = PAWN; p4 <= p3; ++p4) + EntryTable.insert({KING, p1, p2, p3, p4, KING}); + + for (PieceType p4 = PAWN; p4 < KING; ++p4) + EntryTable.insert({KING, p1, p2, p3, KING, p4}); + } + + for (PieceType p3 = PAWN; p3 <= p1; ++p3) + for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4) + EntryTable.insert({KING, p1, p2, KING, p3, p4}); } - } else { - v = -Tablebases::probe_dtz(pos, success) - 1; - } - pos.undo_move(move); - if (*success == 0) return 0; - if (v < best) - best = v; } - return best; - } + + sync_cout << "info string Found " << EntryTable.size() << " tablebases" << sync_endl; } -static int wdl_to_dtz[] = { - -1, -101, 0, 101, 1 -}; +// Probe the WDL table for a particular position. +// If *result != FAIL, the probe was successful. +// The return value is from the point of view of the side to move: +// -2 : loss +// -1 : loss, but draw under 50-move rule +// 0 : draw +// 1 : win, but draw under 50-move rule +// 2 : win +WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { + + *result = OK; + return search(pos, result); +} // Probe the DTZ table for a particular position. -// If *success != 0, the probe was successful. +// If *result != FAIL, the probe was successful. // The return value is from the point of view of the side to move: // n < -100 : loss, but draw under 50-move rule // -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0) @@ -578,103 +1434,90 @@ static int wdl_to_dtz[] = { // // In short, if a move is available resulting in dtz + 50-move-counter <= 99, // then do not accept moves leading to dtz + 50-move-counter == 100. -// -int Tablebases::probe_dtz(Position& pos, int *success) -{ - *success = 1; - int v = probe_dtz_no_ep(pos, success); +int Tablebases::probe_dtz(Position& pos, ProbeState* result) { - if (pos.ep_square() == SQ_NONE) - return v; - if (*success == 0) return 0; - - // Now handle en passant. - int v1 = -3; - - ExtMove stack[192]; - ExtMove *moves, *end; - StateInfo st; - - if (!pos.checkers()) - end = generate(pos, stack); - else - end = generate(pos, stack); - - for (moves = stack; moves < end; moves++) { - Move capture = moves->move; - if (type_of(capture) != ENPASSANT - || !pos.legal(capture)) - continue; - pos.do_move(capture, st, pos.gives_check(capture)); - int v0 = -probe_ab(pos, -2, 2, success); - pos.undo_move(capture); - if (*success == 0) return 0; - if (v0 > v1) v1 = v0; - } - if (v1 > -3) { - v1 = wdl_to_dtz[v1 + 2]; - if (v < -100) { - if (v1 >= 0) - v = v1; - } else if (v < 0) { - if (v1 >= 0 || v1 < -100) - v = v1; - } else if (v > 100) { - if (v1 > 0) - v = v1; - } else if (v > 0) { - if (v1 == 1) - v = v1; - } else if (v1 >= 0) { - v = v1; - } else { - for (moves = stack; moves < end; moves++) { - Move move = moves->move; - if (type_of(move) == ENPASSANT) continue; - if (pos.legal(move)) break; - } - if (moves == end && !pos.checkers()) { - end = generate(pos, end); - for (; moves < end; moves++) { - Move move = moves->move; - if (pos.legal(move)) - break; - } - } - if (moves == end) - v = v1; + *result = OK; + WDLScore wdl = search(pos, result); + + if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws + return 0; + + // DTZ stores a 'don't care' value in this case, or even a plain wrong + // one as in case the best move is a losing ep, so it cannot be probed. + if (*result == ZEROING_BEST_MOVE) + return dtz_before_zeroing(wdl); + + int dtz = probe_table(pos, result, wdl); + + if (*result == FAIL) + return 0; + + if (*result != CHANGE_STM) + return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl); + + // DTZ stores results for the other side, so we need to do a 1-ply search and + // find the winning move that minimizes DTZ. + StateInfo st; + int minDTZ = 0xFFFF; + + for (const Move& move : MoveList(pos)) + { + bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; + + pos.do_move(move, st); + + // For zeroing moves we want the dtz of the move _before_ doing it, + // otherwise we will get the dtz of the next move sequence. Search the + // position after the move to get the score sign (because even in a + // winning position we could make a losing capture or going for a draw). + dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) + : -probe_dtz(pos, result); + + pos.undo_move(move); + + if (*result == FAIL) + return 0; + + // Convert result from 1-ply search. Zeroing moves are already accounted + // by dtz_before_zeroing() that returns the DTZ of the previous move. + if (!zeroing) + dtz += sign_of(dtz); + + // Skip the draws and if we are winning only pick positive dtz + if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl)) + minDTZ = dtz; } - } - return v; + // Special handle a mate position, when there are no legal moves, in this + // case return value is somewhat arbitrary, so stick to the original TB code + // that returns -1 in this case. + return minDTZ == 0xFFFF ? -1 : minDTZ; } // Check whether there has been at least one repetition of positions // since the last capture or pawn move. static int has_repeated(StateInfo *st) { - while (1) { - int i = 4, e = std::min(st->rule50, st->pliesFromNull); - if (e < i) - return 0; - StateInfo *stp = st->previous->previous; - do { - stp = stp->previous->previous; - if (stp->key == st->key) - return 1; - i += 2; - } while (i <= e); - st = st->previous; - } -} + while (1) { + int i = 4, e = std::min(st->rule50, st->pliesFromNull); -static Value wdl_to_Value[5] = { - -VALUE_MATE + MAX_PLY + 1, - VALUE_DRAW - 2, - VALUE_DRAW, - VALUE_DRAW + 2, - VALUE_MATE - MAX_PLY - 1 -}; + if (e < i) + return 0; + + StateInfo *stp = st->previous->previous; + + do { + stp = stp->previous->previous; + + if (stp->key == st->key) + return 1; + + i += 2; + } while (i <= e); + + st = st->previous; + } +} // Use the DTZ tables to filter out moves that don't preserve the win or draw. // If the position is lost, but DTZ is fairly high, only keep moves that @@ -684,103 +1527,130 @@ static Value wdl_to_Value[5] = { // no moves were filtered out. bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score) { - int success; - - int dtz = probe_dtz(pos, &success); - if (!success) return false; - - StateInfo st; - - // Probe each move. - for (size_t i = 0; i < rootMoves.size(); i++) { - Move move = rootMoves[i].pv[0]; - pos.do_move(move, st, pos.gives_check(move)); - int v = 0; - if (pos.checkers() && dtz > 0) { - ExtMove s[192]; - if (generate(pos, s) == s) - v = 1; - } - if (!v) { - if (st.rule50 != 0) { - v = -Tablebases::probe_dtz(pos, &success); - if (v > 0) v++; - else if (v < 0) v--; - } else { - v = -Tablebases::probe_wdl(pos, &success); - v = wdl_to_dtz[v + 2]; - } - } - pos.undo_move(move); - if (!success) return false; - rootMoves[i].score = (Value)v; - } + assert(rootMoves.size()); - // Obtain 50-move counter for the root position. - // In Stockfish there seems to be no clean way, so we do it like this: - int cnt50 = st.previous->rule50; - - // Use 50-move counter to determine whether the root position is - // won, lost or drawn. - int wdl = 0; - if (dtz > 0) - wdl = (dtz + cnt50 <= 100) ? 2 : 1; - else if (dtz < 0) - wdl = (-dtz + cnt50 <= 100) ? -2 : -1; - - // Determine the score to report to the user. - score = wdl_to_Value[wdl + 2]; - // If the position is winning or losing, but too few moves left, adjust the - // score to show how close it is to winning or losing. - // NOTE: int(PawnValueEg) is used as scaling factor in score_to_uci(). - if (wdl == 1 && dtz <= 100) - score = (Value)(((200 - dtz - cnt50) * int(PawnValueEg)) / 200); - else if (wdl == -1 && dtz >= -100) - score = -(Value)(((200 + dtz - cnt50) * int(PawnValueEg)) / 200); - - // Now be a bit smart about filtering out moves. - size_t j = 0; - if (dtz > 0) { // winning (or 50-move rule draw) - int best = 0xffff; - for (size_t i = 0; i < rootMoves.size(); i++) { - int v = rootMoves[i].score; - if (v > 0 && v < best) - best = v; - } - int max = best; - // If the current phase has not seen repetitions, then try all moves - // that stay safely within the 50-move budget, if there are any. - if (!has_repeated(st.previous) && best + cnt50 <= 99) - max = 99 - cnt50; - for (size_t i = 0; i < rootMoves.size(); i++) { - int v = rootMoves[i].score; - if (v > 0 && v <= max) - rootMoves[j++] = rootMoves[i]; - } - } else if (dtz < 0) { // losing (or 50-move rule draw) - int best = 0; - for (size_t i = 0; i < rootMoves.size(); i++) { - int v = rootMoves[i].score; - if (v < best) - best = v; - } - // Try all moves, unless we approach or have a 50-move rule draw. - if (-best * 2 + cnt50 < 100) - return true; - for (size_t i = 0; i < rootMoves.size(); i++) { - if (rootMoves[i].score == best) - rootMoves[j++] = rootMoves[i]; + ProbeState result; + int dtz = probe_dtz(pos, &result); + + if (result == FAIL) + return false; + + StateInfo st; + + // Probe each move + for (size_t i = 0; i < rootMoves.size(); ++i) { + Move move = rootMoves[i].pv[0]; + pos.do_move(move, st); + int v = 0; + + if (pos.checkers() && dtz > 0) { + ExtMove s[MAX_MOVES]; + + if (generate(pos, s) == s) + v = 1; + } + + if (!v) { + if (st.rule50 != 0) { + v = -probe_dtz(pos, &result); + + if (v > 0) + ++v; + else if (v < 0) + --v; + } else { + v = -probe_wdl(pos, &result); + v = dtz_before_zeroing(WDLScore(v)); + } + } + + pos.undo_move(move); + + if (result == FAIL) + return false; + + rootMoves[i].score = (Value)v; } - } else { // drawing - // Try all moves that preserve the draw. - for (size_t i = 0; i < rootMoves.size(); i++) { - if (rootMoves[i].score == 0) - rootMoves[j++] = rootMoves[i]; + + // Obtain 50-move counter for the root position. + // In Stockfish there seems to be no clean way, so we do it like this: + int cnt50 = st.previous ? st.previous->rule50 : 0; + + // Use 50-move counter to determine whether the root position is + // won, lost or drawn. + WDLScore wdl = WDLDraw; + + if (dtz > 0) + wdl = (dtz + cnt50 <= 100) ? WDLWin : WDLCursedWin; + else if (dtz < 0) + wdl = (-dtz + cnt50 <= 100) ? WDLLoss : WDLBlessedLoss; + + // Determine the score to report to the user. + score = WDL_to_value[wdl + 2]; + + // If the position is winning or losing, but too few moves left, adjust the + // score to show how close it is to winning or losing. + // NOTE: int(PawnValueEg) is used as scaling factor in score_to_uci(). + if (wdl == WDLCursedWin && dtz <= 100) + score = (Value)(((200 - dtz - cnt50) * int(PawnValueEg)) / 200); + else if (wdl == WDLBlessedLoss && dtz >= -100) + score = -(Value)(((200 + dtz - cnt50) * int(PawnValueEg)) / 200); + + // Now be a bit smart about filtering out moves. + size_t j = 0; + + if (dtz > 0) { // winning (or 50-move rule draw) + int best = 0xffff; + + for (size_t i = 0; i < rootMoves.size(); ++i) { + int v = rootMoves[i].score; + + if (v > 0 && v < best) + best = v; + } + + int max = best; + + // If the current phase has not seen repetitions, then try all moves + // that stay safely within the 50-move budget, if there are any. + if (!has_repeated(st.previous) && best + cnt50 <= 99) + max = 99 - cnt50; + + for (size_t i = 0; i < rootMoves.size(); ++i) { + int v = rootMoves[i].score; + + if (v > 0 && v <= max) + rootMoves[j++] = rootMoves[i]; + } + } else if (dtz < 0) { // losing (or 50-move rule draw) + int best = 0; + + for (size_t i = 0; i < rootMoves.size(); ++i) { + int v = rootMoves[i].score; + + if (v < best) + best = v; + } + + // Try all moves, unless we approach or have a 50-move rule draw. + if (-best * 2 + cnt50 < 100) + return true; + + for (size_t i = 0; i < rootMoves.size(); ++i) { + if (rootMoves[i].score == best) + rootMoves[j++] = rootMoves[i]; + } + } else { // drawing + // Try all moves that preserve the draw. + for (size_t i = 0; i < rootMoves.size(); ++i) { + if (rootMoves[i].score == 0) + rootMoves[j++] = rootMoves[i]; + } } - } - rootMoves.resize(j, Search::RootMove(MOVE_NONE)); - return true; + rootMoves.resize(j, Search::RootMove(MOVE_NONE)); + + return true; } // Use the WDL tables to filter out moves that don't preserve the win or draw. @@ -790,35 +1660,43 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, Value& // no moves were filtered out. bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score) { - int success; - - int wdl = Tablebases::probe_wdl(pos, &success); - if (!success) return false; - score = wdl_to_Value[wdl + 2]; - - StateInfo st; - - int best = -2; - - // Probe each move. - for (size_t i = 0; i < rootMoves.size(); i++) { - Move move = rootMoves[i].pv[0]; - pos.do_move(move, st, pos.gives_check(move)); - int v = -Tablebases::probe_wdl(pos, &success); - pos.undo_move(move); - if (!success) return false; - rootMoves[i].score = (Value)v; - if (v > best) - best = v; - } + ProbeState result; - size_t j = 0; - for (size_t i = 0; i < rootMoves.size(); i++) { - if (rootMoves[i].score == best) - rootMoves[j++] = rootMoves[i]; - } - rootMoves.resize(j, Search::RootMove(MOVE_NONE)); + WDLScore wdl = Tablebases::probe_wdl(pos, &result); - return true; -} + if (result == FAIL) + return false; + + score = WDL_to_value[wdl + 2]; + + StateInfo st; + int best = WDLLoss; + + // Probe each move + for (size_t i = 0; i < rootMoves.size(); ++i) { + Move move = rootMoves[i].pv[0]; + pos.do_move(move, st); + WDLScore v = -Tablebases::probe_wdl(pos, &result); + pos.undo_move(move); + + if (result == FAIL) + return false; + + rootMoves[i].score = (Value)v; + + if (v > best) + best = v; + } + + size_t j = 0; + + for (size_t i = 0; i < rootMoves.size(); ++i) { + if (rootMoves[i].score == best) + rootMoves[j++] = rootMoves[i]; + } + + rootMoves.resize(j, Search::RootMove(MOVE_NONE)); + + return true; +} diff --git a/Engines/Linux64/stockfish/src/syzygy/tbprobe.h b/Engines/Linux64/stockfish/src/syzygy/tbprobe.h index b23fdf6..287b617 100644 --- a/Engines/Linux64/stockfish/src/syzygy/tbprobe.h +++ b/Engines/Linux64/stockfish/src/syzygy/tbprobe.h @@ -1,19 +1,79 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (c) 2013 Ronald de Man + Copyright (C) 2016-2018 Marco Costalba, Lucas Braesch + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + #ifndef TBPROBE_H #define TBPROBE_H +#include + #include "../search.h" namespace Tablebases { +enum WDLScore { + WDLLoss = -2, // Loss + WDLBlessedLoss = -1, // Loss, but draw under 50-move rule + WDLDraw = 0, // Draw + WDLCursedWin = 1, // Win, but draw under 50-move rule + WDLWin = 2, // Win + + WDLScoreNone = -1000 +}; + +// Possible states after a probing operation +enum ProbeState { + FAIL = 0, // Probe failed (missing file table) + OK = 1, // Probe succesful + CHANGE_STM = -1, // DTZ should check the other side + ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) +}; + extern int MaxCardinality; -void init(const std::string& path); -int probe_wdl(Position& pos, int *success); -int probe_dtz(Position& pos, int *success); +void init(const std::string& paths); +WDLScore probe_wdl(Position& pos, ProbeState* result); +int probe_dtz(Position& pos, ProbeState* result); bool root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score); void filter_root_moves(Position& pos, Search::RootMoves& rootMoves); +inline std::ostream& operator<<(std::ostream& os, const WDLScore v) { + + os << (v == WDLLoss ? "Loss" : + v == WDLBlessedLoss ? "Blessed loss" : + v == WDLDraw ? "Draw" : + v == WDLCursedWin ? "Cursed win" : + v == WDLWin ? "Win" : "None"); + + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const ProbeState v) { + + os << (v == FAIL ? "Failed" : + v == OK ? "Success" : + v == CHANGE_STM ? "Probed opponent side" : + v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None"); + + return os; +} + } #endif diff --git a/Engines/Linux64/stockfish/src/thread.cpp b/Engines/Linux64/stockfish/src/thread.cpp index 1f1490a..97beb58 100644 --- a/Engines/Linux64/stockfish/src/thread.cpp +++ b/Engines/Linux64/stockfish/src/thread.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,172 +29,144 @@ ThreadPool Threads; // Global object -/// Thread constructor launches the thread and then waits until it goes to sleep -/// in idle_loop(). -Thread::Thread() { +/// Thread constructor launches the thread and waits until it goes to sleep +/// in idle_loop(). Note that 'searching' and 'exit' should be alredy set. - resetCalls = exit = false; - maxPly = callsCnt = 0; - tbHits = 0; - history.clear(); - counterMoves.clear(); - idx = Threads.size(); // Start from 0 +Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { - std::unique_lock lk(mutex); - searching = true; - nativeThread = std::thread(&Thread::idle_loop, this); - sleepCondition.wait(lk, [&]{ return !searching; }); + wait_for_search_finished(); } -/// Thread destructor waits for thread termination before returning +/// Thread destructor wakes up the thread in idle_loop() and waits +/// for its termination. Thread should be already waiting. Thread::~Thread() { - mutex.lock(); + assert(!searching); + exit = true; - sleepCondition.notify_one(); - mutex.unlock(); - nativeThread.join(); + start_searching(); + stdThread.join(); } -/// Thread::wait_for_search_finished() waits on sleep condition -/// until not searching +/// Thread::clear() reset histories, usually before a new game -void Thread::wait_for_search_finished() { +void Thread::clear() { - std::unique_lock lk(mutex); - sleepCondition.wait(lk, [&]{ return !searching; }); -} + counterMoves.fill(MOVE_NONE); + mainHistory.fill(0); + captureHistory.fill(0); + for (auto& to : contHistory) + for (auto& h : to) + h.fill(0); -/// Thread::wait() waits on sleep condition until condition is true + contHistory[NO_PIECE][0].fill(Search::CounterMovePruneThreshold - 1); +} -void Thread::wait(std::atomic_bool& condition) { +/// Thread::start_searching() wakes up the thread that will start the search - std::unique_lock lk(mutex); - sleepCondition.wait(lk, [&]{ return bool(condition); }); +void Thread::start_searching() { + + std::lock_guard lk(mutex); + searching = true; + cv.notify_one(); // Wake up the thread in idle_loop() } -/// Thread::start_searching() wakes up the thread that will start the search +/// Thread::wait_for_search_finished() blocks on the condition variable +/// until the thread has finished searching. -void Thread::start_searching(bool resume) { +void Thread::wait_for_search_finished() { std::unique_lock lk(mutex); - - if (!resume) - searching = true; - - sleepCondition.notify_one(); + cv.wait(lk, [&]{ return !searching; }); } -/// Thread::idle_loop() is where the thread is parked when it has no work to do +/// Thread::idle_loop() is where the thread is parked, blocked on the +/// condition variable, when it has no work to do. void Thread::idle_loop() { - while (!exit) + // If OS already scheduled us on a different group than 0 then don't overwrite + // the choice, eventually we are one of many one-threaded processes running on + // some Windows NUMA hardware, for instance in fishtest. To make it simple, + // just check if running threads are below a threshold, in this case all this + // NUMA machinery is not needed. + if (Options["Threads"] >= 8) + WinProcGroup::bindThisThread(idx); + + while (true) { std::unique_lock lk(mutex); - searching = false; + cv.notify_one(); // Wake up anyone waiting for search finished + cv.wait(lk, [&]{ return searching; }); - while (!searching && !exit) - { - sleepCondition.notify_one(); // Wake up any waiting thread - sleepCondition.wait(lk); - } + if (exit) + return; lk.unlock(); - if (!exit) - search(); + search(); } } +/// ThreadPool::set() creates/destroys threads to match the requested number. +/// Created and launced threads wil go immediately to sleep in idle_loop. +/// Upon resizing, threads are recreated to allow for binding if necessary. -/// ThreadPool::init() creates and launches requested threads that will go -/// immediately to sleep. We cannot use a constructor because Threads is a -/// static object and we need a fully initialized engine at this point due to -/// allocation of Endgames in the Thread constructor. - -void ThreadPool::init() { - - push_back(new MainThread); - read_uci_options(); -} - - -/// ThreadPool::exit() terminates threads before the program exits. Cannot be -/// done in destructor because threads must be terminated before deleting any -/// static objects while still in main(). - -void ThreadPool::exit() { - - while (size()) - delete back(), pop_back(); -} - - -/// ThreadPool::read_uci_options() updates internal threads parameters from the -/// corresponding UCI options and creates/destroys threads to match requested -/// number. Thread objects are dynamically allocated. +void ThreadPool::set(size_t requested) { -void ThreadPool::read_uci_options() { + if (size() > 0) { // destroy any existing thread(s) + main()->wait_for_search_finished(); - size_t requested = Options["Threads"]; - - assert(requested > 0); + while (size() > 0) + delete back(), pop_back(); + } - while (size() < requested) - push_back(new Thread); + if (requested > 0) { // create new thread(s) + push_back(new MainThread(0)); - while (size() > requested) - delete back(), pop_back(); + while (size() < requested) + push_back(new Thread(size())); + clear(); + } } +/// ThreadPool::clear() sets threadPool data to initial values. -/// ThreadPool::nodes_searched() returns the number of nodes searched - -uint64_t ThreadPool::nodes_searched() const { +void ThreadPool::clear() { - uint64_t nodes = 0; for (Thread* th : *this) - nodes += th->rootPos.nodes_searched(); - return nodes; -} - - -/// ThreadPool::tb_hits() returns the number of TB hits + th->clear(); -uint64_t ThreadPool::tb_hits() const { - - uint64_t hits = 0; - for (Thread* th : *this) - hits += th->tbHits; - return hits; + main()->callsCnt = 0; + main()->previousScore = VALUE_INFINITE; + main()->previousTimeReduction = 1; } - -/// ThreadPool::start_thinking() wakes up the main thread sleeping in idle_loop() -/// and starts a new search, then returns immediately. +/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and +/// returns immediately. Main thread will wake up other threads and start the search. void ThreadPool::start_thinking(Position& pos, StateListPtr& states, - const Search::LimitsType& limits) { + const Search::LimitsType& limits, bool ponderMode) { main()->wait_for_search_finished(); - Search::Signals.stopOnPonderhit = Search::Signals.stop = false; + stopOnPonderhit = stop = false; + ponder = ponderMode; Search::Limits = limits; Search::RootMoves rootMoves; for (const auto& m : MoveList(pos)) if ( limits.searchmoves.empty() || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) - rootMoves.push_back(Search::RootMove(m)); + rootMoves.emplace_back(m); if (!rootMoves.empty()) Tablebases::filter_root_moves(pos, rootMoves); @@ -206,18 +178,22 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, if (states.get()) setupStates = std::move(states); // Ownership transfer, states is now empty + // We use Position::set() to set root position across threads. But there are + // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot + // be deduced from a fen string, so set() clears them and to not lose the info + // we need to backup and later restore setupStates->back(). Note that setupStates + // is shared by threads but is accessed in read-only mode. StateInfo tmp = setupStates->back(); - for (Thread* th : Threads) + for (Thread* th : *this) { - th->maxPly = 0; - th->tbHits = 0; - th->rootDepth = DEPTH_ZERO; + th->nodes = th->tbHits = th->nmp_ply = th->nmp_odd = 0; + th->rootDepth = th->completedDepth = DEPTH_ZERO; th->rootMoves = rootMoves; th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th); } - setupStates->back() = tmp; // Restore st->previous, cleared by Position::set() + setupStates->back() = tmp; main()->start_searching(); } diff --git a/Engines/Linux64/stockfish/src/thread.h b/Engines/Linux64/stockfish/src/thread.h index d1165bb..1397449 100644 --- a/Engines/Linux64/stockfish/src/thread.h +++ b/Engines/Linux64/stockfish/src/thread.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,7 +22,6 @@ #define THREAD_H_INCLUDED #include -#include #include #include #include @@ -36,74 +35,87 @@ #include "thread_win32.h" -/// Thread struct keeps together all the thread-related stuff. We also use -/// per-thread pawn and material hash tables so that once we get a pointer to an -/// entry its life time is unlimited and we don't have to care about someone -/// changing the entry under our feet. +/// Thread class keeps together all the thread-related stuff. We use +/// per-thread pawn and material hash tables so that once we get a +/// pointer to an entry its life time is unlimited and we don't have +/// to care about someone changing the entry under our feet. class Thread { - std::thread nativeThread; Mutex mutex; - ConditionVariable sleepCondition; - bool exit, searching; + ConditionVariable cv; + size_t idx; + bool exit = false, searching = true; // Set before starting std::thread + std::thread stdThread; public: - Thread(); + explicit Thread(size_t); virtual ~Thread(); virtual void search(); + void clear(); void idle_loop(); - void start_searching(bool resume = false); + void start_searching(); void wait_for_search_finished(); - void wait(std::atomic_bool& b); Pawns::Table pawnsTable; Material::Table materialTable; Endgames endgames; - size_t idx, PVIdx; - int maxPly, callsCnt; - uint64_t tbHits; + size_t PVIdx; + int selDepth, nmp_ply, nmp_odd; + std::atomic nodes, tbHits; Position rootPos; Search::RootMoves rootMoves; - Depth rootDepth; - Depth completedDepth; - std::atomic_bool resetCalls; - HistoryStats history; - MoveStats counterMoves; - FromToStats fromTo; - CounterMoveHistoryStats counterMoveHistory; + Depth rootDepth, completedDepth; + CounterMoveHistory counterMoves; + ButterflyHistory mainHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory contHistory; }; -/// MainThread is a derived class with a specific overload for the main thread +/// MainThread is a derived class specific for main thread struct MainThread : public Thread { - virtual void search(); - bool easyMovePlayed, failedLow; - double bestMoveChanges; + using Thread::Thread; + + void search() override; + void check_time(); + + bool failedLow; + double bestMoveChanges, previousTimeReduction; Value previousScore; + int callsCnt; }; /// ThreadPool struct handles all the threads-related stuff like init, starting, /// parking and, most importantly, launching a thread. All the access to threads -/// data is done through this class. +/// is done through this class. struct ThreadPool : public std::vector { - void init(); // No constructor and destructor, threads rely on globals that should - void exit(); // be initialized and valid during the whole thread lifetime. + void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); + void clear(); + void set(size_t); - MainThread* main() { return static_cast(at(0)); } - void start_thinking(Position&, StateListPtr&, const Search::LimitsType&); - void read_uci_options(); - uint64_t nodes_searched() const; - uint64_t tb_hits() const; + MainThread* main() const { return static_cast(front()); } + uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } + uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } + + std::atomic_bool stop, ponder, stopOnPonderhit; private: StateListPtr setupStates; + + uint64_t accumulate(std::atomic Thread::* member) const { + + uint64_t sum = 0; + for (Thread* th : *this) + sum += (th->*member).load(std::memory_order_relaxed); + return sum; + } }; extern ThreadPool Threads; diff --git a/Engines/Linux64/stockfish/src/thread_win32.h b/Engines/Linux64/stockfish/src/thread_win32.h index 47516c6..5da186a 100644 --- a/Engines/Linux64/stockfish/src/thread_win32.h +++ b/Engines/Linux64/stockfish/src/thread_win32.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/stockfish/src/timeman.cpp b/Engines/Linux64/stockfish/src/timeman.cpp index 6d3b731..035fe33 100644 --- a/Engines/Linux64/stockfish/src/timeman.cpp +++ b/Engines/Linux64/stockfish/src/timeman.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/stockfish/src/timeman.h b/Engines/Linux64/stockfish/src/timeman.h index 9930a4b..f4e3a95 100644 --- a/Engines/Linux64/stockfish/src/timeman.h +++ b/Engines/Linux64/stockfish/src/timeman.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Linux64/stockfish/src/tt.cpp b/Engines/Linux64/stockfish/src/tt.cpp index f5b72ba..25f1cd0 100644 --- a/Engines/Linux64/stockfish/src/tt.cpp +++ b/Engines/Linux64/stockfish/src/tt.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,7 +33,7 @@ TranspositionTable TT; // Our global transposition table void TranspositionTable::resize(size_t mbSize) { - size_t newClusterCount = size_t(1) << msb((mbSize * 1024 * 1024) / sizeof(Cluster)); + size_t newClusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); if (newClusterCount == clusterCount) return; @@ -41,7 +41,7 @@ void TranspositionTable::resize(size_t mbSize) { clusterCount = newClusterCount; free(mem); - mem = calloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1, 1); + mem = malloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1); if (!mem) { @@ -51,6 +51,7 @@ void TranspositionTable::resize(size_t mbSize) { } table = (Cluster*)((uintptr_t(mem) + CacheLineSize - 1) & ~(CacheLineSize - 1)); + clear(); } diff --git a/Engines/Linux64/stockfish/src/tt.h b/Engines/Linux64/stockfish/src/tt.h index 677f38e..ca7dfbf 100644 --- a/Engines/Linux64/stockfish/src/tt.h +++ b/Engines/Linux64/stockfish/src/tt.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -104,9 +104,9 @@ class TranspositionTable { void resize(size_t mbSize); void clear(); - // The lowest order bits of the key are used to get the index of the cluster + // The 32 lowest order bits of the key are used to get the index of the cluster TTEntry* first_entry(const Key key) const { - return &table[(size_t)key & (clusterCount - 1)].entry[0]; + return &table[(uint32_t(key) * uint64_t(clusterCount)) >> 32].entry[0]; } private: diff --git a/Engines/Linux64/stockfish/src/types.h b/Engines/Linux64/stockfish/src/types.h index 519f6af..009a933 100644 --- a/Engines/Linux64/stockfish/src/types.h +++ b/Engines/Linux64/stockfish/src/types.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -76,7 +76,7 @@ # include // Header for _pext_u64() intrinsic # define pext(b, m) _pext_u64(b, m) #else -# define pext(b, m) (0) +# define pext(b, m) 0 #endif #ifdef USE_POPCNT @@ -128,7 +128,7 @@ enum MoveType { }; enum Color { - WHITE, BLACK, NO_COLOR, COLOR_NB = 2 + WHITE, BLACK, COLOR_NB = 2 }; enum CastlingSide { @@ -146,7 +146,7 @@ enum CastlingRight { }; template struct MakeCastling { - static const CastlingRight + static constexpr CastlingRight right = C == WHITE ? S == QUEEN_SIDE ? WHITE_OOO : WHITE_OO : S == QUEEN_SIDE ? BLACK_OOO : BLACK_OO; }; @@ -183,11 +183,11 @@ enum Value : int { VALUE_MATE_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + 2 * MAX_PLY, - PawnValueMg = 188, PawnValueEg = 248, - KnightValueMg = 753, KnightValueEg = 832, - BishopValueMg = 826, BishopValueEg = 897, - RookValueMg = 1285, RookValueEg = 1371, - QueenValueMg = 2513, QueenValueEg = 2650, + PawnValueMg = 171, PawnValueEg = 240, + KnightValueMg = 764, KnightValueEg = 848, + BishopValueMg = 826, BishopValueEg = 891, + RookValueMg = 1282, RookValueEg = 1373, + QueenValueMg = 2526, QueenValueEg = 2646, MidgameLimit = 15258, EndgameLimit = 3915 }; @@ -195,6 +195,7 @@ enum Value : int { enum PieceType { NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, ALL_PIECES = 0, + QUEEN_DIAGONAL = 7, PIECE_TYPE_NB = 8 }; @@ -205,11 +206,9 @@ enum Piece { PIECE_NB = 16 }; -const Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; extern Value PieceValue[PHASE_NB][PIECE_NB]; -enum Depth { +enum Depth : int { ONE_PLY = 1, @@ -224,7 +223,7 @@ enum Depth { static_assert(!(ONE_PLY & (ONE_PLY - 1)), "ONE_PLY is not a power of 2"); -enum Square { +enum Square : int { SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, @@ -235,12 +234,14 @@ enum Square { SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, SQ_NONE, - SQUARE_NB = 64, + SQUARE_NB = 64 +}; +enum Direction : int { NORTH = 8, EAST = 1, - SOUTH = -8, - WEST = -1, + SOUTH = -NORTH, + WEST = -EAST, NORTH_EAST = NORTH + EAST, SOUTH_EAST = SOUTH + EAST, @@ -263,7 +264,7 @@ enum Rank : int { /// care to avoid left-shifting a signed int to avoid undefined behavior. enum Score : int { SCORE_ZERO }; -inline Score make_score(int mg, int eg) { +constexpr Score make_score(int mg, int eg) { return Score((int)((unsigned int)eg << 16) + mg); } @@ -271,97 +272,123 @@ inline Score make_score(int mg, int eg) { /// according to the standard a simple cast to short is implementation defined /// and so is a right shift of a signed integer. inline Value eg_value(Score s) { - union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) }; return Value(eg.s); } inline Value mg_value(Score s) { - union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) }; return Value(mg.s); } -#define ENABLE_BASE_OPERATORS_ON(T) \ -inline T operator+(T d1, T d2) { return T(int(d1) + int(d2)); } \ -inline T operator-(T d1, T d2) { return T(int(d1) - int(d2)); } \ -inline T operator*(int i, T d) { return T(i * int(d)); } \ -inline T operator*(T d, int i) { return T(int(d) * i); } \ -inline T operator-(T d) { return T(-int(d)); } \ -inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; } \ -inline T& operator-=(T& d1, T d2) { return d1 = d1 - d2; } \ -inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } - -#define ENABLE_FULL_OPERATORS_ON(T) \ -ENABLE_BASE_OPERATORS_ON(T) \ -inline T& operator++(T& d) { return d = T(int(d) + 1); } \ -inline T& operator--(T& d) { return d = T(int(d) - 1); } \ -inline T operator/(T d, int i) { return T(int(d) / i); } \ -inline int operator/(T d1, T d2) { return int(d1) / int(d2); } \ +#define ENABLE_BASE_OPERATORS_ON(T) \ +constexpr T operator+(T d1, T d2) { return T(int(d1) + int(d2)); } \ +constexpr T operator-(T d1, T d2) { return T(int(d1) - int(d2)); } \ +constexpr T operator-(T d) { return T(-int(d)); } \ +inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; } \ +inline T& operator-=(T& d1, T d2) { return d1 = d1 - d2; } + +#define ENABLE_INCR_OPERATORS_ON(T) \ +inline T& operator++(T& d) { return d = T(int(d) + 1); } \ +inline T& operator--(T& d) { return d = T(int(d) - 1); } + +#define ENABLE_FULL_OPERATORS_ON(T) \ +ENABLE_BASE_OPERATORS_ON(T) \ +ENABLE_INCR_OPERATORS_ON(T) \ +constexpr T operator*(int i, T d) { return T(i * int(d)); } \ +constexpr T operator*(T d, int i) { return T(int(d) * i); } \ +constexpr T operator/(T d, int i) { return T(int(d) / i); } \ +constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ +inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } ENABLE_FULL_OPERATORS_ON(Value) -ENABLE_FULL_OPERATORS_ON(PieceType) -ENABLE_FULL_OPERATORS_ON(Piece) -ENABLE_FULL_OPERATORS_ON(Color) ENABLE_FULL_OPERATORS_ON(Depth) -ENABLE_FULL_OPERATORS_ON(Square) -ENABLE_FULL_OPERATORS_ON(File) -ENABLE_FULL_OPERATORS_ON(Rank) +ENABLE_FULL_OPERATORS_ON(Direction) + +ENABLE_INCR_OPERATORS_ON(PieceType) +ENABLE_INCR_OPERATORS_ON(Piece) +ENABLE_INCR_OPERATORS_ON(Color) +ENABLE_INCR_OPERATORS_ON(Square) +ENABLE_INCR_OPERATORS_ON(File) +ENABLE_INCR_OPERATORS_ON(Rank) ENABLE_BASE_OPERATORS_ON(Score) #undef ENABLE_FULL_OPERATORS_ON +#undef ENABLE_INCR_OPERATORS_ON #undef ENABLE_BASE_OPERATORS_ON /// Additional operators to add integers to a Value -inline Value operator+(Value v, int i) { return Value(int(v) + i); } -inline Value operator-(Value v, int i) { return Value(int(v) - i); } +constexpr Value operator+(Value v, int i) { return Value(int(v) + i); } +constexpr Value operator-(Value v, int i) { return Value(int(v) - i); } inline Value& operator+=(Value& v, int i) { return v = v + i; } inline Value& operator-=(Value& v, int i) { return v = v - i; } +/// Additional operators to add a Direction to a Square +inline Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } +inline Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } +inline Square& operator+=(Square &s, Direction d) { return s = s + d; } +inline Square& operator-=(Square &s, Direction d) { return s = s - d; } + /// Only declared but not defined. We don't want to multiply two scores due to /// a very high risk of overflow. So user should explicitly convert to integer. -inline Score operator*(Score s1, Score s2); +Score operator*(Score, Score) = delete; /// Division of a Score must be handled separately for each term inline Score operator/(Score s, int i) { return make_score(mg_value(s) / i, eg_value(s) / i); } -inline Color operator~(Color c) { +/// Multiplication of a Score by an integer. We check for overflow in debug mode. +inline Score operator*(Score s, int i) { + + Score result = Score(int(s) * i); + + assert(eg_value(result) == (i * eg_value(s))); + assert(mg_value(result) == (i * mg_value(s))); + assert((i == 0) || (result / i) == s ); + + return result; +} + +constexpr Color operator~(Color c) { return Color(c ^ BLACK); // Toggle color } -inline Square operator~(Square s) { +constexpr Square operator~(Square s) { return Square(s ^ SQ_A8); // Vertical flip SQ_A1 -> SQ_A8 } -inline Piece operator~(Piece pc) { +constexpr File operator~(File f) { + return File(f ^ FILE_H); // Horizontal flip FILE_A -> FILE_H +} + +constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT } -inline CastlingRight operator|(Color c, CastlingSide s) { +constexpr CastlingRight operator|(Color c, CastlingSide s) { return CastlingRight(WHITE_OO << ((s == QUEEN_SIDE) + 2 * c)); } -inline Value mate_in(int ply) { +constexpr Value mate_in(int ply) { return VALUE_MATE - ply; } -inline Value mated_in(int ply) { +constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; } -inline Square make_square(File f, Rank r) { +constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); } -inline Piece make_piece(Color c, PieceType pt) { +constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); } -inline PieceType type_of(Piece pc) { +constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } @@ -370,27 +397,27 @@ inline Color color_of(Piece pc) { return Color(pc >> 3); } -inline bool is_ok(Square s) { +constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } -inline File file_of(Square s) { +constexpr File file_of(Square s) { return File(s & 7); } -inline Rank rank_of(Square s) { +constexpr Rank rank_of(Square s) { return Rank(s >> 3); } -inline Square relative_square(Color c, Square s) { +constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); } -inline Rank relative_rank(Color c, Rank r) { +constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); } -inline Rank relative_rank(Color c, Square s) { +constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); } @@ -399,23 +426,27 @@ inline bool opposite_colors(Square s1, Square s2) { return ((s >> 3) ^ s) & 1; } -inline Square pawn_push(Color c) { +constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } -inline Square from_sq(Move m) { +constexpr Square from_sq(Move m) { return Square((m >> 6) & 0x3F); } -inline Square to_sq(Move m) { +constexpr Square to_sq(Move m) { return Square(m & 0x3F); } -inline MoveType type_of(Move m) { +constexpr int from_to(Move m) { + return m & 0xFFF; +} + +constexpr MoveType type_of(Move m) { return MoveType(m & (3 << 14)); } -inline PieceType promotion_type(Move m) { +constexpr PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + KNIGHT); } @@ -424,11 +455,11 @@ inline Move make_move(Square from, Square to) { } template -inline Move make(Square from, Square to, PieceType pt = KNIGHT) { +constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } -inline bool is_ok(Move m) { +constexpr bool is_ok(Move m) { return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE } diff --git a/Engines/Linux64/stockfish/src/uci.cpp b/Engines/Linux64/stockfish/src/uci.cpp index b195b87..adba98d 100644 --- a/Engines/Linux64/stockfish/src/uci.cpp +++ b/Engines/Linux64/stockfish/src/uci.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ along with this program. If not, see . */ +#include #include #include #include @@ -27,30 +28,27 @@ #include "position.h" #include "search.h" #include "thread.h" +#include "tt.h" #include "timeman.h" #include "uci.h" +#include "syzygy/tbprobe.h" using namespace std; -extern void benchmark(const Position& pos, istream& is); +extern vector setup_bench(const Position&, istream&); namespace { // FEN string of the initial position, normal chess const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - // A list to keep track of the position states along the setup moves (from the - // start position to the position just before the search starts). Needed by - // 'draw by repetition' detection. - StateListPtr States(new std::deque(1)); - // position() is called when engine receives the "position" UCI command. // The function sets up the position described in the given FEN string ("fen") // or the starting position ("startpos") and then makes the moves given in the // following move list ("moves"). - void position(Position& pos, istringstream& is) { + void position(Position& pos, istringstream& is, StateListPtr& states) { Move m; string token, fen; @@ -68,14 +66,14 @@ namespace { else return; - States = StateListPtr(new std::deque(1)); - pos.set(fen, Options["UCI_Chess960"], &States->back(), Threads.main()); + states = StateListPtr(new std::deque(1)); // Drop old and create a new one + pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); // Parse move list (if any) while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) { - States->push_back(StateInfo()); - pos.do_move(m, States->back(), pos.gives_check(m)); + states->emplace_back(); + pos.do_move(m, states->back()); } } @@ -108,10 +106,11 @@ namespace { // the thinking time and other parameters from the input string, then starts // the search. - void go(Position& pos, istringstream& is) { + void go(Position& pos, istringstream& is, StateListPtr& states) { Search::LimitsType limits; string token; + bool ponderMode = false; limits.startTime = now(); // As early as possible! @@ -129,10 +128,53 @@ namespace { else if (token == "nodes") is >> limits.nodes; else if (token == "movetime") is >> limits.movetime; else if (token == "mate") is >> limits.mate; + else if (token == "perft") is >> limits.perft; else if (token == "infinite") limits.infinite = 1; - else if (token == "ponder") limits.ponder = 1; + else if (token == "ponder") ponderMode = true; + + Threads.start_thinking(pos, states, limits, ponderMode); + } + + + // bench() is called when engine receives the "bench" command. Firstly + // a list of UCI commands is setup according to bench parameters, then + // it is run one by one printing a summary at the end. + + void bench(Position& pos, istream& args, StateListPtr& states) { + + string token; + uint64_t num, nodes = 0, cnt = 1; + + vector list = setup_bench(pos, args); + num = count_if(list.begin(), list.end(), [](string s) { return s.find("go ") == 0; }); + + TimePoint elapsed = now(); - Threads.start_thinking(pos, States, limits); + for (const auto& cmd : list) + { + istringstream is(cmd); + is >> skipws >> token; + + if (token == "go") + { + cerr << "\nPosition: " << cnt++ << '/' << num << endl; + go(pos, is, states); + Threads.main()->wait_for_search_finished(); + nodes += Threads.nodes_searched(); + } + else if (token == "setoption") setoption(is); + else if (token == "position") position(pos, is, states); + else if (token == "ucinewgame") Search::clear(); + } + + elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' + + dbg_print(); // Just before exiting + + cerr << "\n===========================" + << "\nTotal time (ms) : " << elapsed + << "\nNodes searched : " << nodes + << "\nNodes/second : " << 1000 * nodes / elapsed << endl; } } // namespace @@ -148,8 +190,10 @@ void UCI::loop(int argc, char* argv[]) { Position pos; string token, cmd; + StateListPtr states(new std::deque(1)); + auto uiThread = std::make_shared(0); - pos.set(StartFEN, false, &States->back(), Threads.main()); + pos.set(StartFEN, false, &states->back(), uiThread.get()); for (int i = 1; i < argc; ++i) cmd += std::string(argv[i]) + " "; @@ -160,61 +204,42 @@ void UCI::loop(int argc, char* argv[]) { istringstream is(cmd); - token.clear(); // getline() could return empty or blank line + token.clear(); // Avoid a stale if getline() returns empty or blank line is >> skipws >> token; - // The GUI sends 'ponderhit' to tell us to ponder on the same move the - // opponent has played. In case Signals.stopOnPonderhit is set we are - // waiting for 'ponderhit' to stop the search (for instance because we - // already ran out of time), otherwise we should continue searching but - // switching from pondering to normal search. + // The GUI sends 'ponderhit' to tell us the user has played the expected move. + // So 'ponderhit' will be sent if we were told to ponder on the same move the + // user has played. We should continue searching but switch from pondering to + // normal search. In case Threads.stopOnPonderhit is set we are waiting for + // 'ponderhit' to stop the search, for instance if max search depth is reached. if ( token == "quit" || token == "stop" - || (token == "ponderhit" && Search::Signals.stopOnPonderhit)) - { - Search::Signals.stop = true; - Threads.main()->start_searching(true); // Could be sleeping - } + || (token == "ponderhit" && Threads.stopOnPonderhit)) + Threads.stop = true; + else if (token == "ponderhit") - Search::Limits.ponder = 0; // Switch to normal search + Threads.ponder = false; // Switch to normal search else if (token == "uci") sync_cout << "id name " << engine_info(true) << "\n" << Options << "\nuciok" << sync_endl; - else if (token == "ucinewgame") - { - Search::clear(); - Time.availableNodes = 0; - } - else if (token == "isready") sync_cout << "readyok" << sync_endl; - else if (token == "go") go(pos, is); - else if (token == "position") position(pos, is); else if (token == "setoption") setoption(is); + else if (token == "go") go(pos, is, states); + else if (token == "position") position(pos, is, states); + else if (token == "ucinewgame") Search::clear(); + else if (token == "isready") sync_cout << "readyok" << sync_endl; - // Additional custom non-UCI commands, useful for debugging - else if (token == "flip") pos.flip(); - else if (token == "bench") benchmark(pos, is); - else if (token == "d") sync_cout << pos << sync_endl; - else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; - else if (token == "perft") - { - int depth; - stringstream ss; - - is >> depth; - ss << Options["Hash"] << " " - << Options["Threads"] << " " << depth << " current perft"; - - benchmark(pos, ss); - } + // Additional custom non-UCI commands, mainly for debugging + else if (token == "flip") pos.flip(); + else if (token == "bench") bench(pos, is, states); + else if (token == "d") sync_cout << pos << sync_endl; + else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; else sync_cout << "Unknown command: " << cmd << sync_endl; - } while (token != "quit" && argc == 1); // Passed args have one-shot behaviour - - Threads.main()->wait_for_search_finished(); + } while (token != "quit" && argc == 1); // Command line args are one-shot } @@ -227,6 +252,8 @@ void UCI::loop(int argc, char* argv[]) { string UCI::value(Value v) { + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + stringstream ss; if (abs(v) < VALUE_MATE - MAX_PLY) diff --git a/Engines/Linux64/stockfish/src/uci.h b/Engines/Linux64/stockfish/src/uci.h index 4697877..0b3550b 100644 --- a/Engines/Linux64/stockfish/src/uci.h +++ b/Engines/Linux64/stockfish/src/uci.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -49,7 +49,7 @@ class Option { Option(OnChange = nullptr); Option(bool v, OnChange = nullptr); Option(const char* v, OnChange = nullptr); - Option(int v, int min, int max, OnChange = nullptr); + Option(int v, int minv, int maxv, OnChange = nullptr); Option& operator=(const std::string&); void operator<<(const Option&); diff --git a/Engines/Linux64/stockfish/src/ucioption.cpp b/Engines/Linux64/stockfish/src/ucioption.cpp index ab931bb..87ebaa8 100644 --- a/Engines/Linux64/stockfish/src/ucioption.cpp +++ b/Engines/Linux64/stockfish/src/ucioption.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,7 +39,7 @@ namespace UCI { void on_clear_hash(const Option&) { Search::clear(); } void on_hash_size(const Option& o) { TT.resize(o); } void on_logger(const Option& o) { start_logger(o); } -void on_threads(const Option&) { Threads.read_uci_options(); } +void on_threads(const Option& o) { Threads.set(o); } void on_tb_path(const Option& o) { Tablebases::init(o); } @@ -55,11 +55,12 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const void init(OptionsMap& o) { - const int MaxHashMB = Is64Bit ? 1024 * 1024 : 2048; + // at most 2^32 clusters. + const int MaxHashMB = Is64Bit ? 131072 : 2048; o["Debug Log File"] << Option("", on_logger); - o["Contempt"] << Option(0, -100, 100); - o["Threads"] << Option(1, 1, 128, on_threads); + o["Contempt"] << Option(20, -100, 100); + o["Threads"] << Option(1, 1, 512, on_threads); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Clear Hash"] << Option(on_clear_hash); o["Ponder"] << Option(false); diff --git a/Engines/Windows/_tools/LCEngine.pyd b/Engines/Windows/_tools/LCEngine.pyd deleted file mode 100644 index 07b9d4e..0000000 Binary files a/Engines/Windows/_tools/LCEngine.pyd and /dev/null differ diff --git a/Engines/Windows/_tools/LCEngineV1.pyd b/Engines/Windows/_tools/LCEngineV1.pyd new file mode 100644 index 0000000..89a8a82 Binary files /dev/null and b/Engines/Windows/_tools/LCEngineV1.pyd differ diff --git a/Engines/Windows/mcbrain/.travis.yml b/Engines/Windows/mcbrain/.travis.yml index 87a3e7d..0a214d1 100644 --- a/Engines/Windows/mcbrain/.travis.yml +++ b/Engines/Windows/mcbrain/.travis.yml @@ -18,11 +18,12 @@ matrix: compiler: clang addons: apt: - sources: ['ubuntu-toolchain-r-test'] - packages: ['clang', 'g++-multilib', 'valgrind', 'expect'] + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-trusty-5.0'] + packages: ['clang-5.0', 'llvm-5.0-dev', 'g++-multilib', 'valgrind', 'expect'] env: - - COMPILER=clang++ + - COMPILER=clang++-5.0 - COMP=clang + - LDFLAGS=-fuse-ld=gold - os: osx compiler: gcc @@ -44,30 +45,30 @@ before_script: - cd src script: - # - # checking bench for various build types - # - # obtain reference from git log - - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9][0-9]*\)/\1/g" > git_sig + # Obtain bench reference from git log + - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9]*\).*/\1/g" > git_sig - export benchref=$(cat git_sig) - echo "Reference bench:" $benchref - # verify against reference - - make clean && make ARCH=x86-64 build > /dev/null && ../tests/signature.sh $benchref - - make clean && make ARCH=x86-32 build > /dev/null && ../tests/signature.sh $benchref - # - # perft - # - - make clean && make ARCH=x86-64 build > /dev/null && ../tests/perft.sh # - # reproducible search + # Verify bench number against various builds + - export CXXFLAGS=-Werror + - make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-64 build && ../tests/signature.sh $benchref # - - make clean && make ARCH=x86-64 build > /dev/null && ../tests/reprosearch.sh + # Check perft and reproducible search + - ../tests/perft.sh + - ../tests/reprosearch.sh # - # valgrind + # Valgrind # - - if [ -x "$(command -v valgrind )" ]; then make clean && make ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi + - export CXXFLAGS=-O1 + - if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi + - if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi # - # sanitizer + # Sanitizer # - # use g++-6 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc - - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make ARCH=x86-64 sanitize=yes build > /dev/null && ../tests/instrumented.sh --sanitizer; fi + # Use g++-6 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=thread optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi diff --git a/Engines/Windows/mcbrain/AUTHORS b/Engines/Windows/mcbrain/AUTHORS index 3095efc..9b91b93 100644 --- a/Engines/Windows/mcbrain/AUTHORS +++ b/Engines/Windows/mcbrain/AUTHORS @@ -1,6 +1,5 @@ # Generated with 'git shortlog -sn | cut -c8-', which sorts by commits, manually ordered the first four authors, merged duplicates -Michael Byrne (McBrain) Tord Romstad Marco Costalba (mcostalba) Joona Kiiski (zamar) @@ -95,4 +94,5 @@ ppigazzini renouve sf-x thaspel +unknown diff --git a/Engines/Windows/mcbrain/McBrain-9_x32_old.exe b/Engines/Windows/mcbrain/McBrain-9_x32_old.exe new file mode 100644 index 0000000..b07cd42 Binary files /dev/null and b/Engines/Windows/mcbrain/McBrain-9_x32_old.exe differ diff --git a/Engines/Windows/mcbrain/McBrain-9_x64_bmi2.exe b/Engines/Windows/mcbrain/McBrain-9_x64_bmi2.exe new file mode 100644 index 0000000..47e4bc6 Binary files /dev/null and b/Engines/Windows/mcbrain/McBrain-9_x64_bmi2.exe differ diff --git a/Engines/Windows/mcbrain/Readme.md b/Engines/Windows/mcbrain/Readme.md index e3b36cc..46f2609 100644 --- a/Engines/Windows/mcbrain/Readme.md +++ b/Engines/Windows/mcbrain/Readme.md @@ -98,9 +98,9 @@ for a quick reference. ### Resource For Understanding the Code Base -* [Chessprogramingwiki](https://chessprogramming.wikispaces.com) has good overall chess engines explanations +* [Chess Programming Wiki](https://chessprogramming.wikispaces.com) has good overall chess engines explanations (techniques used here are well explained like hash maps etc), it was -also recommended by the [support at stockfish.](http://support.stockfishchess.org/discussions/questions/1132-how-to-understand-stockfish-sources) +also recommended by the [support team at stockfish.](http://support.stockfishchess.org/discussions/questions/1132-how-to-understand-stockfish-sources) * [Here](https://chessprogramming.wikispaces.com/Stockfish) you can find a set of features and techniques used by stockfish and each of them is explained at the wiki, however, it's a generic way rather than focusing on stockfish's own implementation, but it will still help you. diff --git a/Engines/Windows/mcbrain/SF-McBrain-v40_x32_old.exe b/Engines/Windows/mcbrain/SF-McBrain-v40_x32_old.exe deleted file mode 100644 index 89c489f..0000000 Binary files a/Engines/Windows/mcbrain/SF-McBrain-v40_x32_old.exe and /dev/null differ diff --git a/Engines/Windows/mcbrain/SF-McBrain-v40_x64_bmi2.exe b/Engines/Windows/mcbrain/SF-McBrain-v40_x64_bmi2.exe deleted file mode 100644 index 8aa2c37..0000000 Binary files a/Engines/Windows/mcbrain/SF-McBrain-v40_x64_bmi2.exe and /dev/null differ diff --git a/Engines/Windows/mcbrain/Top CPU Contributors.txt b/Engines/Windows/mcbrain/Top CPU Contributors.txt index 0bb2a92..4c2aa47 100644 --- a/Engines/Windows/mcbrain/Top CPU Contributors.txt +++ b/Engines/Windows/mcbrain/Top CPU Contributors.txt @@ -1,93 +1,132 @@ -Contributors with >10,000 CPU hours as of November 3, 2016 +Contributors with >10,000 CPU hours as of January 23, 2018 Thank you! -Username CPU Hours Games played -cw 220301 16924200 -glinscott 186639 13936027 -fastgm 184045 14608140 -mibere 165859 13563572 -crunchy 160974 14091929 -spams 143806 10956698 -bking_US 136938 10558137 -dsmith 103332 7622414 -BrunoBanani 100008 7448565 -ctoks 99216 7989224 -JojoM 96528 8138437 -vdbergh 88372 6322455 -drabel 75214 6034715 -velislav 71485 5483953 -sqrt2 70825 5471595 -BRAVONE 66265 5356681 -malala 57618 4480635 -psk 54292 4337164 -leszek 52415 4254611 -marrco 51573 4132787 -Freja 48348 3773248 -Thanar 47723 4062940 -Fisherman 46361 3865994 -renouve 46003 3544864 -CSU_Dynasty 45136 4096148 -rap 44619 3219490 -dv8silencer 44175 3961325 -tinker 43975 3261777 -tvijlbrief 42291 2965762 -sunu 41289 3172937 -mhunt 38278 2697512 -Antihistamine 37735 2795761 -finfish 36001 2734928 -brabos 32630 2566008 -jromang 32044 2166097 -robnjr 31781 2726352 -CoffeeOne 29940 2597953 -sterni1971 28924 2737221 -EthanOConnor 28429 2143255 -Pyafue 27266 1986098 -jkiiski 27009 1925255 -biffhero 26557 2033420 -nssy 25231 2037166 -mgrabiak 24000 1974653 -slakovv 23548 2031279 -Sharaf_DG 22175 1790697 -homyur 21415 1705644 -team-oh 20347 1653708 -Zirie 20204 1493227 -nabildanial 19538 1586321 -nesoneg 19306 1493435 -cuistot 19105 1387031 -Patrick_G 19027 1406466 -mhoram 18304 1396701 -rkl 17566 1409460 -ville 17541 1540130 -oryx 17480 1578240 -rstoesser 17264 1335177 -xor12 16786 1492708 -jundery 16786 1115855 -bigpen0r 16700 1287118 -iisiraider 16366 1089410 -davar 16266 1328093 -vdv 16072 1629971 -VoyagerOne 16049 1485459 -Bobo1239 15837 1550883 -DragonLord 15791 1251348 -purplefishies 15602 1106850 -Isidor 14598 1317485 -speedycpu 14215 874201 -OssumOpossum 14078 1029265 -enedene 13378 935618 -bpfliegel 12944 886523 -AdrianSA 12921 924980 -JanErik 12782 1106788 -dju 12600 901552 -jpulman 12015 854815 -ttruscott 11929 976348 -fatmurphy 11726 901134 -ElbertoOne 11641 1082697 -j3corre 11638 973654 -chris 11450 1228430 -pb00067 11248 1021031 -modolief 11185 926456 -Dark_wizzie 10933 1017910 -SC 10637 925516 -Thomas A. 10485 736094 -mschmidt 10354 818594 -infinity 10020 746397 +Username CPU Hours Games played +mibere 518300 41835669 +crunchy 375564 29121434 +cw 371664 28748719 +fastgm 299773 20765374 +JojoM 220590 15299913 +glinscott 204517 13932027 +bking_US 187568 12233168 +ctoks 169342 13475495 +spams 149531 10940322 +Thanar 137015 11714855 +velislav 127305 10047749 +vdbergh 121741 9056874 +malala 117291 8126488 +vdv 117218 8289983 +leszek 114825 8331897 +dsmith 114010 7622414 +CSU_Dynasty 113516 9582758 +sqrt2 112407 8782694 +marrco 111143 8222921 +drabel 108168 9061580 +BrunoBanani 104938 7448565 +Data 94621 8433010 +CoffeeOne 90394 3964243 +BRAVONE 80811 5341681 +psk 77195 6156031 +brabos 70284 5685893 +Fisherman 66650 5572406 +nssy 64587 5369140 +Pking_cda 64499 5704075 +sterni1971 63488 5070004 +mgrabiak 62385 5420812 +tvijlbrief 58957 4154234 +jromang 58854 4704502 +dv8silencer 57421 3961325 +sunu 56620 4609155 +tinker 56039 4204914 +biffhero 55743 4810039 +teddybaer 52982 4740444 +bcross 50548 5071599 +renouve 50318 3544864 +Freja 50296 3805120 +robnjr 47504 4131742 +eva42 46542 4044694 +davar 46538 4030604 +finfish 46244 3481661 +rap 46201 3219490 +ttruscott 45037 3645430 +solarlight 44155 4074841 +TueRens 41372 3891510 +ElbertoOne 41321 3920894 +Antihistamine 39218 2792761 +mhunt 38991 2697512 +bigpen0r 37820 3149955 +homyur 35569 3009637 +VoyagerOne 35137 3302650 +mhoram 34770 2684128 +racerschmacer 33022 3231055 +speedycpu 32043 2531964 +EthanOConnor 31638 2143255 +oryx 29574 2767730 +Pyafue 28885 1986098 +jkiiski 28014 1923255 +Garf 27579 2770144 +slakovv 27017 2031279 +Bobo1239 27000 2488707 +pb00067 26817 2306694 +robal 26337 2316795 +hyperbolic.tom 26248 2200777 +rkl 24898 2236013 +SC 23988 2126825 +nabildanial 23524 1586321 +achambord 23495 1942546 +Sharaf_DG 22975 1790697 +chriswk 22876 1947731 +anst 22568 2013953 +Patrick_G 22435 1682293 +cuistot 22201 1383031 +gri 21901 1820968 +Prcuvu 21182 1890546 +Zirie 21171 1493227 +JanErik 20596 1791991 +Isidor 20560 1730290 +xor12 20535 1819280 +team-oh 20364 1653708 +nesoneg 20264 1493435 +rstoesser 19802 1335177 +grandphish2 19402 1834196 +sg4032 18427 1671742 +dew 18263 1423326 +ianh2105 18133 1668562 +MazeOfGalious 18022 1644593 +ville 17900 1539130 +j3corre 17607 975954 +eudhan 17502 1424648 +iisiraider 17175 1118788 +jundery 17172 1115855 +SFTUser 16635 1363975 +purplefishies 16621 1106850 +DragonLord 16599 1252348 +chris 15274 1575333 +xoto 14900 1486261 +dju 14861 901552 +dex 14647 1228763 +nordlandia 14551 1369718 +ronaldjerum 14361 1210607 +OssumOpossum 14149 1029265 +IgorLeMasson 13844 1228391 +enedene 13762 935618 +ako027ako 13442 1250249 +AdrianSA 13324 924980 +bpfliegel 13318 886523 +ncfish1 13056 932344 +wei 12863 1369596 +jpulman 12776 854815 +horst.prack 12436 1151505 +joster 12424 986622 +cisco2015 12265 1205019 +fatmurphy 12015 901134 +modolief 11228 926456 +Dark_wizzie 11214 1017910 +mschmidt 10973 818594 +eastorwest 10970 1117836 +infinity 10762 746397 +SapphireBrand 10692 1024604 +Thomas A. 10553 736094 +pgontarz 10294 878746 +Andrew Grant 10195 922933 +stocky 10083 718114 diff --git a/Engines/Windows/mcbrain/appveyor.yml b/Engines/Windows/mcbrain/appveyor.yml index a46a0f3..c711dd6 100644 --- a/Engines/Windows/mcbrain/appveyor.yml +++ b/Engines/Windows/mcbrain/appveyor.yml @@ -1,5 +1,5 @@ version: 1.0.{build} -clone_depth: 5 +clone_depth: 50 branches: only: @@ -13,10 +13,11 @@ os: Visual Studio 2015 platform: - x86 - x64 - - Any CPU # build Configuration, i.e. Debug, Release, etc. -configuration: Debug +configuration: + - Debug + - Release matrix: # The build fail immediately once one of the job fails @@ -28,18 +29,43 @@ init: - msbuild /version before_build: - - cd src - - echo project (Stockfish) >> CMakeLists.txt - - echo add_executable(stockfish benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp >> CMakeLists.txt - - echo main.cpp material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp >> CMakeLists.txt - - echo search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp syzygy/tbprobe.cpp) >> CMakeLists.txt - - echo set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src) >> CMakeLists.txt -# - echo target_compile_options(stockfish PUBLIC "/Ox") >> CMakeLists.txt + - ps: | + # Get sources + $src = get-childitem -Path *.cpp -Recurse | select -ExpandProperty FullName + $src = $src -join ' ' + $src = $src.Replace("\", "/") + + # Build CMakeLists.txt + $t = 'cmake_minimum_required(VERSION 3.8)', + 'project(Stockfish)', + 'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)', + 'set(source_files', $src, ')', + 'add_executable(stockfish ${source_files})' + + # Write CMakeLists.txt withouth BOM + $MyPath = (Get-Item -Path "." -Verbose).FullName + '\CMakeLists.txt' + $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False + [System.IO.File]::WriteAllLines($MyPath, $t, $Utf8NoBomEncoding) + + # Obtain bench reference from git log + $b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1 + $bench = $b -match '\D+(\d+)' | % { $matches[1] } + Write-Host "Reference bench:" $bench + $g = "Visual Studio 14 2015" + If (${env:PLATFORM} -eq 'x64') { $g = $g + ' Win64' } + cmake -G "${g}" . + Write-Host "Generated files for: " $g build_script: - - cmake -G "Visual Studio 14 2015 Win64" . - - cmake --build . + - cmake --build . --config %CONFIGURATION% -- /verbosity:minimal before_test: - - cd Debug - - stockfish.exe bench > null + - cd src/%CONFIGURATION% + - ps: | + # Verify bench number + ./stockfish bench 2> out.txt 1> null + $s = (gc "./out.txt" | out-string) + $r = ($s -match 'Nodes searched \D+(\d+)' | % { $matches[1] }) + Write-Host "Engine bench:" $r + Write-Host "Reference bench:" $bench + If ($r -ne $bench) { exit 1 } diff --git a/Engines/Windows/mcbrain/src/Makefile b/Engines/Windows/mcbrain/src/Makefile index f1d5dec..6e66fbb 100644 --- a/Engines/Windows/mcbrain/src/Makefile +++ b/Engines/Windows/mcbrain/src/Makefile @@ -5,7 +5,7 @@ # Copyright (C) 2004-2008 Tord Romstad (Glaurung Author) # Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) # Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) -# Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) +# Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) # McBrain is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,8 +30,8 @@ ifeq ($(KERNEL),Linux) endif ### Executable name -EXE = SF-McBrain-v40 -### bench nodes -> Nodes searched : 6376731 +EXE = McBrain-9 +### bench nodes -> Nodes searched : 6871658 ### Installation dir definitions PREFIX = /usr/local @@ -156,7 +156,7 @@ endif ifeq ($(COMP),gcc) comp=gcc CXX=g++ - CXXFLAGS += -pedantic -Wextra -Wshadow -static-libstdc++ + CXXFLAGS += -pedantic -Wextra -Wshadow ifeq ($(ARCH),armv7) ifeq ($(OS),Android) @@ -280,9 +280,7 @@ endif ### 3.3 Optimization ifeq ($(optimize),yes) -CXXFLAGS += -O1 -O3 -msse4.2 -mpopcnt -mtune=native -####-Os -O1 -mtune=native -#### for those on macOS High Sierra 10.13 and higher use, "-Os -O1 -mtune=native" for PGO using gcc7 + CXXFLAGS += -O3 -mtune=native ifeq ($(comp),gcc) @@ -308,8 +306,6 @@ CXXFLAGS += -O1 -O3 -msse4.2 -mpopcnt -mtune=native ifeq ($(comp),clang) ifeq ($(KERNEL),Darwin) - CXXFLAGS += -flto - LDFLAGS += $(CXXFLAGS) ifeq ($(arch),i386) CXXFLAGS += -mdynamic-no-pic endif @@ -355,24 +351,20 @@ endif ### 3.8 Link Time Optimization, it works since gcc 4.5 but not on mingw under Windows. ### This is a mix of compile and link time options because the lto link phase ### needs access to the optimization flags. -ifeq ($(comp),gcc) - ifeq ($(optimize),yes) - ifeq ($(debug),no) +ifeq ($(optimize),yes) +ifeq ($(debug), no) + ifeq ($(comp),$(filter $(comp),gcc clang)) CXXFLAGS += -flto LDFLAGS += $(CXXFLAGS) endif - endif -endif -ifeq ($(comp),mingw) + ifeq ($(comp),mingw) ifeq ($(KERNEL),Linux) - ifeq ($(optimize),yes) - ifeq ($(debug),no) CXXFLAGS += -flto LDFLAGS += $(CXXFLAGS) endif endif - endif +endif endif ### 3.9 Android 5 can only run position independent executables. Note that this @@ -440,7 +432,6 @@ help: build: config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all strip $(EXE) - cp $(EXE) /Users/michaelbyrne/cluster.mfb/$(EXE) profile-build: config-sanity objclean profileclean @echo "" @@ -460,7 +451,12 @@ profile-build: config-sanity objclean profileclean @echo "Step 5/5. Deleting profile data ..." $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean strip $(EXE) - cp $(EXE) /Users/michaelbyrne/cluster.mfb/$(EXE) + +cprom: + make -j profile-build ARCH=x86-64-modern COMP=clang + +cprob: + make -j profile-build ARCH=x86-64-bmi2 COMP=clang strip: strip $(EXE) diff --git a/Engines/Windows/mcbrain/src/benchmark.cpp b/Engines/Windows/mcbrain/src/benchmark.cpp index f5499ef..807f9de 100644 --- a/Engines/Windows/mcbrain/src/benchmark.cpp +++ b/Engines/Windows/mcbrain/src/benchmark.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/bitbase.cpp b/Engines/Windows/mcbrain/src/bitbase.cpp index ae5c879..a651e27 100644 --- a/Engines/Windows/mcbrain/src/bitbase.cpp +++ b/Engines/Windows/mcbrain/src/bitbase.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/bitboard.cpp b/Engines/Windows/mcbrain/src/bitboard.cpp index d84d447..9dd637b 100644 --- a/Engines/Windows/mcbrain/src/bitboard.cpp +++ b/Engines/Windows/mcbrain/src/bitboard.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/bitboard.h b/Engines/Windows/mcbrain/src/bitboard.h index 598bbbc..c964d1e 100644 --- a/Engines/Windows/mcbrain/src/bitboard.h +++ b/Engines/Windows/mcbrain/src/bitboard.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/endgame.cpp b/Engines/Windows/mcbrain/src/endgame.cpp index 2bf6501..3d568f5 100644 --- a/Engines/Windows/mcbrain/src/endgame.cpp +++ b/Engines/Windows/mcbrain/src/endgame.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/endgame.h b/Engines/Windows/mcbrain/src/endgame.h index 3bc3a02..3bace6f 100644 --- a/Engines/Windows/mcbrain/src/endgame.h +++ b/Engines/Windows/mcbrain/src/endgame.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify diff --git a/Engines/Windows/mcbrain/src/evaluate.cpp b/Engines/Windows/mcbrain/src/evaluate.cpp index a7d304a..e420d3d 100644 --- a/Engines/Windows/mcbrain/src/evaluate.cpp +++ b/Engines/Windows/mcbrain/src/evaluate.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -98,6 +98,7 @@ namespace { template void initialize(); template Score evaluate_king(); template Score evaluate_threats(); + int king_distance(Color c, Square s); template Score evaluate_passed_pawns(); template Score evaluate_space(); template Score evaluate_pieces(); @@ -112,7 +113,8 @@ namespace { Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; // attackedBy[color][piece type] is a bitboard representing all squares - // attacked by a given color and piece type (can be also ALL_PIECES). + // attacked by a given color and piece type. Special "piece types" which are + // also calculated are QUEEN_DIAGONAL and ALL_PIECES. Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; // attackedBy2[color] are the squares attacked by 2 pieces of a given color, @@ -184,23 +186,23 @@ namespace { // which piece type attacks which one. Attacks on lesser pieces which are // pawn-defended are not considered. const Score ThreatByMinor[PIECE_TYPE_NB] = { - S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72, 107), S(48, 118) - }; + S(0, 0), S(0, 31), S(39, 42), S(57, 44), S(68, 112), S(47, 120) + }; //Fauzi Akram const Score ThreatByRook[PIECE_TYPE_NB] = { - S(0, 0), S(0, 25), S(40, 62), S(40, 59), S(0, 34), S(35, 48) - }; + S(0, 0), S(0, 24), S(38, 71), S(38, 61), S(0, 38), S(36, 38) + }; //Fauzi Akram // ThreatByKing[on one/on many] contains bonuses for king attacks on // pawns or pieces which are not pawn-defended. - const Score ThreatByKing[] = { S(3, 62), S(9, 138) }; + const Score ThreatByKing[] = { S(3, 65), S(9, 145) }; //Fauzi Akram // Passed[mg/eg][Rank] contains midgame and endgame bonuses for passed pawns. // We don't use a Score because we process the two components independently. const Value Passed[][RANK_NB] = { - { V(5), V( 5), V(31), V(73), V(166), V(252) }, - { V(7), V(14), V(38), V(73), V(166), V(252) } - }; + { V(0), V(5), V( 5), V(32), V(70), V(172), V(217) }, + { V(0), V(7), V(13), V(42), V(70), V(170), V(269) } + }; //Fauzi Akram // PassedFile[File] contains a bonus according to the file of a passed pawn const Score PassedFile[FILE_NB] = { @@ -208,6 +210,9 @@ namespace { S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10) }; + // Rank factor applied on some bonus for passed pawn on rank 4 or beyond + const int RankFactor[RANK_NB] = {0, 0, 0, 2, 7, 12, 19}; //Fauzi Akram + // KingProtector[PieceType-2] contains a bonus according to distance from king const Score KingProtector[] = { S(-3, -5), S(-4, -3), S(-3, 0), S(-1, 1) }; @@ -218,17 +223,15 @@ namespace { const Score RookOnPawn = S( 8, 24); const Score TrappedRook = S( 92, 0); const Score WeakQueen = S( 50, 10); - const Score OtherCheck = S( 10, 10); const Score CloseEnemies = S( 7, 0); const Score PawnlessFlank = S( 20, 80); - const Score ThreatByHangingPawn = S( 71, 61); - const Score ThreatBySafePawn = S(192,175); + const Score ThreatBySafePawn = S(175,168); //Fauzi Akram const Score ThreatByRank = S( 16, 3); - const Score Hanging = S( 48, 27); + const Score Hanging = S( 52, 30); //Fauzi Akram const Score WeakUnopposedPawn = S( 5, 25); - const Score ThreatByPawnPush = S( 38, 22); - const Score ThreatByAttackOnQueen = S( 38, 22); - const Score HinderPassedPawn = S( 7, 0); + const Score ThreatByPawnPush = S( 47, 26); //Fauzi Akram + const Score ThreatByAttackOnQueen = S( 42, 21); //Fauzi Akram + const Score HinderPassedPawn = S( 8, 1); //Fauzi Akram const Score TrappedBishopA1H1 = S( 50, 50); #undef S @@ -238,10 +241,10 @@ namespace { const int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 78, 56, 45, 11 }; // Penalties for enemy's safe checks - const int QueenCheck = 780; - const int RookCheck = 880; - const int BishopCheck = 435; - const int KnightCheck = 790; + const int QueenSafeCheck = 780; + const int RookSafeCheck = 880; + const int BishopSafeCheck = 435; + const int KnightSafeCheck = 790; // Threshold for lazy and space evaluation const Value LazyThreshold = Value(1500); @@ -312,8 +315,8 @@ namespace { while ((s = *pl++) != SQ_NONE) { // Find attacked squares, including x-ray attacks for bishops and rooks - b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(Us, QUEEN)) - : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(Us, ROOK, QUEEN)) + b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) + : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) : pos.attacks_from(s); if (pos.pinned_pieces(Us) & s) @@ -428,17 +431,29 @@ namespace { Score Evaluation::evaluate_king() { const Color Them = (Us == WHITE ? BLACK : WHITE); - const Direction Up = (Us == WHITE ? NORTH : SOUTH); const Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB); const Square ksq = pos.square(Us); - Bitboard weak, b, b1, b2, safe, other; - int kingDanger; + Bitboard weak, b, b1, b2, safe, unsafeChecks; // King shelter and enemy pawns storm Score score = pe->king_safety(pos, ksq); + // King tropism: firstly, find squares that opponent attacks in our king flank + File kf = file_of(ksq); + b = attackedBy[Them][ALL_PIECES] & KingFlank[kf] & Camp; + + assert(((Us == WHITE ? b << 4 : b >> 4) & b) == 0); + assert(popcount(Us == WHITE ? b << 4 : b >> 4) == popcount(b)); + + // Secondly, add the squares which are attacked twice in that flank and + // which are not defended by our pawns. + b = (Us == WHITE ? b << 4 : b >> 4) + | (b & attackedBy2[Them] & ~attackedBy[Us][PAWN]); + + int tropism = popcount(b); + // Main king safety evaluation if (kingAttackersCount[Them] > (1 - pos.count(Them))) { @@ -447,18 +462,7 @@ namespace { & ~attackedBy2[Us] & (attackedBy[Us][KING] | attackedBy[Us][QUEEN] | ~attackedBy[Us][ALL_PIECES]); - // Initialize the 'kingDanger' variable, which will be transformed - // later into a king danger score. The initial value is based on the - // number and types of the enemy's attacking pieces, the number of - // attacked and weak squares around our king, the absence of queen and - // the quality of the pawn shelter (current 'score' value). - kingDanger = kingAttackersCount[Them] * kingAttackersWeight[Them] - + 102 * kingAdjacentZoneAttacksCount[Them] - + 191 * popcount(kingRing[Us] & weak) - + 143 * bool(pos.pinned_pieces(Us)) - - 848 * !pos.count(Them) - - 9 * mg_value(score) / 8 - + 40; + int kingDanger = unsafeChecks = 0; // Analyse the safe enemy's checks which are possible on next move safe = ~pos.pieces(Them); @@ -469,54 +473,52 @@ namespace { // Enemy queen safe checks if ((b1 | b2) & attackedBy[Them][QUEEN] & safe & ~attackedBy[Us][QUEEN]) - kingDanger += QueenCheck; - - // Some other potential checks are also analysed, even from squares - // currently occupied by the opponent own pieces, as long as the square - // is not attacked by our pawns, and is not occupied by a blocked pawn. - other = ~( attackedBy[Us][PAWN] - | (pos.pieces(Them, PAWN) & shift(pos.pieces(PAWN)))); - - // Enemy rooks safe and other checks - if (b1 & attackedBy[Them][ROOK] & safe) - kingDanger += RookCheck; + kingDanger += QueenSafeCheck; - else if (b1 & attackedBy[Them][ROOK] & other) - score -= OtherCheck; + b1 &= attackedBy[Them][ROOK]; + b2 &= attackedBy[Them][BISHOP]; - // Enemy bishops safe and other checks - if (b2 & attackedBy[Them][BISHOP] & safe) - kingDanger += BishopCheck; + // Enemy rooks checks + if (b1 & safe) + kingDanger += RookSafeCheck; + else + unsafeChecks |= b1; - else if (b2 & attackedBy[Them][BISHOP] & other) - score -= OtherCheck; + // Enemy bishops checks + if (b2 & safe) + kingDanger += BishopSafeCheck; + else + unsafeChecks |= b2; - // Enemy knights safe and other checks + // Enemy knights checks b = pos.attacks_from(ksq) & attackedBy[Them][KNIGHT]; if (b & safe) - kingDanger += KnightCheck; - - else if (b & other) - score -= OtherCheck; - - // Transform the kingDanger units into a Score, and substract it from the evaluation + kingDanger += KnightSafeCheck; + else + unsafeChecks |= b; + + // Unsafe or occupied checking squares will also be considered, as long as + // the square is in the attacker's mobility area. + unsafeChecks &= mobilityArea[Them]; + + kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] + + 102 * kingAdjacentZoneAttacksCount[Them] + + 191 * popcount(kingRing[Us] & weak) + + 143 * popcount(pos.pinned_pieces(Us) | unsafeChecks) + - 848 * !pos.count(Them) + - 9 * mg_value(score) / 8 + + 4 * tropism; + + // Transform the kingDanger units into a Score, and subtract it from the evaluation if (kingDanger > 0) + { + int mobilityDanger = (mg_value(mobility[Them] - mobility[Us])) * 2; + kingDanger = std::max(0, kingDanger + mobilityDanger); score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16); + } } - // King tropism: firstly, find squares that opponent attacks in our king flank - File kf = file_of(ksq); - b = attackedBy[Them][ALL_PIECES] & KingFlank[kf] & Camp; - - assert(((Us == WHITE ? b << 4 : b >> 4) & b) == 0); - assert(popcount(Us == WHITE ? b << 4 : b >> 4) == popcount(b)); - - // Secondly, add the squares which are attacked twice in that flank and - // which are not defended by our pawns. - b = (Us == WHITE ? b << 4 : b >> 4) - | (b & attackedBy2[Them] & ~attackedBy[Us][PAWN]); - - score -= CloseEnemies * popcount(b); + score -= CloseEnemies * tropism; // Penalty when our king is on a pawnless flank if (!(pos.pieces(PAWN) & KingFlank[kf])) @@ -555,9 +557,6 @@ namespace { safeThreats = (shift(b) | shift(b)) & weak; score += ThreatBySafePawn * popcount(safeThreats); - - if (weak ^ safeThreats) - score += ThreatByHangingPawn; } // Squares strongly protected by the opponent, either because they attack the @@ -634,6 +633,11 @@ namespace { return score; } + // helper used by evaluate_passed_pawns to cap the distance + template + int Evaluation::king_distance(Color c, Square s) { + return std::min(distance(pos.square(c), s), 5); + } // evaluate_passed_pawns() evaluates the passed pawns and candidate passed // pawns of the given color. @@ -658,8 +662,8 @@ namespace { bb = forward_file_bb(Us, s) & (attackedBy[Them][ALL_PIECES] | pos.pieces(Them)); score -= HinderPassedPawn * popcount(bb); - int r = relative_rank(Us, s) - RANK_2; - int rr = r * (r - 1); + int r = relative_rank(Us, s); + int rr = RankFactor[r]; Value mbonus = Passed[MG][r], ebonus = Passed[EG][r]; @@ -668,12 +672,11 @@ namespace { Square blockSq = s + Up; // Adjust bonus based on the king's proximity - ebonus += distance(pos.square(Them), blockSq) * 5 * rr - - distance(pos.square( Us), blockSq) * 2 * rr; + ebonus += (king_distance(Them, blockSq) * 5 - king_distance(Us, blockSq) * 2) * rr; // If blockSq is not the queening square then consider also a second push - if (relative_rank(Us, blockSq) != RANK_8) - ebonus -= distance(pos.square(Us), blockSq + Up) * rr; + if (r != RANK_7) + ebonus -= king_distance(Us, blockSq + Up) * rr; // If the pawn is free to advance, then increase the bonus if (pos.empty(blockSq)) @@ -693,7 +696,7 @@ namespace { // If there aren't any enemy attacks, assign a big bonus. Otherwise // assign a smaller bonus if the block square isn't attacked. - int k = !unsafeSquares ? 18 : !(unsafeSquares & blockSq) ? 8 : 0; + int k = !unsafeSquares ? 20 : !(unsafeSquares & blockSq) ? 9 : 0; //Fauzi Akram // If the path to the queen is fully defended, assign a big bonus. // Otherwise assign a smaller bonus if the block square is defended. @@ -903,7 +906,7 @@ namespace { Trace::add(TOTAL, score); } - return (pos.side_to_move() == WHITE ? v : -v) + Eval::Tempo; // Side to move point of view + return pos.side_to_move() == WHITE ? v : -v; // Side to move point of view } } // namespace @@ -915,7 +918,7 @@ Score Eval::Contempt = SCORE_ZERO; Value Eval::evaluate(const Position& pos) { - return Evaluation<>(pos).value(); + return Evaluation<>(pos).value() + Eval::Tempo; } /// trace() is like evaluate(), but instead of returning a value, it returns @@ -926,7 +929,7 @@ std::string Eval::trace(const Position& pos) { std::memset(scores, 0, sizeof(scores)); - Value v = Evaluation(pos).value(); + Value v = Evaluation(pos).value() + Eval::Tempo; v = pos.side_to_move() == WHITE ? v : -v; // White's point of view std::stringstream ss; diff --git a/Engines/Windows/mcbrain/src/evaluate.h b/Engines/Windows/mcbrain/src/evaluate.h index 3772c3f..8d388fa 100644 --- a/Engines/Windows/mcbrain/src/evaluate.h +++ b/Engines/Windows/mcbrain/src/evaluate.h @@ -30,7 +30,7 @@ class Position; namespace Eval { -const Value Tempo = Value(24); // Must be visible to search +const Value Tempo = Value(20); // Must be visible to search extern Score Contempt; diff --git a/Engines/Windows/mcbrain/src/main.cpp b/Engines/Windows/mcbrain/src/main.cpp index a7beca8..637dc3b 100644 --- a/Engines/Windows/mcbrain/src/main.cpp +++ b/Engines/Windows/mcbrain/src/main.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -48,11 +48,11 @@ int main(int argc, char* argv[]) { polybook.init(Options["BookFile"]); Tablebases::init(Options["SyzygyPath"]); TT.resize(Options["Hash"]); - Threads.init(Options["Threads"]); + Threads.set(Options["Threads"]); Search::clear(); // After threads are up UCI::loop(argc, argv); - Threads.exit(); + Threads.set(0); return 0; } diff --git a/Engines/Windows/mcbrain/src/material.cpp b/Engines/Windows/mcbrain/src/material.cpp index a58d52f..2b28837 100644 --- a/Engines/Windows/mcbrain/src/material.cpp +++ b/Engines/Windows/mcbrain/src/material.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -40,7 +40,7 @@ namespace { { 32, 255, -3 }, // Knight OUR PIECES { 0, 104, 4, 0 }, // Bishop { -26, -2, 47, 105, -149 }, // Rook - {-185, 24, 122, 137, -134, 0 } // Queen + {-189, 24, 117, 133, -134, -10 } // Queen }; const int QuadraticTheirs[][PIECE_TYPE_NB] = { @@ -51,18 +51,7 @@ namespace { { 9, 63, 0 }, // Knight OUR PIECES { 59, 65, 42, 0 }, // Bishop { 46, 39, 24, -24, 0 }, // Rook - { 101, 100, -37, 141, 268, 0 } // Queen - }; - - // PawnSet[pawn count] contains a bonus/malus indexed by number of pawns - const int PawnSet[] = { - 24, -32, 107, -51, 117, -9, -126, -21, 31 - }; - - // QueenMinorsImbalance[opp_minor_count] is applied when only one side has a queen. - // It contains a bonus/malus for the side with the queen. - const int QueenMinorsImbalance[13] = { - 31, -8, -15, -25, -5 + { 97, 100, -42, 137, 268, 0 } // Queen }; // Endgame evaluation and scaling functions are accessed directly and not through @@ -101,7 +90,7 @@ namespace { const Color Them = (Us == WHITE ? BLACK : WHITE); - int bonus = PawnSet[pieceCount[Us][PAWN]]; + int bonus = 0; // Second-degree polynomial material imbalance, by Tord Romstad for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) @@ -118,10 +107,6 @@ namespace { bonus += pieceCount[Us][pt1] * v; } - // Special handling of Queen vs. Minors - if (pieceCount[Us][QUEEN] == 1 && pieceCount[Them][QUEEN] == 0) - bonus += QueenMinorsImbalance[pieceCount[Them][KNIGHT] + pieceCount[Them][BISHOP]]; - return bonus; } diff --git a/Engines/Windows/mcbrain/src/material.h b/Engines/Windows/mcbrain/src/material.h index b412bb1..95d6c84 100644 --- a/Engines/Windows/mcbrain/src/material.h +++ b/Engines/Windows/mcbrain/src/material.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/misc.cpp b/Engines/Windows/mcbrain/src/misc.cpp index 2d48cb0..a14d9c4 100644 --- a/Engines/Windows/mcbrain/src/misc.cpp +++ b/Engines/Windows/mcbrain/src/misc.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,7 +52,7 @@ namespace { /// Version number. If Version is left empty, then compile date in the format /// DD-MM-YY and show in engine_info. -const string Version = "v4.0"; +const string Version = "9"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We @@ -123,7 +123,7 @@ const string engine_info(bool to_uci) { string month, day, year; stringstream ss, date(__DATE__); // From compiler, format is "Sep 21 2008" - ss << "SF-McBrain " << Version << setfill('0'); + ss << "McBrain " << Version << setfill('0'); if (Version.empty()) { @@ -134,7 +134,7 @@ const string engine_info(bool to_uci) { ss << (Is64Bit ? " 64" : "") << (HasPext ? " BMI2" : (HasPopCnt ? " POPCNT" : "")) << (to_uci ? "\nid author ": " by ") - << "M. Byrne and others (C) 2017"; + << "M. Byrne, Stockfish authors and others"; return ss.str(); } @@ -294,14 +294,6 @@ int get_group(size_t idx) { void bindThisThread(size_t idx) { - // If OS already scheduled us on a different group than 0 then don't overwrite - // the choice, eventually we are one of many one-threaded processes running on - // some Windows NUMA hardware, for instance in fishtest. To make it simple, - // just check if running threads are below a threshold, in this case all this - // NUMA machinery is not needed. - if (Threads.size() < 8) - return; - // Use only local variables to be thread-safe int group = get_group(idx); diff --git a/Engines/Windows/mcbrain/src/misc.h b/Engines/Windows/mcbrain/src/misc.h index f907c96..98b5202 100644 --- a/Engines/Windows/mcbrain/src/misc.h +++ b/Engines/Windows/mcbrain/src/misc.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/movegen.cpp b/Engines/Windows/mcbrain/src/movegen.cpp index e75ccbb..d06bc86 100644 --- a/Engines/Windows/mcbrain/src/movegen.cpp +++ b/Engines/Windows/mcbrain/src/movegen.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/movegen.h b/Engines/Windows/mcbrain/src/movegen.h index 1a02a2a..666e4ef 100644 --- a/Engines/Windows/mcbrain/src/movegen.h +++ b/Engines/Windows/mcbrain/src/movegen.h @@ -46,7 +46,7 @@ struct ExtMove { // Inhibit unwanted implicit conversions to Move // with an ambiguity that yields to a compile error. - operator float() const; + operator float() const = delete; }; inline bool operator<(const ExtMove& f, const ExtMove& s) { diff --git a/Engines/Windows/mcbrain/src/movepick.cpp b/Engines/Windows/mcbrain/src/movepick.cpp index ef169ff..f62ca2b 100644 --- a/Engines/Windows/mcbrain/src/movepick.cpp +++ b/Engines/Windows/mcbrain/src/movepick.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,12 +26,11 @@ namespace { enum Stages { - MAIN_SEARCH, CAPTURES_INIT, CAPTURE_KILLERS, GOOD_CAPTURES, KILLERS, COUNTERMOVE, QUIET_INIT, QUIET, BAD_CAPTURES, - EVASION, EVASIONS_INIT, ALL_EVASIONS, - PROBCUT, PROBCUT_INIT, PROBCUT_CAPTURES, - QSEARCH_WITH_CHECKS, QCAPTURES_1_INIT, QCAPTURES_1, QCHECKS, - QSEARCH_NO_CHECKS, QCAPTURES_2_INIT, QCAPTURES_2, - QSEARCH_RECAPTURES, QRECAPTURES + MAIN_SEARCH, CAPTURES_INIT, GOOD_CAPTURES, KILLERS, COUNTERMOVE, QUIET_INIT, QUIET, BAD_CAPTURES, + EVASION, EVASIONS_INIT, ALL_EVASIONS, + PROBCUT, PROBCUT_INIT, PROBCUT_CAPTURES, + QSEARCH, QCAPTURES_INIT, QCAPTURES, QCHECKS, + QSEARCH_RECAPTURES, QRECAPTURES }; // partial_insertion_sort() sorts moves in descending order up to and including @@ -69,10 +68,10 @@ namespace { /// MovePicker constructor for the main search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, - const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, - Move* killers_p) : - pos(p), mainHistory(mh), captureHistory(cph), contHistory(ch), countermove(cm), - killers{killers_p[0], killers_p[1], killers_p[2], killers_p[3]}, depth(d){ + const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, Move* killers_p) + : pos(p), mainHistory(mh), captureHistory(cph), contHistory(ch), countermove(cm), + killers{killers_p[0], killers_p[1]}, depth(d){ + assert(d > DEPTH_ZERO); stage = pos.checkers() ? EVASION : MAIN_SEARCH; @@ -82,18 +81,15 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHist /// MovePicker constructor for quiescence search MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const CapturePieceToHistory* cph, Square s) - : pos(p), mainHistory(mh), captureHistory(cph) { + : pos(p), mainHistory(mh), captureHistory(cph), depth(d) { assert(d <= DEPTH_ZERO); if (pos.checkers()) stage = EVASION; - else if (d > DEPTH_QS_NO_CHECKS) - stage = QSEARCH_WITH_CHECKS; - else if (d > DEPTH_QS_RECAPTURES) - stage = QSEARCH_NO_CHECKS; + stage = QSEARCH; else { @@ -162,8 +158,7 @@ Move MovePicker::next_move(bool skipQuiets) { switch (stage) { - case MAIN_SEARCH: case EVASION: case QSEARCH_WITH_CHECKS: - case QSEARCH_NO_CHECKS: case PROBCUT: + case MAIN_SEARCH: case EVASION: case QSEARCH: case PROBCUT: ++stage; return ttMove; @@ -172,34 +167,16 @@ Move MovePicker::next_move(bool skipQuiets) { endMoves = generate(pos, cur); score(); ++stage; - move = killers[2]; // First capture killer move - if( move != MOVE_NONE - && move != ttMove - && pos.pseudo_legal(move) - && pos.capture_or_promotion(move)) - return move; /* fallthrough */ - - case CAPTURE_KILLERS: - ++stage; - move = killers[3]; // Second capture killer move - if( move != MOVE_NONE - && move != ttMove - && pos.pseudo_legal(move) - && pos.capture_or_promotion(move)) - return move; - /* fallthrough */ case GOOD_CAPTURES: while (cur < endMoves) { move = pick_best(cur++, endMoves); - if ( move != ttMove - && move != killers[2] - && move != killers[3]) - { - if (pos.see_ge(move, Value(-55 * (cur-1)->value / 1024))) - return move; + if (move != ttMove) + { + if (pos.see_ge(move, Value(-55 * (cur-1)->value / 1024))) + return move; // Losing capture, move it to the beginning of the array *endBadCaptures++ = move; @@ -299,21 +276,21 @@ Move MovePicker::next_move(bool skipQuiets) { } break; - case QCAPTURES_1_INIT: case QCAPTURES_2_INIT: + case QCAPTURES_INIT: cur = moves; endMoves = generate(pos, cur); score(); ++stage; /* fallthrough */ - case QCAPTURES_1: case QCAPTURES_2: + case QCAPTURES: while (cur < endMoves) { move = pick_best(cur++, endMoves); if (move != ttMove) return move; } - if (stage == QCAPTURES_2) + if (depth <= DEPTH_QS_NO_CHECKS) break; cur = moves; endMoves = generate(pos, cur); @@ -332,14 +309,13 @@ Move MovePicker::next_move(bool skipQuiets) { case QSEARCH_RECAPTURES: cur = moves; endMoves = generate(pos, cur); - score(); ++stage; /* fallthrough */ case QRECAPTURES: while (cur < endMoves) { - move = pick_best(cur++, endMoves); + move = *cur++; if (to_sq(move) == recaptureSquare) return move; } diff --git a/Engines/Windows/mcbrain/src/movepick.h b/Engines/Windows/mcbrain/src/movepick.h index c651aaf..26e2a14 100644 --- a/Engines/Windows/mcbrain/src/movepick.h +++ b/Engines/Windows/mcbrain/src/movepick.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/pawns.cpp b/Engines/Windows/mcbrain/src/pawns.cpp index d9e789d..3e5c2ed 100644 --- a/Engines/Windows/mcbrain/src/pawns.cpp +++ b/Engines/Windows/mcbrain/src/pawns.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -44,23 +44,17 @@ namespace { // Doubled pawn penalty const Score Doubled = S(18, 38); - // Lever bonus by rank - const Score Lever[RANK_NB] = { - S( 0, 0), S( 0, 0), S(0, 0), S(0, 0), - S(17, 16), S(33, 32), S(0, 0), S(0, 0) - }; - // Weakness of our pawn shelter in front of the king by [isKingFile][distance from edge][rank]. // RANK_1 = 0 is used for files where we have no pawns or our pawn is behind our king. const Value ShelterWeakness[][int(FILE_NB) / 2][RANK_NB] = { - { { V( 98), V(20), V(11), V(42), V( 83), V( 84), V(101) }, // Not On King file - { V(103), V( 8), V(33), V(86), V( 87), V(105), V(113) }, - { V(100), V( 2), V(65), V(95), V( 59), V( 89), V(115) }, - { V( 72), V( 6), V(52), V(74), V( 83), V( 84), V(112) } }, - { { V(105), V(19), V( 3), V(27), V( 85), V( 93), V( 84) }, // On King file - { V(121), V( 7), V(33), V(95), V(112), V( 86), V( 72) }, - { V(121), V(26), V(65), V(90), V( 65), V( 76), V(117) }, - { V( 79), V( 0), V(45), V(65), V( 94), V( 92), V(105) } } + { { V( 97), V(17), V( 9), V(44), V( 84), V( 87), V( 99) }, // Not On King file + { V(106), V( 6), V(33), V(86), V( 87), V(104), V(112) }, + { V(101), V( 2), V(65), V(98), V( 58), V( 89), V(115) }, + { V( 73), V( 7), V(54), V(73), V( 84), V( 83), V(111) } }, + { { V(104), V(20), V( 6), V(27), V( 86), V( 93), V( 82) }, // On King file + { V(123), V( 9), V(34), V(96), V(112), V( 88), V( 75) }, + { V(120), V(25), V(65), V(91), V( 66), V( 78), V(117) }, + { V( 81), V( 2), V(47), V(63), V( 94), V( 93), V(104) } } }; // Danger of enemy pawns moving toward our king by [type][distance from edge][rank]. @@ -185,9 +179,6 @@ namespace { if (doubled && !supported) score -= Doubled; - - if (lever) - score += Lever[relative_rank(Us, s)]; } return score; diff --git a/Engines/Windows/mcbrain/src/pawns.h b/Engines/Windows/mcbrain/src/pawns.h index 586a056..e834e18 100644 --- a/Engines/Windows/mcbrain/src/pawns.h +++ b/Engines/Windows/mcbrain/src/pawns.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-218 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/polybook.cpp b/Engines/Windows/mcbrain/src/polybook.cpp index aebc91b..9aaacb4 100644 --- a/Engines/Windows/mcbrain/src/polybook.cpp +++ b/Engines/Windows/mcbrain/src/polybook.cpp @@ -1,3 +1,24 @@ +/* + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +// polybook.cpp was written by Thomas Ziproth /* BrainFish, a UCI chess playing engine derived from Stockfish Copyright (C) 2016-2017 Thomas Zipproth diff --git a/Engines/Windows/mcbrain/src/polybook.h b/Engines/Windows/mcbrain/src/polybook.h index 2e79b60..8696eda 100644 --- a/Engines/Windows/mcbrain/src/polybook.h +++ b/Engines/Windows/mcbrain/src/polybook.h @@ -1,6 +1,28 @@ /* + McBrain, a UCI chess playing engine derived from Stockfish and Glaurung 2.1 + Copyright (C) 2004-2008 Tord Romstad (Glaurung author) + Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) + Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + + McBrain is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + McBrain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +//polybook.h was wriiten by Thomas Zipproth +/* +polybook.h was written by Thomas Zipproth BrainFish, a UCI chess playing engine derived from Stockfish -Copyright (C) 2016-2017 Thomas Zipproth +Copyright (C) 2016-2018 Thomas Zipproth BrainFish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -88,4 +110,4 @@ class PolyBook extern PolyBook polybook; -#endif // #ifndef POLYBOOK_H_INCLUDED \ No newline at end of file +#endif // #ifndef POLYBOOK_H_INCLUDED diff --git a/Engines/Windows/mcbrain/src/position.cpp b/Engines/Windows/mcbrain/src/position.cpp index 253d4b3..acef66b 100644 --- a/Engines/Windows/mcbrain/src/position.cpp +++ b/Engines/Windows/mcbrain/src/position.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/position.h b/Engines/Windows/mcbrain/src/position.h index 17c559e..c809549 100644 --- a/Engines/Windows/mcbrain/src/position.h +++ b/Engines/Windows/mcbrain/src/position.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/psqt.cpp b/Engines/Windows/mcbrain/src/psqt.cpp index 6f7d210..55a2e72 100644 --- a/Engines/Windows/mcbrain/src/psqt.cpp +++ b/Engines/Windows/mcbrain/src/psqt.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,13 +39,13 @@ namespace PSQT { const Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { { }, { // Pawn - { S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }, - { S(-11, 7), S( 6,-4), S( 7, 8), S( 3,-2) }, - { S(-18,-4), S( -2,-5), S( 19, 5), S(24, 4) }, - { S(-17, 3), S( -9, 3), S( 20,-8), S(35,-3) }, - { S( -6, 7), S( 5, 8), S( 3, 7), S(21, 6) }, - { S( -6, 8), S( -8, 9), S( -6, 8), S(-2, 7) }, - { S( -4, 9), S( 20, 10), S( -8, 9), S(-4, 8) } + { S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }, + { S(-11, 7), S( 6,-4), S( 7, 8), S( 3,-2) }, + { S(-18,-4), S( -2,-5), S( 19, 5), S(24, 4) }, + { S(-17, 3), S( -9, 3), S( 20,-8), S(35,-3) }, + { S( -6, 8), S( 5, 9), S( 3, 7), S(21,-6) }, + { S( -6, 8), S( -8,-5), S( -6, 2), S(-2, 4) }, + { S( -4, 3), S( 20,-9), S( -8, 1), S(-4,18) } }, { // Knight { S(-161,-105), S(-96,-82), S(-80,-46), S(-73,-14) }, diff --git a/Engines/Windows/mcbrain/src/search.cpp b/Engines/Windows/mcbrain/src/search.cpp index 7935441..92753c0 100644 --- a/Engines/Windows/mcbrain/src/search.cpp +++ b/Engines/Windows/mcbrain/src/search.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,16 +23,16 @@ #include #include #include // For std::memset -#include //for sleep +#include //for sleep //MichaelB7 #include #include -#include +#include // ELO MichaelB7 #include "evaluate.h" #include "misc.h" #include "movegen.h" #include "movepick.h" -#include "polybook.h" +#include "polybook.h" // Cerebellum #include "position.h" #include "search.h" #include "timeman.h" @@ -71,16 +71,16 @@ namespace { const int skipPhase[] = { 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7 }; // Razoring and futility margin based on depth - // razor_margin[0] is unused as long as depth >= ONE_PLY in search - const int razor_margin[] = { 0, 570, 603, 554 }; - Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); } + const int razor_margin = 600; + //Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); } + Value futility_margin(Depth d) { return Value(16 * d^2 / ONE_PLY + 200 * d / ONE_PLY);} // idea from JD Watson, code modfied by MichaelB7 // Futility and reductions lookup tables, initialized at startup int FutilityMoveCounts[2][16]; // [improving][depth] - int Reductions[2][2][128][96]; // [pv][improving][depth][moveNumber] + int Reductions[2][2][128][96]; // [pv][improving][depth][moveNumber] //MichaelB7 template Depth reduction(bool i, Depth d, int mn) { - return Reductions[PvNode][i][std::min(d / ONE_PLY, 127)][std::min(mn, 95)] * ONE_PLY; + return Reductions[PvNode][i][std::min(d / ONE_PLY, 127)][std::min(mn, 95)] * ONE_PLY; //MichaelB7 } // History and stats update bonus, based on depth @@ -102,7 +102,7 @@ namespace { int tactical; - bool bruteForce, limitStrength, noNULL; + bool bookEnabled, bruteForce, limitStrength, noNULL; template Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning); @@ -152,10 +152,10 @@ namespace { void Search::init() { for (int imp = 0; imp <= 1; ++imp) - for (int d = 1; d < 128; ++d) - for (int mc = 1; mc < 96; ++mc) + for (int d = 1; d < 128; ++d) // MichaelB7 + for (int mc = 1; mc < 96; ++mc) // MichaelB7 { - double r = log(d) * log(mc) / 1.89; + double r = log(d) * log(mc) / 1.89 ; Reductions[NonPV][imp][d][mc] = int(std::round(r)); Reductions[PV][imp][d][mc] = std::max(Reductions[NonPV][imp][d][mc] - 1, 0); @@ -181,13 +181,7 @@ void Search::clear() { Time.availableNodes = 0; TT.clear(); - - for (Thread* th : Threads) - th->clear(); - - Threads.main()->callsCnt = 0; - Threads.main()->previousScore = VALUE_INFINITE; - Threads.main()->previousTimeReduction = 1; + Threads.clear(); } @@ -207,6 +201,7 @@ void MainThread::search() { limitStrength = Options["UCI_LimitStrength"]; noNULL = Options["No_Null_Moves"]; tactical = Options["Tactical"]; + bookEnabled = Options["Book_Enabled"]; Color us = rootPos.side_to_move(); Time.init(Limits, us, rootPos.game_ply()); @@ -214,6 +209,14 @@ void MainThread::search() { int contempt = Options["Contempt"] * PawnValueEg / 100; // From centipawns + // In analysis mode, adjust contempt in accordance with user preference + if (Limits.infinite || Options["UCI_AnalyseMode"]) + contempt = Options["Analysis Contempt"] == "Off" ? 0 + : Options["Analysis Contempt"] == "White" && us == BLACK ? -contempt + : Options["Analysis Contempt"] == "Black" && us == WHITE ? -contempt + : contempt; // contempt remains with the side to move + + // Eval::Contempt is from white's point of view Eval::Contempt = (us == WHITE ? make_score(contempt, contempt / 2) : -make_score(contempt, contempt / 2)); @@ -231,7 +234,7 @@ void MainThread::search() { if (!Limits.infinite && !Limits.mate) bookMove = polybook.probe(rootPos); - if (bookMove && std::count(rootMoves.begin(), rootMoves.end(), bookMove)) + if (bookEnabled && bookMove && std::count(rootMoves.begin(), rootMoves.end(), bookMove)) { for (Thread* th : Threads) std::swap(th->rootMoves[0], *std::find(th->rootMoves.begin(), th->rootMoves.end(), bookMove)); @@ -240,9 +243,7 @@ void MainThread::search() { { if (limitStrength) { - int uci_elo = (Options["UCI_ELO"]); - std::mt19937 gen(now()); std::uniform_int_distribution dis(-33, 33); int rand = dis(gen); @@ -346,10 +347,10 @@ void Thread::search() { mainThread->bestMoveChanges = 0; } - size_t multiPV = Options["MultiPV"]; + multiPV = Options["MultiPV"]; Skill skill(Options["Skill Level"]); if (tactical) multiPV = pow(2, tactical); - if (::pv_is_draw(rootPos)) multiPV = 8 ; + // When playing with strength handicap enable MultiPV search that we will // use behind the scenes to retrieve a set of possible moves. @@ -357,19 +358,25 @@ void Thread::search() { multiPV = std::max(multiPV, (size_t)4); multiPV = std::min(multiPV, rootMoves.size()); + + // UCI options specify 256 as the max MultiPv option + int consecutiveEarlyExits[257] = {0}; // Iterative deepening loop until requested to stop or the target depth is reached while ( (rootDepth += ONE_PLY) < DEPTH_MAX && !Threads.stop && !(Limits.depth && mainThread && rootDepth / ONE_PLY > Limits.depth)) { - // Distribute search depths across the threads - if (idx) - { - int i = (idx - 1) % 20; - if (((rootDepth / ONE_PLY + rootPos.game_ply() + skipPhase[i]) / skipSize[i]) % 2) - continue; - } + // Distribute search depths across the threads + if (idx) + { + int i = (idx - 1) % 20; + if (idx == Threads.size() - 1 && !ss->excludedMove && rootDepth < 14 * ONE_PLY && rootMoves.size() > 1 && rootPos.see_ge(rootMoves[0].pv[0], VALUE_ZERO + 1)) + ss->excludedMove = rootMoves[0].pv[0]; //lazy_or_stubborn by pb00068 + else + if (((rootDepth / ONE_PLY + rootPos.game_ply() + skipPhase[i]) / skipSize[i]) % 2) + continue; + } // Age out PV variability metric if (mainThread) @@ -409,7 +416,8 @@ void Thread::search() { // and we want to keep the same order for all the moves except the // new PV that goes to the front. Note that in case of MultiPV // search the already searched PV lines are preserved. - std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end()); + if (PVIdx + 1 == multiPV) + std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end()); // If search has been stopped, we break immediately. Sorting and // writing PV back to TT is safe because RootMoves is still @@ -438,15 +446,20 @@ void Thread::search() { Threads.stopOnPonderhit = false; } } - else if (bestValue >= beta) - beta = std::min(bestValue + delta2, VALUE_INFINITE); + + else if ( bestValue >= beta) + beta = std::min(bestValue + delta2, VALUE_INFINITE); //Ivan Ivec + else + { + delta1 += delta1 / 4 + 5; //Ivan Ivec + delta2 += delta2 / 4 + 5; //Ivan Ivec + + assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + + consecutiveEarlyExits[PVIdx] = (bestValue >= beta) ? consecutiveEarlyExits[PVIdx] + 1 : 0; break; - - delta1 += delta1 / 4 + 5; - delta2 += delta2 / 4 + 5; - - assert(alpha >= -VALUE_INFINITE && beta <= VALUE_INFINITE); + } } // Sort the PV lines searched so far and update the GUI @@ -473,14 +486,14 @@ void Thread::search() { if (!mainThread) continue; - - if (Options["FastPlay"]) - { - if ( Time.elapsed() > Time.optimum() / 256 - && ( abs(bestValue) > 12300 || abs(bestValue) >= VALUE_MATE_IN_MAX_PLY )) - Threads.stop = true; - } - +//BEGINS MichaelB7 + if (Options["FastPlay"]) + { + if ( Time.elapsed() > Time.optimum() / 256 + && ( abs(bestValue) > 12300 || abs(bestValue) >= VALUE_MATE_IN_MAX_PLY )) + Threads.stop = true; + } +//ENDS MichaelB7 // If skill level is enabled and time is up, pick a sub-optimal best move if (skill.enabled() && skill.time_to_pick(rootDepth)) skill.pick_best(multiPV); @@ -618,7 +631,7 @@ namespace { // search to overwrite a previous full search TT value, so we use a different // position key in case of an excluded move. excludedMove = ss->excludedMove; - posKey = pos.key() ^ Key(excludedMove); + posKey = pos.key() ^ Key(excludedMove << 16); // isn't a very good hash tte = TT.probe(posKey, ttHit); ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0] @@ -739,18 +752,18 @@ namespace { ss->staticEval, TT.generation()); } - if (skipEarlyPruning || !pos.non_pawn_material()) + if (skipEarlyPruning || !pos.non_pawn_material(pos.side_to_move())) goto moves_loop; // Step 6. Razoring (skipped when in check) if ( !bruteForce && !PvNode && depth < 4 * ONE_PLY - && eval + razor_margin[depth / ONE_PLY] <= alpha) + && eval + razor_margin <= alpha) { if (depth <= ONE_PLY) return qsearch(pos, ss, alpha, alpha+1); - Value ralpha = alpha - razor_margin[depth / ONE_PLY]; + Value ralpha = alpha - razor_margin; Value v = qsearch(pos, ss, ralpha, ralpha+1); if (v <= ralpha) return v; @@ -760,18 +773,17 @@ namespace { if ( !bruteForce && !rootNode && depth < 7 * ONE_PLY && eval - futility_margin(depth) >= beta - && eval < VALUE_KNOWN_WIN ) // Do not return unproven wins + && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; // Step 8. Null move search with verification search (is omitted in PV nodes) - if ( !noNULL - && !PvNode + if ( !PvNode && eval >= beta - && (ss->staticEval >= beta - int(320 * log(depth / ONE_PLY)) + 500) - && thisThread->selDepth + 3 > thisThread->rootDepth / ONE_PLY - && (ss->ply >= thisThread->nmp_ply || ss->ply % 2 == thisThread->pair) - && !(MoveList(pos).size() < 4) - ) + && ss->staticEval >= beta - int(320 * log(depth / ONE_PLY)) + 500 //Corchess/IIvec + && (((ss-2)->currentMove != MOVE_NULL) || (ss-2)->staticEval >= beta - 36 * depth / ONE_PLY + 225) + && thisThread->selDepth + 3 > thisThread->rootDepth / ONE_PLY // idea from Corchess/IIvec + && (ss->ply >= thisThread->nmp_ply || ss->ply % 2 != thisThread->pair) + && !(MoveList(pos).size() < 4)) //MichaelB7 { assert(eval - beta >= 0); @@ -874,7 +886,7 @@ namespace { && tte->depth() >= depth - 3 * ONE_PLY; skipQuiets = false; ttCapture = false; - goodCap = false; + goodCap = false; pvExact = PvNode && ttHit && tte->bound() == BOUND_EXACT; // Step 11. Loop through moves @@ -886,12 +898,20 @@ namespace { if (move == excludedMove) continue; - // At root obey the "searchmoves" option and skip moves not listed in Root - // Move List. As a consequence any illegal move is also skipped. In MultiPV - // mode we also skip PV moves which have been already searched. - if (rootNode && !std::count(thisThread->rootMoves.begin() + thisThread->PVIdx, - thisThread->rootMoves.end(), move)) - continue; + if (rootNode) + { + // At root obey the "searchmoves" option and skip moves not listed in Root + // Move List. As a consequence any illegal move is also skipped. + if (!std::count(thisThread->rootMoves.begin() + thisThread->PVIdx, + thisThread->rootMoves.end(), move)) + continue; + + // In MultiPV mode we not only skip PV moves which have already been searched, + // but also any other move except we have reached the last PV line. + if ( thisThread->PVIdx + 1 < thisThread->multiPV + && move != ttMove) + continue; + } ss->moveCount = ++moveCount; @@ -933,31 +953,22 @@ namespace { ss->excludedMove = MOVE_NONE; if (value < rBeta) - extension = ONE_PLY; + extension = ONE_PLY; } - else if ( givesCheck - && !moveCountPruning - && (pos.see_ge(move) || PvNode )) - extension = ONE_PLY; - - else if ( PvNode - && depth < 13 * ONE_PLY - && pos.advanced_pawn_push(move) - && !moveCountPruning) - extension = ONE_PLY; + else if (!moveCountPruning) + { + if ( givesCheck + && (pos.see_ge(move) || PvNode )) + extension = ONE_PLY; + + else if ( PvNode + && depth < 13 * ONE_PLY + && ( givesCheck || pos.advanced_pawn_push(move) + || ( ss->forcingTree && (ss)->newDepth - depth > 1))) + extension = ONE_PLY; + } - else if ( PvNode - && depth < 13 * ONE_PLY - && ss->forcingTree && (ss)->newDepth - depth > 1 - && !moveCountPruning) - extension = ONE_PLY; - else if ( depth % 5 == 0 - && !pos.non_pawn_material() - && type_of(movedPiece) == KING - && !moveCountPruning) - extension = ONE_PLY; - // Calculate new depth for this move newDepth = depth - ONE_PLY + extension; @@ -989,7 +1000,7 @@ namespace { // Futility pruning: parent node if ( lmrDepth < 7 && !inCheck - && ss->staticEval + 256 + 200 * lmrDepth <= alpha) + && ss->staticEval + 256 + futility_margin(lmrDepth * ONE_PLY) <= alpha)// idea from JD Watson, modifed by MichaelB7 continue; // Prune moves with negative SEE @@ -1031,7 +1042,7 @@ namespace { if ( !bruteForce && depth >= 3 * ONE_PLY && moveCount > 1 && (!captureOrPromotion || moveCountPruning) - && (ss+2)->killers[0] != move + && (ss+2)->killers[0] != move && (ss+2)->killers[1] != move) { Depth r = reduction(improving, depth, moveCount); @@ -1041,7 +1052,7 @@ namespace { else { // Decrease reduction if opponent's move count is high - if ((ss-1)->moveCount > 15) + if ((ss-1)->moveCount > 13 ) r -= ONE_PLY; // Decrease reduction for exact PV nodes @@ -1049,7 +1060,7 @@ namespace { r -= ONE_PLY; // Increase reduction if ttMove is a capture - if (ttCapture) + if (ttCapture || (!pvExact && depth > 13 * ONE_PLY)) r += ONE_PLY; else if(goodCap && !inCheck && !givesCheck) r += ONE_PLY; @@ -1068,7 +1079,7 @@ namespace { ss->statScore = thisThread->mainHistory[~pos.side_to_move()][from_to(move)] + (*contHist[0])[movedPiece][to_sq(move)] + (*contHist[1])[movedPiece][to_sq(move)] - + (*contHist[3])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] - 4000; // Decrease/increase reduction by comparing opponent's stat score @@ -1388,7 +1399,6 @@ namespace { // Don't search moves with negative SEE values if ( (!InCheck || evasionPrunable) - && type_of(move) != PROMOTION && !pos.see_ge(move)) continue; @@ -1643,12 +1653,19 @@ namespace { string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { std::stringstream ss; + int temp = 0; int elapsed = Time.elapsed() + 1; const RootMoves& rootMoves = pos.this_thread()->rootMoves; size_t PVIdx = pos.this_thread()->PVIdx; - size_t multiPV = std::min((size_t)Options["MultiPV"], rootMoves.size()); + size_t multiPV = pos.this_thread()->multiPV; uint64_t nodesSearched = Threads.nodes_searched(); uint64_t tbHits = Threads.tb_hits() + (TB::RootInTB ? rootMoves.size() : 0); + + if (tactical) + { + temp = multiPV; + multiPV = 1; + } for (size_t i = 0; i < multiPV; ++i) { @@ -1688,11 +1705,10 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { for (Move m : rootMoves[i].pv) ss << " " << UCI::move(m, pos.is_chess960()); } - + if (tactical) multiPV = temp; return ss.str(); } - /// RootMove::extract_ponder_from_tt() is called in case we have no ponder move /// before exiting the search, for instance, in case we stop the search during a /// fail high at root. We try hard to have a ponder move to return to the GUI, diff --git a/Engines/Windows/mcbrain/src/search.h b/Engines/Windows/mcbrain/src/search.h index 90842f6..f1ac439 100644 --- a/Engines/Windows/mcbrain/src/search.h +++ b/Engines/Windows/mcbrain/src/search.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -104,8 +104,7 @@ extern LimitsType Limits; void init(); void clear(); -Value respect(const Position& pos, Color c); - + } // namespace Search #endif // #ifndef SEARCH_H_INCLUDED diff --git a/Engines/Windows/mcbrain/src/tbprobe.cpp b/Engines/Windows/mcbrain/src/tbprobe.cpp index d019f28..09e031c 100644 --- a/Engines/Windows/mcbrain/src/tbprobe.cpp +++ b/Engines/Windows/mcbrain/src/tbprobe.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/tbprobe.h b/Engines/Windows/mcbrain/src/tbprobe.h index 7c0bbe7..97ec7b1 100644 --- a/Engines/Windows/mcbrain/src/tbprobe.h +++ b/Engines/Windows/mcbrain/src/tbprobe.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/thread.cpp b/Engines/Windows/mcbrain/src/thread.cpp index 616b88f..47a21c1 100644 --- a/Engines/Windows/mcbrain/src/thread.cpp +++ b/Engines/Windows/mcbrain/src/thread.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017*2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,6 +25,7 @@ #include "movegen.h" #include "search.h" #include "thread.h" +#include "uci.h" #include "tbprobe.h" ThreadPool Threads; // Global object @@ -36,7 +37,6 @@ ThreadPool Threads; // Global object Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { wait_for_search_finished(); - clear(); // Zero-init histories (based on std::array) } @@ -93,7 +93,13 @@ void Thread::wait_for_search_finished() { void Thread::idle_loop() { - WinProcGroup::bindThisThread(idx); + // If OS already scheduled us on a different group than 0 then don't overwrite + // the choice, eventually we are one of many one-threaded processes running on + // some Windows NUMA hardware, for instance in fishtest. To make it simple, + // just check if running threads are below a threshold, in this case all this + // NUMA machinery is not needed. + if (Options["Threads"] >= 8) + WinProcGroup::bindThisThread(idx); while (true) { @@ -111,42 +117,40 @@ void Thread::idle_loop() { } } +/// ThreadPool::set() creates/destroys threads to match the requested number. +/// Created and launced threads wil go immediately to sleep in idle_loop. +/// Upon resizing, threads are recreated to allow for binding if necessary. -/// ThreadPool::init() creates and launches the threads that will go -/// immediately to sleep in idle_loop. We cannot use the constructor because -/// Threads is a static object and we need a fully initialized engine at -/// this point due to allocation of Endgames in the Thread constructor. - -void ThreadPool::init(size_t requested) { - - push_back(new MainThread(0)); - set(requested); -} +void ThreadPool::set(size_t requested) { + if (size() > 0) { // destroy any existing thread(s) + main()->wait_for_search_finished(); -/// ThreadPool::exit() terminates threads before the program exits. Cannot be -/// done in the destructor because threads must be terminated before deleting -/// any static object, so before main() returns. + while (size() > 0) + delete back(), pop_back(); + } -void ThreadPool::exit() { + if (requested > 0) { // create new thread(s) + push_back(new MainThread(0)); - main()->wait_for_search_finished(); - set(0); + while (size() < requested) + push_back(new Thread(size())); + clear(); + } } +/// ThreadPool::clear() sets threadPool data to initial values. -/// ThreadPool::set() creates/destroys threads to match the requested number +void ThreadPool::clear() { -void ThreadPool::set(size_t requested) { + for (Thread* th : *this) + th->clear(); - while (size() < requested) - push_back(new Thread(size())); - - while (size() > requested) - delete back(), pop_back(); + main()->callsCnt = 0; + main()->previousScore = VALUE_INFINITE; + main()->previousTimeReduction = 1; } - /// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and /// returns immediately. Main thread will wake up other threads and start the search. @@ -182,7 +186,7 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, // is shared by threads but is accessed in read-only mode. StateInfo tmp = setupStates->back(); - for (Thread* th : Threads) + for (Thread* th : *this) { th->nodes = th->tbHits = 0; th->rootDepth = th->completedDepth = DEPTH_ZERO; diff --git a/Engines/Windows/mcbrain/src/thread.h b/Engines/Windows/mcbrain/src/thread.h index 87c5029..25a6511 100644 --- a/Engines/Windows/mcbrain/src/thread.h +++ b/Engines/Windows/mcbrain/src/thread.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -61,8 +61,8 @@ class Thread { Pawns::Table pawnsTable; Material::Table materialTable; Endgames endgames; - size_t PVIdx; - int selDepth, nmp_ply, pair; + size_t PVIdx, multiPV; + int selDepth, nmp_ply, pair; std::atomic nodes, tbHits; Position rootPos; @@ -100,11 +100,19 @@ struct ThreadPool : public std::vector { void init(size_t); // No constructor and destructor, threads rely on globals that should void exit(); // be initialized and valid during the whole thread lifetime. void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); + void clear(); void set(size_t); MainThread* main() const { return static_cast(front()); } uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } + + Depth lowestDepth(){ + Depth lowest = DEPTH_MAX; + for (Thread* th: *this) + lowest = std::min(lowest, th->rootDepth); + return lowest; + } std::atomic_bool stop, ponder, stopOnPonderhit; diff --git a/Engines/Windows/mcbrain/src/thread_win32.h b/Engines/Windows/mcbrain/src/thread_win32.h index ca737e9..d69e817 100644 --- a/Engines/Windows/mcbrain/src/thread_win32.h +++ b/Engines/Windows/mcbrain/src/thread_win32.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/timeman.cpp b/Engines/Windows/mcbrain/src/timeman.cpp index 579921e..6a854c9 100644 --- a/Engines/Windows/mcbrain/src/timeman.cpp +++ b/Engines/Windows/mcbrain/src/timeman.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,8 @@ */ #include +#include +#include #include "search.h" #include "timeman.h" @@ -31,46 +33,41 @@ namespace { enum TimeType { OptimumTime, MaxTime }; - int remaining(int myTime, int myInc, int moveOverhead, int movesToGo, - int moveNum, bool ponder, TimeType type) { + const int MoveHorizon = 50; // Plan time management at most this many moves ahead + const double MaxRatio = 7.09; // When in trouble, we can step over reserved time with this ratio + const double StealRatio = 0.35; // However we must not steal time from remaining moves over this ratio - if (myTime <= 0) - return 0; - double ratio; // Which ratio of myTime we are going to use + // move_importance() is a skew-logistic function based on naive statistical + // analysis of "how many games are still undecided after n half-moves". Game + // is considered "undecided" as long as neither side has >275cp advantage. + // Data was extracted from the CCRL game database with some simple filtering criteria. - // Usage of increment follows quadratic distribution with the maximum at move 25 - double inc = myInc * std::max(55.0, 120 - 0.12 * (moveNum - 25) * (moveNum - 25)); + double move_importance(int ply) { - // In moves-to-go we distribute time according to a quadratic function with - // the maximum around move 20 for 40 moves in y time case. - if (movesToGo) - { - ratio = (type == OptimumTime ? 1.0 : 6.0) / std::min(50, movesToGo); + const double XScale = 7.64; + const double XShift = 58.4; + const double Skew = 0.183; - if (moveNum <= 40) - ratio *= 1.1 - 0.001 * (moveNum - 20) * (moveNum - 20); - else - ratio *= 1.5; + return pow((1 + exp((ply - XShift) / XScale)), -Skew) + DBL_MIN; // Ensure non-zero + } + + template + int remaining(int myTime, int movesToGo, int ply, int slowMover) { - if (movesToGo > 1) - ratio = std::min(0.75, ratio); + const double TMaxRatio = (T == OptimumTime ? 1 : MaxRatio); + const double TStealRatio = (T == OptimumTime ? 0 : StealRatio); - ratio *= 1 + inc / (myTime * 8.5); - } - // Otherwise we increase usage of remaining time as the game goes on - else - { - double k = 1 + 20 * moveNum / (500.0 + moveNum); - ratio = (type == OptimumTime ? 0.017 : 0.07) * (k + inc / myTime); - } + double moveImportance = (move_importance(ply) * slowMover) / 100; + double otherMovesImportance = 0; - int time = int(std::min(1.0, ratio) * std::max(0, myTime - moveOverhead)); + for (int i = 1; i < movesToGo; ++i) + otherMovesImportance += move_importance(ply + 2 * i); - if (type == OptimumTime && ponder) - time = 5 * time / 4; + double ratio1 = (TMaxRatio * moveImportance) / (TMaxRatio * moveImportance + otherMovesImportance); + double ratio2 = (moveImportance + TStealRatio * otherMovesImportance) / (moveImportance + otherMovesImportance); - return time; + return int(myTime * std::min(ratio1, ratio2)); // Intel C++ asks for an explicit cast } } // namespace @@ -85,11 +82,12 @@ namespace { /// inc > 0 && movestogo == 0 means: x basetime + z increment /// inc > 0 && movestogo != 0 means: x moves in y minutes + z increment -void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) -{ - int moveOverhead = Options["Move Overhead"]; - int npmsec = Options["nodestime"]; - bool ponder = Options["Ponder"]; +void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) { + + int minThinkingTime = Options["Minimum Thinking Time"]; + int moveOverhead = Options["Move Overhead"]; + int slowMover = Options["Slow Mover"]; + int npmsec = Options["nodestime"]; // If we have to play in 'nodes as time' mode, then convert from time // to nodes, and use resulting values in time management formulas. @@ -106,11 +104,30 @@ void TimeManagement::init(Search::LimitsType& limits, Color us, int ply) limits.npmsec = npmsec; } - int moveNum = (ply + 1) / 2; - startTime = limits.startTime; - optimumTime = remaining(limits.time[us], limits.inc[us], moveOverhead, - limits.movestogo, moveNum, ponder, OptimumTime); - maximumTime = remaining(limits.time[us], limits.inc[us], moveOverhead, - limits.movestogo, moveNum, ponder, MaxTime); + optimumTime = maximumTime = std::max(limits.time[us], minThinkingTime); + + const int MaxMTG = limits.movestogo ? std::min(limits.movestogo, MoveHorizon) : MoveHorizon; + + // We calculate optimum time usage for different hypothetical "moves to go"-values + // and choose the minimum of calculated search time values. Usually the greatest + // hypMTG gives the minimum values. + for (int hypMTG = 1; hypMTG <= MaxMTG; ++hypMTG) + { + // Calculate thinking time for hypothetical "moves to go"-value + int hypMyTime = limits.time[us] + + limits.inc[us] * (hypMTG - 1) + - moveOverhead * (2 + std::min(hypMTG, 40)); + + hypMyTime = std::max(hypMyTime, 0); + + int t1 = minThinkingTime + remaining(hypMyTime, hypMTG, ply, slowMover); + int t2 = minThinkingTime + remaining(hypMyTime, hypMTG, ply, slowMover); + + optimumTime = std::min(t1, optimumTime); + maximumTime = std::min(t2, maximumTime); + } + + if (Options["Ponder"]) + optimumTime += optimumTime / 4; } diff --git a/Engines/Windows/mcbrain/src/timeman.h b/Engines/Windows/mcbrain/src/timeman.h index 427271a..4d3172b 100644 --- a/Engines/Windows/mcbrain/src/timeman.h +++ b/Engines/Windows/mcbrain/src/timeman.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/mcbrain/src/tt.cpp b/Engines/Windows/mcbrain/src/tt.cpp index 9bb093d..1aed0f3 100644 --- a/Engines/Windows/mcbrain/src/tt.cpp +++ b/Engines/Windows/mcbrain/src/tt.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ TranspositionTable TT; // Our global transposition table void TranspositionTable::resize(size_t mbSize) { - size_t newClusterCount = size_t(1) << msb((mbSize * 1024 * 1024) / sizeof(Cluster)); + size_t newClusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); if (newClusterCount == clusterCount) return; @@ -42,7 +42,7 @@ void TranspositionTable::resize(size_t mbSize) { clusterCount = newClusterCount; free(mem); - mem = calloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1, 1); + mem = malloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1); if (!mem) { @@ -52,6 +52,7 @@ void TranspositionTable::resize(size_t mbSize) { } table = (Cluster*)((uintptr_t(mem) + CacheLineSize - 1) & ~(CacheLineSize - 1)); + clear(); } diff --git a/Engines/Windows/mcbrain/src/tt.h b/Engines/Windows/mcbrain/src/tt.h index 56646a6..e0dc386 100644 --- a/Engines/Windows/mcbrain/src/tt.h +++ b/Engines/Windows/mcbrain/src/tt.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -105,9 +105,9 @@ class TranspositionTable { void resize(size_t mbSize); void clear(); - // The lowest order bits of the key are used to get the index of the cluster + // The 32 lowest order bits of the key are used to get the index of the cluster TTEntry* first_entry(const Key key) const { - return &table[(size_t)key & (clusterCount - 1)].entry[0]; + return &table[(uint32_t(key) * uint64_t(clusterCount)) >> 32].entry[0]; } private: diff --git a/Engines/Windows/mcbrain/src/types.h b/Engines/Windows/mcbrain/src/types.h index 77acc44..7b4318e 100644 --- a/Engines/Windows/mcbrain/src/types.h +++ b/Engines/Windows/mcbrain/src/types.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -334,7 +334,7 @@ inline Square& operator-=(Square &s, Direction d) { return s = s - d; } /// Only declared but not defined. We don't want to multiply two scores due to /// a very high risk of overflow. So user should explicitly convert to integer. -Score operator*(Score s1, Score s2) = delete; +Score operator*(Score, Score) = delete; /// Division of a Score must be handled separately for each term inline Score operator/(Score s, int i) { diff --git a/Engines/Windows/mcbrain/src/tzbook.cpp b/Engines/Windows/mcbrain/src/tzbook.cpp deleted file mode 100644 index 4052256..0000000 --- a/Engines/Windows/mcbrain/src/tzbook.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "tzbook.h" -#include "uci.h" -#include "movegen.h" -#include "thread.h" -#include -#include "misc.h" -#include - -TZBook tzbook; // global TZBook - -using namespace std; - -int qsort_compare_int(const void* a, const void* b) -{ - const int int_a = *((const int*)a); - const int int_b = *((const int*)b); - - if (int_a == int_b) return 0; - else if (int_a < int_b) return -1; - else return 1; -} - -TZBook::TZBook() -{ - keycount = 0; - book_move2_probability = 0; - last_position = 0; - akt_position = 0; - last_anz_pieces = 0; - akt_anz_pieces = 0; - search_counter = 0; - tzhash2 = NULL; - do_search = true; - enabled = false; -} - -TZBook::~TZBook() -{ -} - - -void TZBook::init(const std::string& path) -{ - if (path.length() == 0) return; - - const char *p = path.c_str(); - if (strcmp(p, "") == 0) - return; - - FILE *fpt = fopen(p, "rb"); - if (fpt == NULL) - { - sync_cout << "info string Could not open " << path << sync_endl; - return; - } - - if (tzhash2 != NULL) - { - free(tzhash2); - tzhash2 = NULL; - } - - fseek(fpt, 0L, SEEK_END); - int filesize = ftell(fpt); - fseek(fpt, 0L, SEEK_SET); - - keycount = filesize / 8; - tzhash2 = new TZHash2[keycount]; - - fread(tzhash2, 1, filesize, fpt); - fclose(fpt); - - sync_cout << "info string Book loaded: " << path << sync_endl; - - srand((int)time(NULL)); - enabled = true; -} - - -void TZBook::set_book_move2_probability(int book_move2_prob) -{ - book_move2_probability = book_move2_prob; -} - - -Move TZBook::probe2(Position& pos) -{ - Move m = MOVE_NONE; - if (!enabled) return m; - - akt_position = pos.pieces(); - akt_anz_pieces = popcount(akt_position); - - if (do_search == false) - { - Bitboard b = akt_position ^ last_position; - int n2 = popcount(b); - - if (n2 > 4) do_search = true; - if (akt_anz_pieces > last_anz_pieces) do_search = true; - if (akt_anz_pieces < last_anz_pieces - 2) do_search = true; - } - - last_position = akt_position; - last_anz_pieces = akt_anz_pieces; - - if (do_search) - { - TZHash2 *tz = probe2(pos.key()); - if (tz == NULL) - { - search_counter++; - if (search_counter > 2) - { - do_search = false; - search_counter = 0; - } - } - else - { - if (pos.is_draw(64)) - m = get_move_from_draw_position(pos, tz); - else - m = get_move(pos, tz); - } - } - - return m; -} - - -TZHash2 *TZBook::probe2(Key key) -{ - uint32_t key1 = key >> 32; - unsigned short key2 = key >> 16 & 0xFFFF; - - int start = 0; - int end = keycount; - - for (;;) - { - int mid = (end + start) / 2; - - if (tzhash2[mid].key1 < key1) - start = mid; - else - { - if (tzhash2[mid].key1 > key1) - end = mid; - else - { - start = max(mid - 4, 0); - end = min(mid + 4, keycount); - } - } - - if (end - start < 9) - break; - } - - for (int i = start; i < end; i++) - if ((key1 == tzhash2[i].key1) && (key2 == tzhash2[i].key2)) - return &(tzhash2[i]); - - return NULL; -} - -Move TZBook::movenumber_to_move(Position& pos, int n) -{ - const ExtMove *m = MoveList(pos).begin(); - size_t size = MoveList(pos).size(); - Move *mv = new Move[size]; - for (unsigned int i = 0; i < size; i++) - mv[i] = m[i].move; - - qsort(mv, size, sizeof(mv[0]), qsort_compare_int); - - return mv[n]; -} - -bool TZBook::check_draw(Move m, Position& pos) -{ - StateInfo st; - - pos.do_move(m, st, pos.gives_check(m)); - bool draw = pos.is_draw(64); - pos.undo_move(m); - - return draw; -} - - -Move TZBook::get_move_from_draw_position(Position& pos, TZHash2 *tz) -{ - Move m = movenumber_to_move(pos, tz->move_number); - if (!check_draw(m, pos)) - return m; - - if (tz->move_number2 == 255) - return m; - - m = movenumber_to_move(pos, tz->move_number2); - if (!check_draw(m, pos)) - return m; - - return MOVE_NONE; -} - - -Move TZBook::get_move(Position& pos, TZHash2 *tz) -{ - Move m1 = movenumber_to_move(pos, tz->move_number); - if ((book_move2_probability == 0) || (tz->move_number2 == 255)) - return m1; - - Move m2 = movenumber_to_move(pos, tz->move_number2); - if ((book_move2_probability == 100) || (rand() % 100 < book_move2_probability)) - return m2; - - return m1; -} \ No newline at end of file diff --git a/Engines/Windows/mcbrain/src/tzbook.h b/Engines/Windows/mcbrain/src/tzbook.h deleted file mode 100644 index 744618a..0000000 --- a/Engines/Windows/mcbrain/src/tzbook.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef TZBOOK_H_INCLUDED -#define TZBOOK_H_INCLUDED - -#include "bitboard.h" -#include "position.h" -#include "string.h" - -struct TZHash2 -{ - uint32_t key1; - uint16_t key2; - unsigned char move_number; - unsigned char move_number2; -}; - -class TZBook -{ - public: - - Bitboard last_position; - Bitboard akt_position; - int last_anz_pieces; - int akt_anz_pieces; - int search_counter; - - bool enabled, do_search; - int book_move2_probability; - - TZBook(); - ~TZBook(); - - void init(const std::string& path); - void set_book_move2_probability(int book_move2_prob); - - Move probe2(Position& pos); - TZHash2 *probe2(Key key); - -private: - - int keycount; - TZHash2 *tzhash2; - bool check_draw(Move m, Position& pos); - Move get_move_from_draw_position(Position& pos, TZHash2 *tz); - Move get_move(Position& pos, TZHash2 *tz); - Move movenumber_to_move(Position& pos, int n); -}; - -extern TZBook tzbook; - -#endif // #ifndef TZBOOK_H_INCLUDED \ No newline at end of file diff --git a/Engines/Windows/mcbrain/src/uci.cpp b/Engines/Windows/mcbrain/src/uci.cpp index ecbbe8b..40efb6d 100644 --- a/Engines/Windows/mcbrain/src/uci.cpp +++ b/Engines/Windows/mcbrain/src/uci.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -273,13 +273,13 @@ void UCI::loop(int argc, char* argv[]) { else if (token == "position") { position(pos, is, states); - if (Options["Clean_Search"] == 1) + if (Options["Clean_Search"] == true) Search::clear(); } else if (token == "p") { position(pos, is, states); - if (Options["Clean Search"] == 1) + if (Options["Clean_Search"] == true) Search::clear(); } else if (token == "ucinewgame") Search::clear(); diff --git a/Engines/Windows/mcbrain/src/uci.h b/Engines/Windows/mcbrain/src/uci.h index 1564f72..713468b 100644 --- a/Engines/Windows/mcbrain/src/uci.h +++ b/Engines/Windows/mcbrain/src/uci.h @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -51,11 +51,13 @@ class Option { Option(bool v, OnChange = nullptr); Option(const char* v, OnChange = nullptr); Option(int v, int minv, int maxv, OnChange = nullptr); + Option(const char* v, const char *cur, OnChange = nullptr); Option& operator=(const std::string&); void operator<<(const Option&); operator int() const; operator std::string() const; + bool operator==(const char*); private: friend std::ostream& operator<<(std::ostream&, const OptionsMap&); diff --git a/Engines/Windows/mcbrain/src/ucioption.cpp b/Engines/Windows/mcbrain/src/ucioption.cpp index 8389ca1..3c31396 100644 --- a/Engines/Windows/mcbrain/src/ucioption.cpp +++ b/Engines/Windows/mcbrain/src/ucioption.cpp @@ -3,7 +3,7 @@ Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad (Stockfish Authors) Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (Stockfish Authors) - Copyright (C) 2017 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) + Copyright (C) 2017-2018 Michael Byrne, Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad (McBrain Authors) McBrain is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -60,8 +60,15 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const void init(OptionsMap& o) { - const int MaxHashMB = Is64Bit ? 1024 * 1024 : 2048; + // at most 2^32 clusters. + const int MaxHashMB = Is64Bit ? 131072 : 2048; + o["Debug Log File"] << Option("", on_logger); + o["Contempt"] << Option(20, -100, 100); + o["Analysis Contempt"] << Option("Off var Off var White var Black var Both", "Off"); + o["UCI_AnalyseMode"] << Option(false); + o["Tactical"] << Option(0, 0, 8); + o["Threads"] << Option(1, 1, 512, on_threads); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Ponder"] << Option(false); o["Threads"] << Option(1, 1, 512, on_threads); @@ -70,21 +77,25 @@ void init(OptionsMap& o) { o["BruteForce"] << Option(false); o["FastPlay"] << Option(false); o["No_Null_Moves"] << Option(false); - + o["UCI_Chess960"] << Option(false); o["UCI_LimitStrength"] << Option(false); + o["UCI_ELO"] << Option(1500, 1500, 2800); - o["MultiPV"] << Option(1, 1, 500); o["Skill Level"] << Option(20, 0, 20); - o["Contempt"] << Option(2, -150, 150); - o["Tactical"] << Option(0, 0, 8); - o["Move Overhead"] << Option(100, 0, 5000); + o["MultiPV"] << Option(1, 1, 256); + + + o["Move Overhead"] << Option(30, 0, 5000); + o["Minimum Thinking Time"] << Option(20, 0, 5000); + o["Slow Mover"] << Option(89, 10, 1000); o["nodestime"] << Option(0, 0, 10000); - o["UCI_Chess960"] << Option(false); + o["SyzygyPath"] << Option("", on_tb_path); o["SyzygyProbeDepth"] << Option(1, 1, 100); o["Syzygy50MoveRule"] << Option(true); o["SyzygyProbeLimit"] << Option(6, 0, 6); + o["Book_Enabled"] << Option(false); o["BookFile"] << Option("", on_book_file); o["BestBookMove"] << Option(true, on_best_book_move); o["BookDepth"] << Option(255, 1, 255, on_book_depth); @@ -131,6 +142,9 @@ Option::Option(OnChange f) : type("button"), min(0), max(0), on_change(f) Option::Option(int v, int minv, int maxv, OnChange f) : type("spin"), min(minv), max(maxv), on_change(f) { defaultValue = currentValue = std::to_string(v); } +Option::Option(const char* v, const char* cur, OnChange f) : type("combo"), min(0), max(0), on_change(f) +{ defaultValue = v; currentValue = cur; } + Option::operator int() const { assert(type == "check" || type == "spin"); return (type == "spin" ? stoi(currentValue) : currentValue == "true"); @@ -141,6 +155,11 @@ Option::operator std::string() const { return currentValue; } +bool Option::operator==(const char* s) { + assert(type == "combo"); + return !CaseInsensitiveLess()(currentValue, s) && !CaseInsensitiveLess()(s, currentValue); +} + /// operator<<() inits options and assigns idx in the correct printing order diff --git a/Engines/Windows/stockfish/.travis.yml b/Engines/Windows/stockfish/.travis.yml index af0bb2b..8586921 100644 --- a/Engines/Windows/stockfish/.travis.yml +++ b/Engines/Windows/stockfish/.travis.yml @@ -9,7 +9,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['g++-6', 'g++-6-multilib', 'g++-multilib', 'valgrind'] + packages: ['g++-6', 'g++-6-multilib', 'g++-multilib', 'valgrind', 'expect'] env: - COMPILER=g++-6 - COMP=gcc @@ -19,7 +19,7 @@ matrix: addons: apt: sources: ['ubuntu-toolchain-r-test'] - packages: ['clang', 'g++-multilib', 'valgrind'] + packages: ['clang', 'g++-multilib', 'valgrind', 'expect'] env: - COMPILER=clang++ - COMP=clang @@ -44,12 +44,30 @@ before_script: - cd src script: - - make clean && make build ARCH=x86-64 && ./stockfish bench 2>&1 >/dev/null | grep 'Nodes searched' | tee bench1 - - make clean && make build ARCH=x86-32 && ./stockfish bench 2>&1 >/dev/null | grep 'Nodes searched' | tee bench2 - - echo "Checking for same bench numbers..." - - diff bench1 bench2 > result - - test ! -s result - # if valgrind is available check the build is without error, reduce depth to speedup testing, but not too shallow to catch more cases. - - if [ -x "$(command -v valgrind )" ] ; then make clean && make ARCH=x86-64 debug=yes build && valgrind --error-exitcode=42 ./stockfish bench 128 1 10 default depth 1>/dev/null ; fi - # use g++-6 as a proxy for having sanitizers ... might need revision as they become available for more recent versions of clang/gcc than trusty provides - - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make ARCH=x86-64 sanitize=yes build && ! ./stockfish bench 2>&1 | grep "runtime error:" ; fi + # Obtain bench reference from git log + - git log HEAD | grep "\b[Bb]ench[ :]\+[0-9]\{7\}" | head -n 1 | sed "s/[^0-9]*\([0-9][0-9]*\)/\1/g" > git_sig + - export benchref=$(cat git_sig) + - echo "Reference bench:" $benchref + # + # Verify bench number against various builds + - export CXXFLAGS=-Werror + - make clean && make -j2 ARCH=x86-64 optimize=no debug=yes build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-32 optimize=no debug=yes build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-32 build && ../tests/signature.sh $benchref + - make clean && make -j2 ARCH=x86-64 build && ../tests/signature.sh $benchref + # + # Check perft and reproducible search + - ../tests/perft.sh + - ../tests/reprosearch.sh + # + # Valgrind + # + - export CXXFLAGS=-O1 + - if [ -x "$(command -v valgrind )" ]; then make clean && make -j2 ARCH=x86-64 debug=yes optimize=no build > /dev/null && ../tests/instrumented.sh --valgrind; fi + - if [ -x "$(command -v valgrind )" ]; then ../tests/instrumented.sh --valgrind-thread; fi + # + # Sanitizer + # + # Use g++-6 as a proxy for having sanitizers, might need revision as they become available for more recent versions of clang/gcc + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=undefined optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-undefined; fi + - if [[ "$COMPILER" == "g++-6" ]]; then make clean && make -j2 ARCH=x86-64 sanitize=thread optimize=no debug=yes build > /dev/null && ../tests/instrumented.sh --sanitizer-thread; fi diff --git a/Engines/Windows/stockfish/AUTHORS b/Engines/Windows/stockfish/AUTHORS index 5cd63d2..9b91b93 100644 --- a/Engines/Windows/stockfish/AUTHORS +++ b/Engines/Windows/stockfish/AUTHORS @@ -1,98 +1,98 @@ -# Generated with git shortlog -sn | cut -c8-', which sorts by commits (manually ordered the first four authors) +# Generated with 'git shortlog -sn | cut -c8-', which sorts by commits, manually ordered the first four authors, merged duplicates Tord Romstad -Marco Costalba -Joona Kiiski -Gary Linscott -lucasart +Marco Costalba (mcostalba) +Joona Kiiski (zamar) +Gary Linscott (glinscott) +Lucas Braesch (lucasart) +Bill Henry (VoyagerOne) mstembera -Lucas Braesch +Stéphane Nicolet (Stephane Nicolet, snicolet) Stefan Geschwentner +Alain SAVARD (Rocky640) +Jörg Oster (Joerg Oster, joergoster) Reuven Peleg -Chris Caino -joergoster -VoyagerOne +Chris Caino (Chris Cain, ceebo) Jean-Francois Romang homoSapiensSapiens -Alain SAVARD +Leonid Pechenik +Stefano Cardanobile (Stefano80) Arjun Temurnikar -Stéphane Nicolet -Uri Blass +Uri Blass (uriblass) jundery -Ralph Stößer -Ajith -Leonid Pechenik -Stefano80 -Tom Vijlbrief +Ajith (ajithcj) hxim -snicolet +Ralph Stößer (Ralph Stoesser) +Guenther Demetz +Jonathan Calovski (Mysseno) +Tom Vijlbrief +mbootsector Daylen Yang +ElbertoOne Henri Wiechers -Jonathan Calovski -mbootsector +loco-loco +Joost VandeVondele (Joost Vandevondele) +Ronald de Man (syzygy) +DU-jdto David Zar Eelco de Groot Jerry Donald -Joerg Oster -Jörg Oster +NicklasPersson Ryan Schmitt -mcostalba Alexander Kure Dan Schmidt H. Felix Wittmann +Jacques Joseph R. Prostko Justin Blanchard Linus Arver -NicklasPersson +Luca Brivio +Lyudmil Antonov Rodrigo Exterckötter Tjäder Ron Britvich -Ronald de Man RyanTaker Vince Negri -ceebo -jhellis3 -ppigazzini +erbsenzaehler +Joseph Hellis (jhellis3) shane31 +Andrew Grant Andy Duplain Auguste Pop Balint Pfliegel -Chris Cain -DU-jdto Dariusz Orzechowski DiscanX Ernesto Gatti Gregor Cramer -Guenther Demetz -Hiraoka Takuya +Hiraoka Takuya (HiraokaTakuya) Hongzhi Cheng -Joseph Hellis +IIvec Kelly Wilson Ken T Takusagawa Kojirion -Luca Brivio +Krgp Matt Sullivan Matthew Lai Matthew Sullivan Michel Van den Bergh -Mysseno +Niklas Fiekas Oskar Werkelin Ahlin Pablo Vazquez Pascal Romaret -Ralph Stoesser -Ralph Stößer Raminder Singh Richard Lloyd Ryan Takker -Stephane Nicolet Thanar2 absimaldata +atumanian braich +fanon +gamander gguliash kinderchocolate -loco-loco pellanda +ppigazzini renouve sf-x thaspel unknown -uriblass + diff --git a/Engines/Windows/stockfish/Readme.md b/Engines/Windows/stockfish/Readme.md index 29ebf59..e3b36cc 100644 --- a/Engines/Windows/stockfish/Readme.md +++ b/Engines/Windows/stockfish/Readme.md @@ -10,7 +10,7 @@ Partner or Fritz) in order to be used comfortably. Read the documentation for your GUI of choice for information about how to use Stockfish with it. -This version of Stockfish supports up to 128 cores. The engine defaults +This version of Stockfish supports up to 512 cores. The engine defaults to one search thread, so it is therefore recommended to inspect the value of the *Threads* UCI parameter, and to make sure it equals the number of CPU cores on your computer. @@ -96,6 +96,14 @@ compile (for instance with Microsoft MSVC) you need to manually set/unset some switches in the compiler command line; see file *types.h* for a quick reference. +### Resource For Understanding the Code Base + +* [Chessprogramingwiki](https://chessprogramming.wikispaces.com) has good overall chess engines explanations +(techniques used here are well explained like hash maps etc), it was +also recommended by the [support at stockfish.](http://support.stockfishchess.org/discussions/questions/1132-how-to-understand-stockfish-sources) + +* [Here](https://chessprogramming.wikispaces.com/Stockfish) you can find a set of features and techniques used by stockfish and each of them is explained at the wiki, however, it's a generic way rather than focusing on stockfish's own implementation, but it will still help you. + ### Terms of use diff --git a/Engines/Windows/stockfish/Top CPU Contributors.txt b/Engines/Windows/stockfish/Top CPU Contributors.txt new file mode 100644 index 0000000..4c2aa47 --- /dev/null +++ b/Engines/Windows/stockfish/Top CPU Contributors.txt @@ -0,0 +1,132 @@ +Contributors with >10,000 CPU hours as of January 23, 2018 +Thank you! + +Username CPU Hours Games played +mibere 518300 41835669 +crunchy 375564 29121434 +cw 371664 28748719 +fastgm 299773 20765374 +JojoM 220590 15299913 +glinscott 204517 13932027 +bking_US 187568 12233168 +ctoks 169342 13475495 +spams 149531 10940322 +Thanar 137015 11714855 +velislav 127305 10047749 +vdbergh 121741 9056874 +malala 117291 8126488 +vdv 117218 8289983 +leszek 114825 8331897 +dsmith 114010 7622414 +CSU_Dynasty 113516 9582758 +sqrt2 112407 8782694 +marrco 111143 8222921 +drabel 108168 9061580 +BrunoBanani 104938 7448565 +Data 94621 8433010 +CoffeeOne 90394 3964243 +BRAVONE 80811 5341681 +psk 77195 6156031 +brabos 70284 5685893 +Fisherman 66650 5572406 +nssy 64587 5369140 +Pking_cda 64499 5704075 +sterni1971 63488 5070004 +mgrabiak 62385 5420812 +tvijlbrief 58957 4154234 +jromang 58854 4704502 +dv8silencer 57421 3961325 +sunu 56620 4609155 +tinker 56039 4204914 +biffhero 55743 4810039 +teddybaer 52982 4740444 +bcross 50548 5071599 +renouve 50318 3544864 +Freja 50296 3805120 +robnjr 47504 4131742 +eva42 46542 4044694 +davar 46538 4030604 +finfish 46244 3481661 +rap 46201 3219490 +ttruscott 45037 3645430 +solarlight 44155 4074841 +TueRens 41372 3891510 +ElbertoOne 41321 3920894 +Antihistamine 39218 2792761 +mhunt 38991 2697512 +bigpen0r 37820 3149955 +homyur 35569 3009637 +VoyagerOne 35137 3302650 +mhoram 34770 2684128 +racerschmacer 33022 3231055 +speedycpu 32043 2531964 +EthanOConnor 31638 2143255 +oryx 29574 2767730 +Pyafue 28885 1986098 +jkiiski 28014 1923255 +Garf 27579 2770144 +slakovv 27017 2031279 +Bobo1239 27000 2488707 +pb00067 26817 2306694 +robal 26337 2316795 +hyperbolic.tom 26248 2200777 +rkl 24898 2236013 +SC 23988 2126825 +nabildanial 23524 1586321 +achambord 23495 1942546 +Sharaf_DG 22975 1790697 +chriswk 22876 1947731 +anst 22568 2013953 +Patrick_G 22435 1682293 +cuistot 22201 1383031 +gri 21901 1820968 +Prcuvu 21182 1890546 +Zirie 21171 1493227 +JanErik 20596 1791991 +Isidor 20560 1730290 +xor12 20535 1819280 +team-oh 20364 1653708 +nesoneg 20264 1493435 +rstoesser 19802 1335177 +grandphish2 19402 1834196 +sg4032 18427 1671742 +dew 18263 1423326 +ianh2105 18133 1668562 +MazeOfGalious 18022 1644593 +ville 17900 1539130 +j3corre 17607 975954 +eudhan 17502 1424648 +iisiraider 17175 1118788 +jundery 17172 1115855 +SFTUser 16635 1363975 +purplefishies 16621 1106850 +DragonLord 16599 1252348 +chris 15274 1575333 +xoto 14900 1486261 +dju 14861 901552 +dex 14647 1228763 +nordlandia 14551 1369718 +ronaldjerum 14361 1210607 +OssumOpossum 14149 1029265 +IgorLeMasson 13844 1228391 +enedene 13762 935618 +ako027ako 13442 1250249 +AdrianSA 13324 924980 +bpfliegel 13318 886523 +ncfish1 13056 932344 +wei 12863 1369596 +jpulman 12776 854815 +horst.prack 12436 1151505 +joster 12424 986622 +cisco2015 12265 1205019 +fatmurphy 12015 901134 +modolief 11228 926456 +Dark_wizzie 11214 1017910 +mschmidt 10973 818594 +eastorwest 10970 1117836 +infinity 10762 746397 +SapphireBrand 10692 1024604 +Thomas A. 10553 736094 +pgontarz 10294 878746 +Andrew Grant 10195 922933 +stocky 10083 718114 diff --git a/Engines/Windows/stockfish/Windows/stockfish_9_x32.exe b/Engines/Windows/stockfish/Windows/stockfish_9_x32.exe new file mode 100644 index 0000000..b304d85 Binary files /dev/null and b/Engines/Windows/stockfish/Windows/stockfish_9_x32.exe differ diff --git a/Engines/Windows/stockfish/Windows/stockfish_9_x64_bmi2.exe b/Engines/Windows/stockfish/Windows/stockfish_9_x64_bmi2.exe new file mode 100644 index 0000000..b00b75f Binary files /dev/null and b/Engines/Windows/stockfish/Windows/stockfish_9_x64_bmi2.exe differ diff --git a/Engines/Windows/stockfish/appveyor.yml b/Engines/Windows/stockfish/appveyor.yml index a46a0f3..c711dd6 100644 --- a/Engines/Windows/stockfish/appveyor.yml +++ b/Engines/Windows/stockfish/appveyor.yml @@ -1,5 +1,5 @@ version: 1.0.{build} -clone_depth: 5 +clone_depth: 50 branches: only: @@ -13,10 +13,11 @@ os: Visual Studio 2015 platform: - x86 - x64 - - Any CPU # build Configuration, i.e. Debug, Release, etc. -configuration: Debug +configuration: + - Debug + - Release matrix: # The build fail immediately once one of the job fails @@ -28,18 +29,43 @@ init: - msbuild /version before_build: - - cd src - - echo project (Stockfish) >> CMakeLists.txt - - echo add_executable(stockfish benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp >> CMakeLists.txt - - echo main.cpp material.cpp misc.cpp movegen.cpp movepick.cpp pawns.cpp position.cpp psqt.cpp >> CMakeLists.txt - - echo search.cpp thread.cpp timeman.cpp tt.cpp uci.cpp ucioption.cpp syzygy/tbprobe.cpp) >> CMakeLists.txt - - echo set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src) >> CMakeLists.txt -# - echo target_compile_options(stockfish PUBLIC "/Ox") >> CMakeLists.txt + - ps: | + # Get sources + $src = get-childitem -Path *.cpp -Recurse | select -ExpandProperty FullName + $src = $src -join ' ' + $src = $src.Replace("\", "/") + + # Build CMakeLists.txt + $t = 'cmake_minimum_required(VERSION 3.8)', + 'project(Stockfish)', + 'set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src)', + 'set(source_files', $src, ')', + 'add_executable(stockfish ${source_files})' + + # Write CMakeLists.txt withouth BOM + $MyPath = (Get-Item -Path "." -Verbose).FullName + '\CMakeLists.txt' + $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False + [System.IO.File]::WriteAllLines($MyPath, $t, $Utf8NoBomEncoding) + + # Obtain bench reference from git log + $b = git log HEAD | sls "\b[Bb]ench[ :]+[0-9]{7}" | select -first 1 + $bench = $b -match '\D+(\d+)' | % { $matches[1] } + Write-Host "Reference bench:" $bench + $g = "Visual Studio 14 2015" + If (${env:PLATFORM} -eq 'x64') { $g = $g + ' Win64' } + cmake -G "${g}" . + Write-Host "Generated files for: " $g build_script: - - cmake -G "Visual Studio 14 2015 Win64" . - - cmake --build . + - cmake --build . --config %CONFIGURATION% -- /verbosity:minimal before_test: - - cd Debug - - stockfish.exe bench > null + - cd src/%CONFIGURATION% + - ps: | + # Verify bench number + ./stockfish bench 2> out.txt 1> null + $s = (gc "./out.txt" | out-string) + $r = ($s -match 'Nodes searched \D+(\d+)' | % { $matches[1] }) + Write-Host "Engine bench:" $r + Write-Host "Reference bench:" $bench + If ($r -ne $bench) { exit 1 } diff --git a/Engines/Windows/stockfish/src/Makefile b/Engines/Windows/stockfish/src/Makefile index cdedd25..72afcc3 100644 --- a/Engines/Windows/stockfish/src/Makefile +++ b/Engines/Windows/stockfish/src/Makefile @@ -1,7 +1,7 @@ # Stockfish, a UCI chess playing engine derived from Glaurung 2.1 # Copyright (C) 2004-2008 Tord Romstad (Glaurung author) # Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad -# Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad +# Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad # # Stockfish is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -50,7 +50,9 @@ OBJS = benchmark.o bitbase.o bitboard.o endgame.o evaluate.o main.o \ # ---------------------------------------------------------------------------- # # debug = yes/no --- -DNDEBUG --- Enable/Disable debug mode -# sanitize = yes/no --- (-fsanitize ) --- enable undefined behavior checks +# sanitize = undefined/thread/no (-fsanitize ) +# --- ( undefined ) --- enable undefined behavior checks +# --- ( thread ) --- enable threading error checks # optimize = yes/no --- (-O3/-fast etc.) --- Enable/Disable optimizations # arch = (name) --- (-arch) --- Target architecture # bits = 64/32 --- -DIS_64BIT --- 64-/32-bit operating system @@ -139,7 +141,7 @@ endif ### 3.1 Selecting compiler (default = gcc) -CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -fno-rtti -std=c++11 $(EXTRACXXFLAGS) +CXXFLAGS += -Wall -Wcast-qual -fno-exceptions -std=c++11 $(EXTRACXXFLAGS) DEPENDFLAGS += -std=c++11 LDFLAGS += $(EXTRALDFLAGS) @@ -155,9 +157,11 @@ ifeq ($(COMP),gcc) ifeq ($(ARCH),armv7) ifeq ($(OS),Android) CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) endif else CXXFLAGS += -m$(bits) + LDFLAGS += -m$(bits) endif ifneq ($(KERNEL),Darwin) @@ -200,6 +204,11 @@ ifeq ($(COMP),clang) comp=clang CXX=clang++ CXXFLAGS += -pedantic -Wextra -Wshadow +ifneq ($(KERNEL),Darwin) +ifneq ($(KERNEL),OpenBSD) + LDFLAGS += -latomic +endif +endif ifeq ($(ARCH),armv7) ifeq ($(OS),Android) @@ -210,23 +219,19 @@ ifeq ($(COMP),clang) CXXFLAGS += -m$(bits) LDFLAGS += -m$(bits) endif - - ifeq ($(KERNEL),Darwin) - CXXFLAGS += -stdlib=libc++ - DEPENDFLAGS += -stdlib=libc++ - endif endif ifeq ($(comp),icc) - profile_prepare = icc-profile-prepare profile_make = icc-profile-make profile_use = icc-profile-use - profile_clean = icc-profile-clean else - profile_prepare = gcc-profile-prepare +ifeq ($(comp),clang) + profile_make = clang-profile-make + profile_use = clang-profile-use +else profile_make = gcc-profile-make profile_use = gcc-profile-use - profile_clean = gcc-profile-clean +endif endif ifeq ($(KERNEL),Darwin) @@ -263,8 +268,9 @@ else endif ### 3.2.2 Debugging with undefined behavior sanitizers -ifeq ($(sanitize),yes) - CXXFLAGS += -g3 -fsanitize=undefined +ifneq ($(sanitize),no) + CXXFLAGS += -g3 -fsanitize=$(sanitize) -fuse-ld=gold + LDFLAGS += -fsanitize=$(sanitize) -fuse-ld=gold endif ### 3.3 Optimization @@ -296,10 +302,8 @@ ifeq ($(optimize),yes) ifeq ($(comp),clang) ifeq ($(KERNEL),Darwin) - ifeq ($(pext),no) CXXFLAGS += -flto LDFLAGS += $(CXXFLAGS) - endif ifeq ($(arch),i386) CXXFLAGS += -mdynamic-no-pic endif @@ -423,30 +427,27 @@ help: @echo "" -.PHONY: build profile-build -build: - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity +.PHONY: help build profile-build strip install clean objclean profileclean help \ + config-sanity icc-profile-use icc-profile-make gcc-profile-use gcc-profile-make \ + clang-profile-use clang-profile-make + +build: config-sanity $(MAKE) ARCH=$(ARCH) COMP=$(COMP) all -profile-build: - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) config-sanity - @echo "" - @echo "Step 0/4. Preparing for profile build." - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_prepare) +profile-build: config-sanity objclean profileclean @echo "" - @echo "Step 1/4. Building executable for benchmark ..." - @touch *.cpp *.h syzygy/*.cpp syzygy/*.h + @echo "Step 1/4. Building instrumented executable ..." $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_make) @echo "" @echo "Step 2/4. Running benchmark for pgo-build ..." $(PGOBENCH) > /dev/null @echo "" - @echo "Step 3/4. Building final executable ..." - @touch *.cpp *.h syzygy/*.cpp syzygy/*.h + @echo "Step 3/4. Building optimized executable ..." + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) objclean $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_use) @echo "" @echo "Step 4/4. Deleting profile data ..." - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) $(profile_clean) + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) profileclean strip: strip $(EXE) @@ -456,8 +457,19 @@ install: -cp $(EXE) $(BINDIR) -strip $(BINDIR)/$(EXE) -clean: - $(RM) $(EXE) $(EXE).exe *.o .depend *~ core bench.txt *.gcda ./syzygy/*.o ./syzygy/*.gcda +#clean all +clean: objclean profileclean + @rm -f .depend *~ core + +# clean binaries and objects +objclean: + @rm -f $(EXE) $(EXE).exe *.o ./syzygy/*.o + +# clean auxiliary profiling files +profileclean: + @rm -rf profdir + @rm -f bench.txt *.gcda ./syzygy/*.gcda *.gcno ./syzygy/*.gcno + @rm -f stockfish.profdata *.profraw default: help @@ -472,6 +484,7 @@ config-sanity: @echo "" @echo "Config:" @echo "debug: '$(debug)'" + @echo "sanitize: '$(sanitize)'" @echo "optimize: '$(optimize)'" @echo "arch: '$(arch)'" @echo "bits: '$(bits)'" @@ -490,6 +503,7 @@ config-sanity: @echo "Testing config sanity. If this fails, try 'make help' ..." @echo "" @test "$(debug)" = "yes" || test "$(debug)" = "no" + @test "$(sanitize)" = "undefined" || test "$(sanitize)" = "thread" || test "$(sanitize)" = "no" @test "$(optimize)" = "yes" || test "$(optimize)" = "no" @test "$(arch)" = "any" || test "$(arch)" = "x86_64" || test "$(arch)" = "i386" || \ test "$(arch)" = "ppc64" || test "$(arch)" = "ppc" || test "$(arch)" = "armv7" @@ -503,8 +517,18 @@ config-sanity: $(EXE): $(OBJS) $(CXX) -o $@ $(OBJS) $(LDFLAGS) -gcc-profile-prepare: - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) gcc-profile-clean +clang-profile-make: + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-generate ' \ + EXTRALDFLAGS=' -fprofile-instr-generate' \ + all + +clang-profile-use: + llvm-profdata merge -output=stockfish.profdata *.profraw + $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ + EXTRACXXFLAGS='-fprofile-instr-use=stockfish.profdata' \ + EXTRALDFLAGS='-fprofile-use ' \ + all gcc-profile-make: $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ @@ -518,14 +542,8 @@ gcc-profile-use: EXTRALDFLAGS='-lgcov' \ all -gcc-profile-clean: - @rm -rf *.gcda *.gcno syzygy/*.gcda syzygy/*.gcno bench.txt - -icc-profile-prepare: - $(MAKE) ARCH=$(ARCH) COMP=$(COMP) icc-profile-clean - @mkdir profdir - icc-profile-make: + @mkdir -p profdir $(MAKE) ARCH=$(ARCH) COMP=$(COMP) \ EXTRACXXFLAGS='-prof-gen=srcpos -prof_dir ./profdir' \ all @@ -535,9 +553,6 @@ icc-profile-use: EXTRACXXFLAGS='-prof_use -prof_dir ./profdir' \ all -icc-profile-clean: - @rm -rf profdir bench.txt - .depend: -@$(CXX) $(DEPENDFLAGS) -MM $(OBJS:.o=.cpp) > $@ 2> /dev/null diff --git a/Engines/Windows/stockfish/src/benchmark.cpp b/Engines/Windows/stockfish/src/benchmark.cpp index f2d1f06..1c69dca 100644 --- a/Engines/Windows/stockfish/src/benchmark.cpp +++ b/Engines/Windows/stockfish/src/benchmark.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,23 +23,20 @@ #include #include -#include "misc.h" #include "position.h" -#include "search.h" -#include "thread.h" -#include "uci.h" using namespace std; namespace { const vector Defaults = { + "setoption name UCI_Chess960 value false", "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "r3k2r/p1ppqpb1/bn2pnp1/3PN3/1p2P3/2N2Q1p/PPPBBPPP/R3K2R w KQkq - 0 10", "8/2p5/3p4/KP5r/1R3p1k/8/4P1P1/8 w - - 0 11", "4rrk1/pp1n3p/3q2pQ/2p1pb2/2PP4/2P3N1/P2B2PP/4RRK1 b - - 7 19", - "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14", - "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14", + "rq3rk1/ppp2ppp/1bnpb3/3N2B1/3NP3/7P/PPPQ1PP1/2KR3R w - - 7 14 moves d4e6", + "r1bq1r1k/1pp1n1pp/1p1p4/4p2Q/4Pp2/1BNP4/PPP2PPP/3R1RK1 w - - 2 14 moves g2g4", "r3r1k1/2p2ppp/p1p1bn2/8/1q2P3/2NPQN2/PPP3PP/R4RK1 b - - 2 15", "r1bbk1nr/pp3p1p/2n5/1N4p1/2Np1B2/8/PPP2PPP/2KR1B1R w kq - 0 13", "r1bq1rk1/ppp1nppp/4n3/3p3Q/3P4/1BP1B3/PP1N2PP/R4RK1 w - - 1 16", @@ -52,7 +49,7 @@ const vector Defaults = { "3q2k1/pb3p1p/4pbp1/2r5/PpN2N2/1P2P2P/5PP1/Q2R2K1 b - - 4 26", "6k1/6p1/6Pp/ppp5/3pn2P/1P3K2/1PP2P2/3N4 b - - 0 1", "3b4/5kp1/1p1p1p1p/pP1PpP1P/P1P1P3/3KN3/8/8 w - - 0 1", - "2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1", + "2K5/p7/7P/5pR1/8/5k2/r7/8 w - - 0 1 moves g5g6 f3e3 g6g5 e3f3", "8/6pk/1p6/8/PP3p1p/5P2/4KP1q/3Q4 w - - 0 1", "7k/3p2pp/4q3/8/4Q3/5Kp1/P6b/8 w - - 0 1", "8/2p5/8/2kPKp1p/2p4P/2P5/3P4/8 w - - 0 1", @@ -79,28 +76,35 @@ const vector Defaults = { "8/R7/2q5/8/6k1/8/1P5p/K6R w - - 0 124", // Draw // Mate and stalemate positions + "6k1/3b3r/1p1p4/p1n2p2/1PPNpP1q/P3Q1p1/1R1RB1P1/5K2 b - - 0 1", + "r2r1n2/pp2bk2/2p1p2p/3q4/3PN1QP/2P3R1/P4PP1/5RK1 w - - 0 1", "8/8/8/8/8/6k1/6p1/6K1 w - -", - "5k2/5P2/5K2/8/8/8/8/8 b - -", - "8/8/8/8/8/4k3/4p3/4K3 w - -", - "8/8/8/8/8/5K2/8/3Q1k2 b - -", - "7k/7P/6K1/8/3B4/8/8/8 b - -" + "7k/7P/6K1/8/3B4/8/8/8 b - -", + + // Chess 960 + "setoption name UCI_Chess960 value true", + "bbqnnrkr/pppppppp/8/8/8/8/PPPPPPPP/BBQNNRKR w KQkq - 0 1 moves g2g3 d7d5 d2d4 c8h3 c1g5 e8d6 g5e7 f7f6", + "setoption name UCI_Chess960 value false" }; } // namespace -/// benchmark() runs a simple benchmark by letting Stockfish analyze a set -/// of positions for a given limit each. There are five parameters: the -/// transposition table size, the number of search threads that should -/// be used, the limit value spent for each position (optional, default is -/// depth 13), an optional file name where to look for positions in FEN -/// format (defaults are the positions defined above) and the type of the -/// limit value: depth (default), time in millisecs or number of nodes. +/// setup_bench() builds a list of UCI commands to be run by bench. There +/// are five parameters: TT size in MB, number of search threads that +/// should be used, the limit value spent for each position, a file name +/// where to look for positions in FEN format and the type of the limit: +/// depth, perft, nodes and movetime (in millisecs). +/// +/// bench -> search default positions up to depth 13 +/// bench 64 1 15 -> search default positions up to depth 15 (TT = 64MB) +/// bench 64 4 5000 current movetime -> search current position with 4 threads for 5 sec +/// bench 64 1 100000 default nodes -> search default positions for 100K nodes each +/// bench 16 1 5 default perft -> run a perft 5 on default positions -void benchmark(const Position& current, istream& is) { +vector setup_bench(const Position& current, istream& is) { - string token; - vector fens; - Search::LimitsType limits; + vector fens, list; + string go, token; // Assign default values to missing arguments string ttSize = (is >> token) ? token : "16"; @@ -109,21 +113,7 @@ void benchmark(const Position& current, istream& is) { string fenFile = (is >> token) ? token : "default"; string limitType = (is >> token) ? token : "depth"; - Options["Hash"] = ttSize; - Options["Threads"] = threads; - Search::clear(); - - if (limitType == "time") - limits.movetime = stoi(limit); // movetime is in millisecs - - else if (limitType == "nodes") - limits.nodes = stoi(limit); - - else if (limitType == "mate") - limits.mate = stoi(limit); - - else - limits.depth = stoi(limit); + go = "go " + limitType + " " + limit; if (fenFile == "default") fens = Defaults; @@ -139,7 +129,7 @@ void benchmark(const Position& current, istream& is) { if (!file.is_open()) { cerr << "Unable to open file " << fenFile << endl; - return; + exit(EXIT_FAILURE); } while (getline(file, fen)) @@ -149,35 +139,18 @@ void benchmark(const Position& current, istream& is) { file.close(); } - uint64_t nodes = 0; - TimePoint elapsed = now(); - Position pos; - - for (size_t i = 0; i < fens.size(); ++i) - { - StateListPtr states(new std::deque(1)); - pos.set(fens[i], Options["UCI_Chess960"], &states->back(), Threads.main()); - - cerr << "\nPosition: " << i + 1 << '/' << fens.size() << endl; - - if (limitType == "perft") - nodes += Search::perft(pos, limits.depth * ONE_PLY); + list.emplace_back("ucinewgame"); + list.emplace_back("setoption name Threads value " + threads); + list.emplace_back("setoption name Hash value " + ttSize); + for (const string& fen : fens) + if (fen.find("setoption") != string::npos) + list.emplace_back(fen); else { - limits.startTime = now(); - Threads.start_thinking(pos, states, limits); - Threads.main()->wait_for_search_finished(); - nodes += Threads.nodes_searched(); + list.emplace_back("position fen " + fen); + list.emplace_back(go); } - } - - elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' - - dbg_print(); // Just before exiting - cerr << "\n===========================" - << "\nTotal time (ms) : " << elapsed - << "\nNodes searched : " << nodes - << "\nNodes/second : " << 1000 * nodes / elapsed << endl; + return list; } diff --git a/Engines/Windows/stockfish/src/bitbase.cpp b/Engines/Windows/stockfish/src/bitbase.cpp index 4170952..3c9bf6e 100644 --- a/Engines/Windows/stockfish/src/bitbase.cpp +++ b/Engines/Windows/stockfish/src/bitbase.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -111,13 +111,13 @@ namespace { ksq[WHITE] = Square((idx >> 0) & 0x3F); ksq[BLACK] = Square((idx >> 6) & 0x3F); us = Color ((idx >> 12) & 0x01); - psq = make_square(File((idx >> 13) & 0x3), RANK_7 - Rank((idx >> 15) & 0x7)); + psq = make_square(File((idx >> 13) & 0x3), Rank(RANK_7 - ((idx >> 15) & 0x7))); // Check if two pieces are on the same square or if a king can be captured if ( distance(ksq[WHITE], ksq[BLACK]) <= 1 || ksq[WHITE] == psq || ksq[BLACK] == psq - || (us == WHITE && (StepAttacksBB[PAWN][psq] & ksq[BLACK]))) + || (us == WHITE && (PawnAttacks[WHITE][psq] & ksq[BLACK]))) result = INVALID; // Immediate win if a pawn can be promoted without getting captured @@ -125,13 +125,13 @@ namespace { && rank_of(psq) == RANK_7 && ksq[us] != psq + NORTH && ( distance(ksq[~us], psq + NORTH) > 1 - || (StepAttacksBB[KING][ksq[us]] & (psq + NORTH)))) + || (PseudoAttacks[KING][ksq[us]] & (psq + NORTH)))) result = WIN; // Immediate draw if it is a stalemate or a king captures undefended pawn else if ( us == BLACK - && ( !(StepAttacksBB[KING][ksq[us]] & ~(StepAttacksBB[KING][ksq[~us]] | StepAttacksBB[PAWN][psq])) - || (StepAttacksBB[KING][ksq[us]] & psq & ~StepAttacksBB[KING][ksq[~us]]))) + && ( !(PseudoAttacks[KING][ksq[us]] & ~(PseudoAttacks[KING][ksq[~us]] | PawnAttacks[~us][psq])) + || (PseudoAttacks[KING][ksq[us]] & psq & ~PseudoAttacks[KING][ksq[~us]]))) result = DRAW; // Position will be classified later @@ -157,7 +157,7 @@ namespace { const Result Bad = (Us == WHITE ? DRAW : WIN); Result r = INVALID; - Bitboard b = StepAttacksBB[KING][ksq[Us]]; + Bitboard b = PseudoAttacks[KING][ksq[Us]]; while (b) r |= Us == WHITE ? db[index(Them, ksq[Them] , pop_lsb(&b), psq)] diff --git a/Engines/Windows/stockfish/src/bitboard.cpp b/Engines/Windows/stockfish/src/bitboard.cpp index e329ab5..ba00c78 100644 --- a/Engines/Windows/stockfish/src/bitboard.cpp +++ b/Engines/Windows/stockfish/src/bitboard.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,29 +26,22 @@ uint8_t PopCnt16[1 << 16]; int SquareDistance[SQUARE_NB][SQUARE_NB]; -Bitboard RookMasks [SQUARE_NB]; -Bitboard RookMagics [SQUARE_NB]; -Bitboard* RookAttacks[SQUARE_NB]; -unsigned RookShifts [SQUARE_NB]; - -Bitboard BishopMasks [SQUARE_NB]; -Bitboard BishopMagics [SQUARE_NB]; -Bitboard* BishopAttacks[SQUARE_NB]; -unsigned BishopShifts [SQUARE_NB]; - Bitboard SquareBB[SQUARE_NB]; Bitboard FileBB[FILE_NB]; Bitboard RankBB[RANK_NB]; Bitboard AdjacentFilesBB[FILE_NB]; -Bitboard InFrontBB[COLOR_NB][RANK_NB]; -Bitboard StepAttacksBB[PIECE_NB][SQUARE_NB]; +Bitboard ForwardRanksBB[COLOR_NB][RANK_NB]; Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; Bitboard LineBB[SQUARE_NB][SQUARE_NB]; Bitboard DistanceRingBB[SQUARE_NB][8]; -Bitboard ForwardBB[COLOR_NB][SQUARE_NB]; +Bitboard ForwardFileBB[COLOR_NB][SQUARE_NB]; Bitboard PassedPawnMask[COLOR_NB][SQUARE_NB]; Bitboard PawnAttackSpan[COLOR_NB][SQUARE_NB]; Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; +Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; + +Magic RookMagics[SQUARE_NB]; +Magic BishopMagics[SQUARE_NB]; namespace { @@ -61,10 +54,7 @@ namespace { Bitboard RookTable[0x19000]; // To store rook attacks Bitboard BishopTable[0x1480]; // To store bishop attacks - typedef unsigned (Fn)(Square, Bitboard); - - void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[], - Bitboard masks[], unsigned shifts[], Square deltas[], Fn index); + void init_magics(Bitboard table[], Magic magics[], Direction directions[]); // bsf_index() returns the index into BSFTable[] to look up the bitscan. Uses // Matt Taylor's folding for 32 bit case, extended to 64 bit by Kim Walisch. @@ -173,14 +163,14 @@ void Bitboards::init() { AdjacentFilesBB[f] = (f > FILE_A ? FileBB[f - 1] : 0) | (f < FILE_H ? FileBB[f + 1] : 0); for (Rank r = RANK_1; r < RANK_8; ++r) - InFrontBB[WHITE][r] = ~(InFrontBB[BLACK][r + 1] = InFrontBB[BLACK][r] | RankBB[r]); + ForwardRanksBB[WHITE][r] = ~(ForwardRanksBB[BLACK][r + 1] = ForwardRanksBB[BLACK][r] | RankBB[r]); for (Color c = WHITE; c <= BLACK; ++c) for (Square s = SQ_A1; s <= SQ_H8; ++s) { - ForwardBB[c][s] = InFrontBB[c][rank_of(s)] & FileBB[file_of(s)]; - PawnAttackSpan[c][s] = InFrontBB[c][rank_of(s)] & AdjacentFilesBB[file_of(s)]; - PassedPawnMask[c][s] = ForwardBB[c][s] | PawnAttackSpan[c][s]; + ForwardFileBB [c][s] = ForwardRanksBB[c][rank_of(s)] & FileBB[file_of(s)]; + PawnAttackSpan[c][s] = ForwardRanksBB[c][rank_of(s)] & AdjacentFilesBB[file_of(s)]; + PassedPawnMask[c][s] = ForwardFileBB [c][s] | PawnAttackSpan[c][s]; } for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) @@ -191,39 +181,43 @@ void Bitboards::init() { DistanceRingBB[s1][SquareDistance[s1][s2] - 1] |= s2; } - int steps[][9] = { {}, { 7, 9 }, { 17, 15, 10, 6, -6, -10, -15, -17 }, - {}, {}, {}, { 9, 7, -7, -9, 8, 1, -1, -8 } }; + int steps[][5] = { {}, { 7, 9 }, { 6, 10, 15, 17 }, {}, {}, {}, { 1, 7, 8, 9 } }; for (Color c = WHITE; c <= BLACK; ++c) - for (PieceType pt = PAWN; pt <= KING; ++pt) + for (PieceType pt : { PAWN, KNIGHT, KING }) for (Square s = SQ_A1; s <= SQ_H8; ++s) for (int i = 0; steps[pt][i]; ++i) { - Square to = s + Square(c == WHITE ? steps[pt][i] : -steps[pt][i]); + Square to = s + Direction(c == WHITE ? steps[pt][i] : -steps[pt][i]); if (is_ok(to) && distance(s, to) < 3) - StepAttacksBB[make_piece(c, pt)][s] |= to; + { + if (pt == PAWN) + PawnAttacks[c][s] |= to; + else + PseudoAttacks[pt][s] |= to; + } } - Square RookDeltas[] = { NORTH, EAST, SOUTH, WEST }; - Square BishopDeltas[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; + Direction RookDirections[] = { NORTH, EAST, SOUTH, WEST }; + Direction BishopDirections[] = { NORTH_EAST, SOUTH_EAST, SOUTH_WEST, NORTH_WEST }; - init_magics(RookTable, RookAttacks, RookMagics, RookMasks, RookShifts, RookDeltas, magic_index); - init_magics(BishopTable, BishopAttacks, BishopMagics, BishopMasks, BishopShifts, BishopDeltas, magic_index); + init_magics(RookTable, RookMagics, RookDirections); + init_magics(BishopTable, BishopMagics, BishopDirections); for (Square s1 = SQ_A1; s1 <= SQ_H8; ++s1) { PseudoAttacks[QUEEN][s1] = PseudoAttacks[BISHOP][s1] = attacks_bb(s1, 0); PseudoAttacks[QUEEN][s1] |= PseudoAttacks[ ROOK][s1] = attacks_bb< ROOK>(s1, 0); - for (Piece pc = W_BISHOP; pc <= W_ROOK; ++pc) + for (PieceType pt : { BISHOP, ROOK }) for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) { - if (!(PseudoAttacks[pc][s1] & s2)) + if (!(PseudoAttacks[pt][s1] & s2)) continue; - LineBB[s1][s2] = (attacks_bb(pc, s1, 0) & attacks_bb(pc, s2, 0)) | s1 | s2; - BetweenBB[s1][s2] = attacks_bb(pc, s1, SquareBB[s2]) & attacks_bb(pc, s2, SquareBB[s1]); + LineBB[s1][s2] = (attacks_bb(pt, s1, 0) & attacks_bb(pt, s2, 0)) | s1 | s2; + BetweenBB[s1][s2] = attacks_bb(pt, s1, SquareBB[s2]) & attacks_bb(pt, s2, SquareBB[s1]); } } } @@ -231,14 +225,14 @@ void Bitboards::init() { namespace { - Bitboard sliding_attack(Square deltas[], Square sq, Bitboard occupied) { + Bitboard sliding_attack(Direction directions[], Square sq, Bitboard occupied) { Bitboard attack = 0; for (int i = 0; i < 4; ++i) - for (Square s = sq + deltas[i]; - is_ok(s) && distance(s, s - deltas[i]) == 1; - s += deltas[i]) + for (Square s = sq + directions[i]; + is_ok(s) && distance(s, s - directions[i]) == 1; + s += directions[i]) { attack |= s; @@ -255,17 +249,14 @@ namespace { // chessprogramming.wikispaces.com/Magic+Bitboards. In particular, here we // use the so called "fancy" approach. - void init_magics(Bitboard table[], Bitboard* attacks[], Bitboard magics[], - Bitboard masks[], unsigned shifts[], Square deltas[], Fn index) { + void init_magics(Bitboard table[], Magic magics[], Direction directions[]) { + // Optimal PRNG seeds to pick the correct magics in the shortest time int seeds[][RANK_NB] = { { 8977, 44560, 54343, 38998, 5731, 95205, 104912, 17020 }, { 728, 10316, 55013, 32803, 12281, 15100, 16645, 255 } }; Bitboard occupancy[4096], reference[4096], edges, b; - int age[4096] = {0}, current = 0, i, size; - - // attacks[s] is a pointer to the beginning of the attacks table for square 's' - attacks[SQ_A1] = table; + int epoch[4096] = {}, cnt = 0, size = 0; for (Square s = SQ_A1; s <= SQ_H8; ++s) { @@ -277,28 +268,28 @@ namespace { // all the attacks for each possible subset of the mask and so is 2 power // the number of 1s of the mask. Hence we deduce the size of the shift to // apply to the 64 or 32 bits word to get the index. - masks[s] = sliding_attack(deltas, s, 0) & ~edges; - shifts[s] = (Is64Bit ? 64 : 32) - popcount(masks[s]); + Magic& m = magics[s]; + m.mask = sliding_attack(directions, s, 0) & ~edges; + m.shift = (Is64Bit ? 64 : 32) - popcount(m.mask); + + // Set the offset for the attacks table of the square. We have individual + // table sizes for each square with "Fancy Magic Bitboards". + m.attacks = s == SQ_A1 ? table : magics[s - 1].attacks + size; // Use Carry-Rippler trick to enumerate all subsets of masks[s] and // store the corresponding sliding attack bitboard in reference[]. b = size = 0; do { occupancy[size] = b; - reference[size] = sliding_attack(deltas, s, b); + reference[size] = sliding_attack(directions, s, b); if (HasPext) - attacks[s][pext(b, masks[s])] = reference[size]; + m.attacks[pext(b, m.mask)] = reference[size]; size++; - b = (b - masks[s]) & masks[s]; + b = (b - m.mask) & m.mask; } while (b); - // Set the offset for the table of the next square. We have individual - // table sizes for each square with "Fancy Magic Bitboards". - if (s < SQ_H8) - attacks[s + 1] = attacks[s] + size; - if (HasPext) continue; @@ -306,28 +297,30 @@ namespace { // Find a magic for square 's' picking up an (almost) random number // until we find the one that passes the verification test. - do { - do - magics[s] = rng.sparse_rand(); - while (popcount((magics[s] * masks[s]) >> 56) < 6); + for (int i = 0; i < size; ) + { + for (m.magic = 0; popcount((m.magic * m.mask) >> 56) < 6; ) + m.magic = rng.sparse_rand(); // A good magic must map every possible occupancy to an index that // looks up the correct sliding attack in the attacks[s] database. // Note that we build up the database for square 's' as a side - // effect of verifying the magic. - for (++current, i = 0; i < size; ++i) + // effect of verifying the magic. Keep track of the attempt count + // and save it in epoch[], little speed-up trick to avoid resetting + // m.attacks[] after every failed attempt. + for (++cnt, i = 0; i < size; ++i) { - unsigned idx = index(s, occupancy[i]); + unsigned idx = m.index(occupancy[i]); - if (age[idx] < current) + if (epoch[idx] < cnt) { - age[idx] = current; - attacks[s][idx] = reference[i]; + epoch[idx] = cnt; + m.attacks[idx] = reference[i]; } - else if (attacks[s][idx] != reference[i]) + else if (m.attacks[idx] != reference[i]) break; } - } while (i < size); + } } } } diff --git a/Engines/Windows/stockfish/src/bitboard.h b/Engines/Windows/stockfish/src/bitboard.h index 715f6c4..c9f7242 100644 --- a/Engines/Windows/stockfish/src/bitboard.h +++ b/Engines/Windows/stockfish/src/bitboard.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,6 +39,7 @@ const std::string pretty(Bitboard b); } +const Bitboard AllSquares = ~Bitboard(0); const Bitboard DarkSquares = 0xAA55AA55AA55AA55ULL; const Bitboard FileABB = 0x0101010101010101ULL; @@ -65,15 +66,41 @@ extern Bitboard SquareBB[SQUARE_NB]; extern Bitboard FileBB[FILE_NB]; extern Bitboard RankBB[RANK_NB]; extern Bitboard AdjacentFilesBB[FILE_NB]; -extern Bitboard InFrontBB[COLOR_NB][RANK_NB]; -extern Bitboard StepAttacksBB[PIECE_NB][SQUARE_NB]; +extern Bitboard ForwardRanksBB[COLOR_NB][RANK_NB]; extern Bitboard BetweenBB[SQUARE_NB][SQUARE_NB]; extern Bitboard LineBB[SQUARE_NB][SQUARE_NB]; extern Bitboard DistanceRingBB[SQUARE_NB][8]; -extern Bitboard ForwardBB[COLOR_NB][SQUARE_NB]; +extern Bitboard ForwardFileBB[COLOR_NB][SQUARE_NB]; extern Bitboard PassedPawnMask[COLOR_NB][SQUARE_NB]; extern Bitboard PawnAttackSpan[COLOR_NB][SQUARE_NB]; extern Bitboard PseudoAttacks[PIECE_TYPE_NB][SQUARE_NB]; +extern Bitboard PawnAttacks[COLOR_NB][SQUARE_NB]; + + +/// Magic holds all magic bitboards relevant data for a single square +struct Magic { + Bitboard mask; + Bitboard magic; + Bitboard* attacks; + unsigned shift; + + // Compute the attack's index using the 'magic bitboards' approach + unsigned index(Bitboard occupied) const { + + if (HasPext) + return unsigned(pext(occupied, mask)); + + if (Is64Bit) + return unsigned(((occupied & mask) * magic) >> shift); + + unsigned lo = unsigned(occupied) & unsigned(mask); + unsigned hi = unsigned(occupied >> 32) & unsigned(mask >> 32); + return (lo * unsigned(magic) ^ hi * unsigned(magic >> 32)) >> shift; + } +}; + +extern Magic RookMagics[SQUARE_NB]; +extern Magic BishopMagics[SQUARE_NB]; /// Overloads of bitwise operators between a Bitboard and a Square for testing @@ -99,11 +126,10 @@ inline Bitboard& operator^=(Bitboard& b, Square s) { return b ^= SquareBB[s]; } -inline bool more_than_one(Bitboard b) { +constexpr bool more_than_one(Bitboard b) { return b & (b - 1); } - /// rank_bb() and file_bb() return a bitboard representing all the squares on /// the given file or rank. @@ -126,8 +152,8 @@ inline Bitboard file_bb(Square s) { /// shift() moves a bitboard one step along direction D. Mainly for pawns -template -inline Bitboard shift(Bitboard b) { +template +constexpr Bitboard shift(Bitboard b) { return D == NORTH ? b << 8 : D == SOUTH ? b >> 8 : D == NORTH_EAST ? (b & ~FileHBB) << 9 : D == SOUTH_EAST ? (b & ~FileHBB) >> 7 : D == NORTH_WEST ? (b & ~FileABB) << 7 : D == SOUTH_WEST ? (b & ~FileABB) >> 9 @@ -153,28 +179,28 @@ inline Bitboard between_bb(Square s1, Square s2) { } -/// in_front_bb() returns a bitboard representing all the squares on all the ranks +/// forward_ranks_bb() returns a bitboard representing all the squares on all the ranks /// in front of the given one, from the point of view of the given color. For -/// instance, in_front_bb(BLACK, RANK_3) will return the squares on ranks 1 and 2. +/// instance, forward_ranks_bb(BLACK, SQ_D3) will return the 16 squares on ranks 1 and 2. -inline Bitboard in_front_bb(Color c, Rank r) { - return InFrontBB[c][r]; +inline Bitboard forward_ranks_bb(Color c, Square s) { + return ForwardRanksBB[c][rank_of(s)]; } -/// forward_bb() returns a bitboard representing all the squares along the line +/// forward_file_bb() returns a bitboard representing all the squares along the line /// in front of the given one, from the point of view of the given color: -/// ForwardBB[c][s] = in_front_bb(c, s) & file_bb(s) +/// ForwardFileBB[c][s] = forward_ranks_bb(c, s) & file_bb(s) -inline Bitboard forward_bb(Color c, Square s) { - return ForwardBB[c][s]; +inline Bitboard forward_file_bb(Color c, Square s) { + return ForwardFileBB[c][s]; } /// pawn_attack_span() returns a bitboard representing all the squares that can be /// attacked by a pawn of the given color when it moves along its file, starting /// from the given square: -/// PawnAttackSpan[c][s] = in_front_bb(c, s) & adjacent_files_bb(s); +/// PawnAttackSpan[c][s] = forward_ranks_bb(c, s) & adjacent_files_bb(file_of(s)); inline Bitboard pawn_attack_span(Color c, Square s) { return PawnAttackSpan[c][s]; @@ -183,7 +209,7 @@ inline Bitboard pawn_attack_span(Color c, Square s) { /// passed_pawn_mask() returns a bitboard mask which can be used to test if a /// pawn of the given color and on the given square is a passed pawn: -/// PassedPawnMask[c][s] = pawn_attack_span(c, s) | forward_bb(c, s) +/// PassedPawnMask[c][s] = pawn_attack_span(c, s) | forward_file_bb(c, s) inline Bitboard passed_pawn_mask(Color c, Square s) { return PassedPawnMask[c][s]; @@ -210,50 +236,25 @@ template<> inline int distance(Square x, Square y) { return distance(rank_ /// attacks_bb() returns a bitboard representing all the squares attacked by a -/// piece of type Pt (bishop or rook) placed on 's'. The helper magic_index() -/// looks up the index using the 'magic bitboards' approach. -template -inline unsigned magic_index(Square s, Bitboard occupied) { - - extern Bitboard RookMasks[SQUARE_NB]; - extern Bitboard RookMagics[SQUARE_NB]; - extern unsigned RookShifts[SQUARE_NB]; - extern Bitboard BishopMasks[SQUARE_NB]; - extern Bitboard BishopMagics[SQUARE_NB]; - extern unsigned BishopShifts[SQUARE_NB]; - - Bitboard* const Masks = Pt == ROOK ? RookMasks : BishopMasks; - Bitboard* const Magics = Pt == ROOK ? RookMagics : BishopMagics; - unsigned* const Shifts = Pt == ROOK ? RookShifts : BishopShifts; - - if (HasPext) - return unsigned(pext(occupied, Masks[s])); - - if (Is64Bit) - return unsigned(((occupied & Masks[s]) * Magics[s]) >> Shifts[s]); - - unsigned lo = unsigned(occupied) & unsigned(Masks[s]); - unsigned hi = unsigned(occupied >> 32) & unsigned(Masks[s] >> 32); - return (lo * unsigned(Magics[s]) ^ hi * unsigned(Magics[s] >> 32)) >> Shifts[s]; -} +/// piece of type Pt (bishop or rook) placed on 's'. template inline Bitboard attacks_bb(Square s, Bitboard occupied) { - extern Bitboard* RookAttacks[SQUARE_NB]; - extern Bitboard* BishopAttacks[SQUARE_NB]; - - return (Pt == ROOK ? RookAttacks : BishopAttacks)[s][magic_index(s, occupied)]; + const Magic& m = Pt == ROOK ? RookMagics[s] : BishopMagics[s]; + return m.attacks[m.index(occupied)]; } -inline Bitboard attacks_bb(Piece pc, Square s, Bitboard occupied) { +inline Bitboard attacks_bb(PieceType pt, Square s, Bitboard occupied) { + + assert(pt != PAWN); - switch (type_of(pc)) + switch (pt) { case BISHOP: return attacks_bb(s, occupied); - case ROOK : return attacks_bb(s, occupied); + case ROOK : return attacks_bb< ROOK>(s, occupied); case QUEEN : return attacks_bb(s, occupied) | attacks_bb(s, occupied); - default : return StepAttacksBB[pc][s]; + default : return PseudoAttacks[pt][s]; } } @@ -291,7 +292,7 @@ inline Square lsb(Bitboard b) { inline Square msb(Bitboard b) { assert(b); - return Square(63 - __builtin_clzll(b)); + return Square(63 ^ __builtin_clzll(b)); } #elif defined(_WIN64) && defined(_MSC_VER) diff --git a/Engines/Windows/stockfish/src/endgame.cpp b/Engines/Windows/stockfish/src/endgame.cpp index cbca34b..39db219 100644 --- a/Engines/Windows/stockfish/src/endgame.cpp +++ b/Engines/Windows/stockfish/src/endgame.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -83,26 +83,6 @@ namespace { return sq; } - // Get the material key of Position out of the given endgame key code - // like "KBPKN". The trick here is to first forge an ad-hoc FEN string - // and then let a Position object do the work for us. - Key key(const string& code, Color c) { - - assert(code.length() > 0 && code.length() < 8); - assert(code[0] == 'K'); - - string sides[] = { code.substr(code.find('K', 1)), // Weak - code.substr(0, code.find('K', 1)) }; // Strong - - std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); - - string fen = sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/8/8/" - + sides[1] + char(8 - sides[1].length() + '0') + " w - - 0 10"; - - StateInfo st; - return Position().set(fen, false, &st, nullptr).material_key(); - } - } // namespace @@ -130,13 +110,6 @@ Endgames::Endgames() { } -template -void Endgames::add(const string& code) { - map()[key(code, WHITE)] = std::unique_ptr>(new Endgame(WHITE)); - map()[key(code, BLACK)] = std::unique_ptr>(new Endgame(BLACK)); -} - - /// Mate with KX vs K. This function is used to evaluate positions with /// king and plenty of material vs a lone king. It simply gives the /// attacking side a bonus for driving the defending king towards the edge @@ -162,8 +135,8 @@ Value Endgame::operator()(const Position& pos) const { if ( pos.count(strongSide) || pos.count(strongSide) ||(pos.count(strongSide) && pos.count(strongSide)) - ||(pos.count(strongSide) > 1 && opposite_colors(pos.squares(strongSide)[0], - pos.squares(strongSide)[1]))) + || ( (pos.pieces(strongSide, BISHOP) & ~DarkSquares) + && (pos.pieces(strongSide, BISHOP) & DarkSquares))) result = std::min(result + VALUE_KNOWN_WIN, VALUE_MATE_IN_MAX_PLY - 1); return strongSide == pos.side_to_move() ? result : -result; @@ -551,7 +524,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { Square bsq = pos.square(weakSide); Square psq = pos.square(strongSide); Rank rk = relative_rank(strongSide, psq); - Square push = pawn_push(strongSide); + Direction push = pawn_push(strongSide); // If the pawn is on the 5th rank and the pawn (currently) is on // the same color square as the bishop then there is a chance of @@ -625,7 +598,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // If all pawns are ahead of the king, on a single rook file and // the king is within one file of the pawns, it's a draw. - if ( !(pawns & ~in_front_bb(weakSide, rank_of(ksq))) + if ( !(pawns & ~forward_ranks_bb(weakSide, ksq)) && !((pawns & ~FileABB) && (pawns & ~FileHBB)) && distance(ksq, lsb(pawns)) <= 1) return SCALE_FACTOR_DRAW; @@ -671,17 +644,15 @@ ScaleFactor Endgame::operator()(const Position& pos) const { if (relative_rank(strongSide, pawnSq) <= RANK_5) return SCALE_FACTOR_DRAW; - else - { - Bitboard path = forward_bb(strongSide, pawnSq); - if (path & pos.pieces(weakSide, KING)) - return SCALE_FACTOR_DRAW; + Bitboard path = forward_file_bb(strongSide, pawnSq); - if ( (pos.attacks_from(weakBishopSq) & path) - && distance(weakBishopSq, pawnSq) >= 3) - return SCALE_FACTOR_DRAW; - } + if (path & pos.pieces(weakSide, KING)) + return SCALE_FACTOR_DRAW; + + if ( (pos.attacks_from(weakBishopSq) & path) + && distance(weakBishopSq, pawnSq) >= 3) + return SCALE_FACTOR_DRAW; } return SCALE_FACTOR_NONE; } @@ -809,7 +780,7 @@ ScaleFactor Endgame::operator()(const Position& pos) const { // King needs to get close to promoting pawn to prevent knight from blocking. // Rules for this are very tricky, so just approximate. - if (forward_bb(strongSide, pawnSq) & pos.attacks_from(bishopSq)) + if (forward_file_bb(strongSide, pawnSq) & pos.attacks_from(bishopSq)) return ScaleFactor(distance(weakKingSq, pawnSq)); return SCALE_FACTOR_NONE; diff --git a/Engines/Windows/stockfish/src/endgame.h b/Engines/Windows/stockfish/src/endgame.h index 5f6b4bb..b5255a2 100644 --- a/Engines/Windows/stockfish/src/endgame.h +++ b/Engines/Windows/stockfish/src/endgame.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,12 +31,11 @@ #include "types.h" -/// EndgameType lists all supported endgames +/// EndgameCode lists all supported endgame functions by corresponding codes -enum EndgameType { - - // Evaluation functions +enum EndgameCode { + EVALUATION_FUNCTIONS, KNNK, // KNN vs K KXK, // Generic "mate lone king" eval KBNK, // KBN vs K @@ -47,10 +46,7 @@ enum EndgameType { KQKP, // KQ vs KP KQKR, // KQ vs KR - - // Scaling functions SCALING_FUNCTIONS, - KBPsK, // KB and pawns vs K KQKRPs, // KQ vs KR and pawns KRPKR, // KRP vs KR @@ -68,30 +64,28 @@ enum EndgameType { /// Endgame functions can be of two types depending on whether they return a /// Value or a ScaleFactor. -template using +template using eg_type = typename std::conditional<(E < SCALING_FUNCTIONS), Value, ScaleFactor>::type; -/// Base and derived templates for endgame evaluation and scaling functions +/// Base and derived functors for endgame evaluation and scaling functions template struct EndgameBase { + explicit EndgameBase(Color c) : strongSide(c), weakSide(~c) {} virtual ~EndgameBase() = default; - virtual Color strong_side() const = 0; virtual T operator()(const Position&) const = 0; + + const Color strongSide, weakSide; }; -template> +template> struct Endgame : public EndgameBase { - explicit Endgame(Color c) : strongSide(c), weakSide(~c) {} - Color strong_side() const { return strongSide; } - T operator()(const Position&) const; - -private: - Color strongSide, weakSide; + explicit Endgame(Color c) : EndgameBase(c) {} + T operator()(const Position&) const override; }; @@ -101,16 +95,22 @@ struct Endgame : public EndgameBase { class Endgames { - template using Map = std::map>>; - - template> - void add(const std::string& code); + template using Ptr = std::unique_ptr>; + template using Map = std::map>; template Map& map() { return std::get::value>(maps); } + template, typename P = Ptr> + void add(const std::string& code) { + + StateInfo st; + map()[Position().set(code, WHITE, &st).material_key()] = P(new Endgame(WHITE)); + map()[Position().set(code, BLACK, &st).material_key()] = P(new Endgame(BLACK)); + } + std::pair, Map> maps; public: diff --git a/Engines/Windows/stockfish/src/evaluate.cpp b/Engines/Windows/stockfish/src/evaluate.cpp index 434ebd6..8d9dd6e 100644 --- a/Engines/Windows/stockfish/src/evaluate.cpp +++ b/Engines/Windows/stockfish/src/evaluate.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,10 +31,21 @@ namespace { + const Bitboard Center = (FileDBB | FileEBB) & (Rank4BB | Rank5BB); + const Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; + const Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; + const Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; + + const Bitboard KingFlank[FILE_NB] = { + QueenSide, QueenSide, QueenSide, CenterFiles, CenterFiles, KingSide, KingSide, KingSide + }; + namespace Trace { + enum Tracing {NO_TRACE, TRACE}; + enum Term { // The first 8 entries are for PieceType - MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, TOTAL, TERM_NB + MATERIAL = 8, IMBALANCE, MOBILITY, THREAT, PASSED, SPACE, INITIATIVE, TOTAL, TERM_NB }; double scores[TERM_NB][COLOR_NB][PHASE_NB]; @@ -52,7 +63,7 @@ namespace { std::ostream& operator<<(std::ostream& os, Term t) { - if (t == MATERIAL || t == IMBALANCE || t == Term(PAWN) || t == TOTAL) + if (t == MATERIAL || t == IMBALANCE || t == Term(PAWN) || t == INITIATIVE || t == TOTAL) os << " --- --- | --- --- | "; else os << std::setw(5) << scores[t][WHITE][MG] << " " @@ -69,12 +80,40 @@ namespace { using namespace Trace; - // Struct EvalInfo contains various information computed and collected + // Evaluation class contains various information computed and collected // by the evaluation functions. - struct EvalInfo { + template + class Evaluation { + + public: + Evaluation() = delete; + Evaluation(const Position& p) : pos(p) {} + Evaluation& operator=(const Evaluation&) = delete; + + Value value(); + + private: + // Evaluation helpers (used when calling value()) + template void initialize(); + template Score evaluate_king(); + template Score evaluate_threats(); + int king_distance(Color c, Square s); + template Score evaluate_passed_pawns(); + template Score evaluate_space(); + template Score evaluate_pieces(); + ScaleFactor evaluate_scale_factor(Value eg); + Score evaluate_initiative(Value eg); + + // Data members + const Position& pos; + Material::Entry* me; + Pawns::Entry* pe; + Bitboard mobilityArea[COLOR_NB]; + Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; // attackedBy[color][piece type] is a bitboard representing all squares - // attacked by a given color and piece type (can be also ALL_PIECES). + // attacked by a given color and piece type. Special "piece types" which are + // also calculated are QUEEN_DIAGONAL and ALL_PIECES. Bitboard attackedBy[COLOR_NB][PIECE_TYPE_NB]; // attackedBy2[color] are the squares attacked by 2 pieces of a given color, @@ -84,7 +123,7 @@ namespace { // kingRing[color] is the zone around the king which is considered // by the king safety evaluation. This consists of the squares directly - // adjacent to the king, and the three (or two, for a king on an edge file) + // adjacent to the king, and (only for a king on its first rank) the // squares two ranks in front of the king. For instance, if black's king // is on g8, kingRing[BLACK] is a bitboard containing the squares f8, h8, // f7, g7, h7, f6, g6 and h6. @@ -106,105 +145,93 @@ namespace { // a white knight on g5 and black's king is on g8, this white knight adds 2 // to kingAdjacentZoneAttacksCount[WHITE]. int kingAdjacentZoneAttacksCount[COLOR_NB]; - - Bitboard pinnedPieces[COLOR_NB]; - Material::Entry* me; - Pawns::Entry* pi; }; #define V(v) Value(v) #define S(mg, eg) make_score(mg, eg) - // MobilityBonus[PieceType][attacked] contains bonuses for middle and end - // game, indexed by piece type and number of attacked squares in the MobilityArea. + // MobilityBonus[PieceType-2][attacked] contains bonuses for middle and end game, + // indexed by piece type and number of attacked squares in the mobility area. const Score MobilityBonus[][32] = { - {}, {}, - { S(-75,-76), S(-56,-54), S( -9,-26), S( -2,-10), S( 6, 5), S( 15, 11), // Knights - S( 22, 26), S( 30, 28), S( 36, 29) }, - { S(-48,-58), S(-21,-19), S( 16, -2), S( 26, 12), S( 37, 22), S( 51, 42), // Bishops - S( 54, 54), S( 63, 58), S( 65, 63), S( 71, 70), S( 79, 74), S( 81, 86), - S( 92, 90), S( 97, 94) }, - { S(-56,-78), S(-25,-18), S(-11, 26), S( -5, 55), S( -4, 70), S( -1, 81), // Rooks - S( 8,109), S( 14,120), S( 21,128), S( 23,143), S( 31,154), S( 32,160), - S( 43,165), S( 49,168), S( 59,169) }, - { S(-40,-35), S(-25,-12), S( 2, 7), S( 4, 19), S( 14, 37), S( 24, 55), // Queens - S( 25, 62), S( 40, 76), S( 43, 79), S( 47, 87), S( 54, 94), S( 56,102), - S( 60,111), S( 70,116), S( 72,118), S( 73,122), S( 75,128), S( 77,130), - S( 85,133), S( 94,136), S( 99,140), S(108,157), S(112,158), S(113,161), - S(118,174), S(119,177), S(123,191), S(128,199) } + { S(-75,-76), S(-57,-54), S( -9,-28), S( -2,-10), S( 6, 5), S( 14, 12), // Knights + S( 22, 26), S( 29, 29), S( 36, 29) }, + { S(-48,-59), S(-20,-23), S( 16, -3), S( 26, 13), S( 38, 24), S( 51, 42), // Bishops + S( 55, 54), S( 63, 57), S( 63, 65), S( 68, 73), S( 81, 78), S( 81, 86), + S( 91, 88), S( 98, 97) }, + { S(-58,-76), S(-27,-18), S(-15, 28), S(-10, 55), S( -5, 69), S( -2, 82), // Rooks + S( 9,112), S( 16,118), S( 30,132), S( 29,142), S( 32,155), S( 38,165), + S( 46,166), S( 48,169), S( 58,171) }, + { S(-39,-36), S(-21,-15), S( 3, 8), S( 3, 18), S( 14, 34), S( 22, 54), // Queens + S( 28, 61), S( 41, 73), S( 43, 79), S( 48, 92), S( 56, 94), S( 60,104), + S( 60,113), S( 66,120), S( 67,123), S( 70,126), S( 71,133), S( 73,136), + S( 79,140), S( 88,143), S( 88,148), S( 99,166), S(102,170), S(102,175), + S(106,184), S(109,191), S(113,206), S(116,212) } }; - // Outpost[knight/bishop][supported by pawn] contains bonuses for knights and - // bishops outposts, bigger if outpost piece is supported by a pawn. + // Outpost[knight/bishop][supported by pawn] contains bonuses for minor + // pieces if they can reach an outpost square, bigger if that square is + // supported by a pawn. If the minor piece occupies an outpost square + // then score is doubled. const Score Outpost[][2] = { - { S(43,11), S(65,20) }, // Knights - { S(20, 3), S(29, 8) } // Bishops - }; - - // ReachableOutpost[knight/bishop][supported by pawn] contains bonuses for - // knights and bishops which can reach an outpost square in one move, bigger - // if outpost square is supported by a pawn. - const Score ReachableOutpost[][2] = { - { S(21, 5), S(35, 8) }, // Knights - { S( 8, 0), S(14, 4) } // Bishops + { S(22, 6), S(36,12) }, // Knight + { S( 9, 2), S(15, 5) } // Bishop }; // RookOnFile[semiopen/open] contains bonuses for each rook when there is no // friendly pawn on the rook file. - const Score RookOnFile[2] = { S(20, 7), S(45, 20) }; + const Score RookOnFile[] = { S(20, 7), S(45, 20) }; - // ThreatBySafePawn[PieceType] contains bonuses according to which piece - // type is attacked by a pawn which is protected or is not attacked. - const Score ThreatBySafePawn[PIECE_TYPE_NB] = { - S(0, 0), S(0, 0), S(176, 139), S(131, 127), S(217, 218), S(203, 215) + // ThreatByMinor/ByRook[attacked PieceType] contains bonuses according to + // which piece type attacks which one. Attacks on lesser pieces which are + // pawn-defended are not considered. + const Score ThreatByMinor[PIECE_TYPE_NB] = { + S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72, 107), S(48, 118) }; - // Threat[by minor/by rook][attacked PieceType] contains - // bonuses according to which piece type attacks which one. - // Attacks on lesser pieces which are pawn-defended are not considered. - const Score Threat[][PIECE_TYPE_NB] = { - { S(0, 0), S(0, 33), S(45, 43), S(46, 47), S(72,107), S(48,118) }, // by Minor - { S(0, 0), S(0, 25), S(40, 62), S(40, 59), S( 0, 34), S(35, 48) } // by Rook + const Score ThreatByRook[PIECE_TYPE_NB] = { + S(0, 0), S(0, 25), S(40, 62), S(40, 59), S(0, 34), S(35, 48) }; - // ThreatByKing[on one/on many] contains bonuses for King attacks on + // ThreatByKing[on one/on many] contains bonuses for king attacks on // pawns or pieces which are not pawn-defended. - const Score ThreatByKing[2] = { S(3, 62), S(9, 138) }; + const Score ThreatByKing[] = { S(3, 62), S(9, 138) }; // Passed[mg/eg][Rank] contains midgame and endgame bonuses for passed pawns. // We don't use a Score because we process the two components independently. const Value Passed[][RANK_NB] = { - { V(5), V( 5), V(31), V(73), V(166), V(252) }, - { V(7), V(14), V(38), V(73), V(166), V(252) } + { V(0), V(5), V( 5), V(31), V(73), V(166), V(252) }, + { V(0), V(7), V(14), V(38), V(73), V(166), V(252) } }; // PassedFile[File] contains a bonus according to the file of a passed pawn const Score PassedFile[FILE_NB] = { S( 9, 10), S( 2, 10), S( 1, -8), S(-20,-12), - S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10) + S(-20,-12), S( 1, -8), S( 2, 10), S( 9, 10) }; + // Rank factor applied on some bonus for passed pawn on rank 4 or beyond + const int RankFactor[RANK_NB] = {0, 0, 0, 2, 6, 11, 16}; + + // KingProtector[PieceType-2] contains a bonus according to distance from king + const Score KingProtector[] = { S(-3, -5), S(-4, -3), S(-3, 0), S(-1, 1) }; + // Assorted bonuses and penalties used by evaluation - const Score MinorBehindPawn = S(16, 0); - const Score BishopPawns = S( 8, 12); - const Score RookOnPawn = S( 8, 24); - const Score TrappedRook = S(92, 0); - const Score CloseEnemies = S( 7, 0); - const Score SafeCheck = S(20, 20); - const Score OtherCheck = S(10, 10); - const Score ThreatByHangingPawn = S(71, 61); - const Score LooseEnemies = S( 0, 25); - const Score WeakQueen = S(35, 0); - const Score Hanging = S(48, 27); - const Score ThreatByPawnPush = S(38, 22); - const Score Unstoppable = S( 0, 20); - const Score PawnlessFlank = S(20, 80); - const Score HinderPassedPawn = S( 7, 0); - - // Penalty for a bishop on a1/h1 (a8/h8 for black) which is trapped by - // a friendly pawn on b2/g2 (b7/g7 for black). This can obviously only - // happen in Chess960 games. - const Score TrappedBishopA1H1 = S(50, 50); + const Score MinorBehindPawn = S( 16, 0); + const Score BishopPawns = S( 8, 12); + const Score LongRangedBishop = S( 22, 0); + const Score RookOnPawn = S( 8, 24); + const Score TrappedRook = S( 92, 0); + const Score WeakQueen = S( 50, 10); + const Score CloseEnemies = S( 7, 0); + const Score PawnlessFlank = S( 20, 80); + const Score ThreatBySafePawn = S(192,175); + const Score ThreatByRank = S( 16, 3); + const Score Hanging = S( 48, 27); + const Score WeakUnopposedPawn = S( 5, 25); + const Score ThreatByPawnPush = S( 38, 22); + const Score ThreatByAttackOnQueen = S( 38, 22); + const Score HinderPassedPawn = S( 7, 0); + const Score TrappedBishopA1H1 = S( 50, 50); #undef S #undef V @@ -213,99 +240,117 @@ namespace { const int KingAttackWeights[PIECE_TYPE_NB] = { 0, 0, 78, 56, 45, 11 }; // Penalties for enemy's safe checks - const int QueenContactCheck = 997; - const int QueenCheck = 695; - const int RookCheck = 638; - const int BishopCheck = 538; - const int KnightCheck = 874; + const int QueenSafeCheck = 780; + const int RookSafeCheck = 880; + const int BishopSafeCheck = 435; + const int KnightSafeCheck = 790; + + // Threshold for lazy and space evaluation + const Value LazyThreshold = Value(1500); + const Value SpaceThreshold = Value(12222); + + + // initialize() computes king and pawn attacks, and the king ring bitboard + // for a given color. This is done at the beginning of the evaluation. + template template + void Evaluation::initialize() { - // eval_init() initializes king and attack bitboards for a given color - // adding pawn attacks. To be done at the beginning of the evaluation. + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Down = (Us == WHITE ? SOUTH : NORTH); + const Bitboard LowRanks = (Us == WHITE ? Rank2BB | Rank3BB: Rank7BB | Rank6BB); - template - void eval_init(const Position& pos, EvalInfo& ei) { + // Find our pawns on the first two ranks, and those which are blocked + Bitboard b = pos.pieces(Us, PAWN) & (shift(pos.pieces()) | LowRanks); - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Down = (Us == WHITE ? SOUTH : NORTH); + // Squares occupied by those pawns, by our king, or controlled by enemy pawns + // are excluded from the mobility area. + mobilityArea[Us] = ~(b | pos.square(Us) | pe->pawn_attacks(Them)); - ei.pinnedPieces[Us] = pos.pinned_pieces(Us); - Bitboard b = ei.attackedBy[Them][KING]; - ei.attackedBy[Them][ALL_PIECES] |= b; - ei.attackedBy[Us][ALL_PIECES] |= ei.attackedBy[Us][PAWN] = ei.pi->pawn_attacks(Us); - ei.attackedBy2[Us] = ei.attackedBy[Us][PAWN] & ei.attackedBy[Us][KING]; + // Initialise the attack bitboards with the king and pawn information + b = attackedBy[Us][KING] = pos.attacks_from(pos.square(Us)); + attackedBy[Us][PAWN] = pe->pawn_attacks(Us); - // Init king safety tables only if we are going to use them - if (pos.non_pawn_material(Us) >= QueenValueMg) + attackedBy2[Us] = b & attackedBy[Us][PAWN]; + attackedBy[Us][ALL_PIECES] = b | attackedBy[Us][PAWN]; + + // Init our king safety tables only if we are going to use them + if (pos.non_pawn_material(Them) >= RookValueMg + KnightValueMg) { - ei.kingRing[Them] = b | shift(b); - b &= ei.attackedBy[Us][PAWN]; - ei.kingAttackersCount[Us] = popcount(b); - ei.kingAdjacentZoneAttacksCount[Us] = ei.kingAttackersWeight[Us] = 0; + kingRing[Us] = b; + if (relative_rank(Us, pos.square(Us)) == RANK_1) + kingRing[Us] |= shift(b); + + kingAttackersCount[Them] = popcount(b & pe->pawn_attacks(Them)); + kingAdjacentZoneAttacksCount[Them] = kingAttackersWeight[Them] = 0; } else - ei.kingRing[Them] = ei.kingAttackersCount[Us] = 0; + kingRing[Us] = kingAttackersCount[Them] = 0; } // evaluate_pieces() assigns bonuses and penalties to the pieces of a given // color and type. - template - Score evaluate_pieces(const Position& pos, EvalInfo& ei, Score* mobility, - const Bitboard* mobilityArea) { - Bitboard b, bb; - Square s; - Score score = SCORE_ZERO; + template template + Score Evaluation::evaluate_pieces() { - const PieceType NextPt = (Us == WHITE ? Pt : PieceType(Pt + 1)); const Color Them = (Us == WHITE ? BLACK : WHITE); const Bitboard OutpostRanks = (Us == WHITE ? Rank4BB | Rank5BB | Rank6BB : Rank5BB | Rank4BB | Rank3BB); const Square* pl = pos.squares(Us); - ei.attackedBy[Us][Pt] = 0; + Bitboard b, bb; + Square s; + Score score = SCORE_ZERO; + + attackedBy[Us][Pt] = 0; + + if (Pt == QUEEN) + attackedBy[Us][QUEEN_DIAGONAL] = 0; while ((s = *pl++) != SQ_NONE) { // Find attacked squares, including x-ray attacks for bishops and rooks - b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(Us, QUEEN)) - : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(Us, ROOK, QUEEN)) + b = Pt == BISHOP ? attacks_bb(s, pos.pieces() ^ pos.pieces(QUEEN)) + : Pt == ROOK ? attacks_bb< ROOK>(s, pos.pieces() ^ pos.pieces(QUEEN) ^ pos.pieces(Us, ROOK)) : pos.attacks_from(s); - if (ei.pinnedPieces[Us] & s) + if (pos.pinned_pieces(Us) & s) b &= LineBB[pos.square(Us)][s]; - ei.attackedBy2[Us] |= ei.attackedBy[Us][ALL_PIECES] & b; - ei.attackedBy[Us][ALL_PIECES] |= ei.attackedBy[Us][Pt] |= b; + attackedBy2[Us] |= attackedBy[Us][ALL_PIECES] & b; + attackedBy[Us][ALL_PIECES] |= attackedBy[Us][Pt] |= b; - if (b & ei.kingRing[Them]) + if (Pt == QUEEN) + attackedBy[Us][QUEEN_DIAGONAL] |= b & PseudoAttacks[BISHOP][s]; + + if (b & kingRing[Them]) { - ei.kingAttackersCount[Us]++; - ei.kingAttackersWeight[Us] += KingAttackWeights[Pt]; - ei.kingAdjacentZoneAttacksCount[Us] += popcount(b & ei.attackedBy[Them][KING]); + kingAttackersCount[Us]++; + kingAttackersWeight[Us] += KingAttackWeights[Pt]; + kingAdjacentZoneAttacksCount[Us] += popcount(b & attackedBy[Them][KING]); } - if (Pt == QUEEN) - b &= ~( ei.attackedBy[Them][KNIGHT] - | ei.attackedBy[Them][BISHOP] - | ei.attackedBy[Them][ROOK]); - int mob = popcount(b & mobilityArea[Us]); - mobility[Us] += MobilityBonus[Pt][mob]; + mobility[Us] += MobilityBonus[Pt - 2][mob]; + + // Bonus for this piece as a king protector + score += KingProtector[Pt - 2] * distance(s, pos.square(Us)); if (Pt == BISHOP || Pt == KNIGHT) { // Bonus for outpost squares - bb = OutpostRanks & ~ei.pi->pawn_attacks_span(Them); + bb = OutpostRanks & ~pe->pawn_attacks_span(Them); if (bb & s) - score += Outpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & s)]; + score += Outpost[Pt == BISHOP][bool(attackedBy[Us][PAWN] & s)] * 2; else { bb &= b & ~pos.pieces(Us); if (bb) - score += ReachableOutpost[Pt == BISHOP][!!(ei.attackedBy[Us][PAWN] & bb)]; + score += Outpost[Pt == BISHOP][bool(attackedBy[Us][PAWN] & bb)]; } // Bonus when behind a pawn @@ -313,9 +358,15 @@ namespace { && (pos.pieces(PAWN) & (s + pawn_push(Us)))) score += MinorBehindPawn; - // Penalty for pawns on the same color square as the bishop if (Pt == BISHOP) - score -= BishopPawns * ei.pi->pawns_on_same_color_squares(Us, s); + { + // Penalty for pawns on the same color square as the bishop + score -= BishopPawns * pe->pawns_on_same_color_squares(Us, s); + + // Bonus for bishop on a long diagonal which can "see" both center squares + if (more_than_one(Center & (attacks_bb(s, pos.pieces(PAWN)) | s))) + score += LongRangedBishop; + } // An important Chess960 pattern: A cornered bishop blocked by a friendly // pawn diagonally in front of it is a very serious problem, especially @@ -324,7 +375,7 @@ namespace { && pos.is_chess960() && (s == relative_square(Us, SQ_A1) || s == relative_square(Us, SQ_H1))) { - Square d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); + Direction d = pawn_push(Us) + (file_of(s) == FILE_A ? EAST : WEST); if (pos.piece_on(s + d) == make_piece(Us, PAWN)) score -= !pos.empty(s + d + pawn_push(Us)) ? TrappedBishopA1H1 * 4 : pos.piece_on(s + d + d) == make_piece(Us, PAWN) ? TrappedBishopA1H1 * 2 @@ -339,17 +390,16 @@ namespace { score += RookOnPawn * popcount(pos.pieces(Them, PAWN) & PseudoAttacks[ROOK][s]); // Bonus when on an open or semi-open file - if (ei.pi->semiopen_file(Us, file_of(s))) - score += RookOnFile[!!ei.pi->semiopen_file(Them, file_of(s))]; + if (pe->semiopen_file(Us, file_of(s))) + score += RookOnFile[bool(pe->semiopen_file(Them, file_of(s)))]; - // Penalize when trapped by the king, even more if the king cannot castle + // Penalty when trapped by the king, even more if the king cannot castle else if (mob <= 3) { Square ksq = pos.square(Us); if ( ((file_of(ksq) < FILE_E) == (file_of(s) < file_of(ksq))) - && (rank_of(ksq) == rank_of(s) || relative_rank(Us, ksq) == RANK_1) - && !ei.pi->semiopen_side(Us, file_of(ksq), file_of(s) < file_of(ksq))) + && !pe->semiopen_side(Us, file_of(ksq), file_of(s) < file_of(ksq))) score -= (TrappedRook - make_score(mob * 22, 0)) * (1 + !pos.can_castle(Us)); } } @@ -363,129 +413,95 @@ namespace { } } - if (DoTrace) + if (T) Trace::add(Pt, Us, score); - // Recursively call evaluate_pieces() of next piece type until KING is excluded - return score - evaluate_pieces(pos, ei, mobility, mobilityArea); + return score; } - template<> - Score evaluate_pieces(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; } - template<> - Score evaluate_pieces< true, WHITE, KING>(const Position&, EvalInfo&, Score*, const Bitboard*) { return SCORE_ZERO; } - // evaluate_king() assigns bonuses and penalties to a king of a given color - const Bitboard WhiteCamp = Rank1BB | Rank2BB | Rank3BB | Rank4BB | Rank5BB; - const Bitboard BlackCamp = Rank8BB | Rank7BB | Rank6BB | Rank5BB | Rank4BB; - const Bitboard QueenSide = FileABB | FileBBB | FileCBB | FileDBB; - const Bitboard CenterFiles = FileCBB | FileDBB | FileEBB | FileFBB; - const Bitboard KingSide = FileEBB | FileFBB | FileGBB | FileHBB; - - const Bitboard KingFlank[COLOR_NB][FILE_NB] = { - { QueenSide & WhiteCamp, QueenSide & WhiteCamp, QueenSide & WhiteCamp, CenterFiles & WhiteCamp, - CenterFiles & WhiteCamp, KingSide & WhiteCamp, KingSide & WhiteCamp, KingSide & WhiteCamp }, - { QueenSide & BlackCamp, QueenSide & BlackCamp, QueenSide & BlackCamp, CenterFiles & BlackCamp, - CenterFiles & BlackCamp, KingSide & BlackCamp, KingSide & BlackCamp, KingSide & BlackCamp }, - }; - - template - Score evaluate_king(const Position& pos, const EvalInfo& ei) { + template template + Score Evaluation::evaluate_king() { - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard Camp = (Us == WHITE ? AllSquares ^ Rank6BB ^ Rank7BB ^ Rank8BB + : AllSquares ^ Rank1BB ^ Rank2BB ^ Rank3BB); - Bitboard undefended, b, b1, b2, safe, other; - int kingDanger; const Square ksq = pos.square(Us); + Bitboard weak, b, b1, b2, safe, unsafeChecks; // King shelter and enemy pawns storm - Score score = ei.pi->king_safety(pos, ksq); + Score score = pe->king_safety(pos, ksq); // Main king safety evaluation - if (ei.kingAttackersCount[Them]) + if (kingAttackersCount[Them] > (1 - pos.count(Them))) { - // Find the attacked squares which are defended only by the king... - undefended = ei.attackedBy[Them][ALL_PIECES] - & ei.attackedBy[Us][KING] - & ~ei.attackedBy2[Us]; - - // ... and those which are not defended at all in the larger king ring - b = ei.attackedBy[Them][ALL_PIECES] & ~ei.attackedBy[Us][ALL_PIECES] - & ei.kingRing[Us] & ~pos.pieces(Them); - - // Initialize the 'kingDanger' variable, which will be transformed - // later into a king danger score. The initial value is based on the - // number and types of the enemy's attacking pieces, the number of - // attacked and undefended squares around our king and the quality of - // the pawn shelter (current 'score' value). - kingDanger = std::min(807, ei.kingAttackersCount[Them] * ei.kingAttackersWeight[Them]) - + 101 * ei.kingAdjacentZoneAttacksCount[Them] - + 235 * popcount(undefended) - + 134 * (popcount(b) + !!ei.pinnedPieces[Us]) - - 717 * !pos.count(Them) - - 7 * mg_value(score) / 5 - 5; - - // Analyse the enemy's safe queen contact checks. Firstly, find the - // undefended squares around the king reachable by the enemy queen... - b = undefended & ei.attackedBy[Them][QUEEN] & ~pos.pieces(Them); - - // ...and keep squares supported by another enemy piece - kingDanger += QueenContactCheck * popcount(b & ei.attackedBy2[Them]); - - // Analyse the safe enemy's checks which are possible on next move... - safe = ~(ei.attackedBy[Us][ALL_PIECES] | pos.pieces(Them)); - - // ... and some other potential checks, only requiring the square to be - // safe from pawn-attacks, and not being occupied by a blocked pawn. - other = ~( ei.attackedBy[Us][PAWN] - | (pos.pieces(Them, PAWN) & shift(pos.pieces(PAWN)))); - - b1 = pos.attacks_from(ksq); - b2 = pos.attacks_from(ksq); - - // Enemy queen safe checks - if ((b1 | b2) & ei.attackedBy[Them][QUEEN] & safe) - kingDanger += QueenCheck, score -= SafeCheck; + // Attacked squares defended at most once by our queen or king + weak = attackedBy[Them][ALL_PIECES] + & ~attackedBy2[Us] + & (attackedBy[Us][KING] | attackedBy[Us][QUEEN] | ~attackedBy[Us][ALL_PIECES]); - // For other pieces, also consider the square safe if attacked twice, - // and only defended by a queen. - safe |= ei.attackedBy2[Them] - & ~(ei.attackedBy2[Us] | pos.pieces(Them)) - & ei.attackedBy[Us][QUEEN]; + int kingDanger = unsafeChecks = 0; - // Enemy rooks safe and other checks - if (b1 & ei.attackedBy[Them][ROOK] & safe) - kingDanger += RookCheck, score -= SafeCheck; + // Analyse the safe enemy's checks which are possible on next move + safe = ~pos.pieces(Them); + safe &= ~attackedBy[Us][ALL_PIECES] | (weak & attackedBy2[Them]); - else if (b1 & ei.attackedBy[Them][ROOK] & other) - score -= OtherCheck; + b1 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); + b2 = attacks_bb(ksq, pos.pieces() ^ pos.pieces(Us, QUEEN)); - // Enemy bishops safe and other checks - if (b2 & ei.attackedBy[Them][BISHOP] & safe) - kingDanger += BishopCheck, score -= SafeCheck; - - else if (b2 & ei.attackedBy[Them][BISHOP] & other) - score -= OtherCheck; - - // Enemy knights safe and other checks - b = pos.attacks_from(ksq) & ei.attackedBy[Them][KNIGHT]; + // Enemy queen safe checks + if ((b1 | b2) & attackedBy[Them][QUEEN] & safe & ~attackedBy[Us][QUEEN]) + kingDanger += QueenSafeCheck; + + b1 &= attackedBy[Them][ROOK]; + b2 &= attackedBy[Them][BISHOP]; + + // Enemy rooks checks + if (b1 & safe) + kingDanger += RookSafeCheck; + else + unsafeChecks |= b1; + + // Enemy bishops checks + if (b2 & safe) + kingDanger += BishopSafeCheck; + else + unsafeChecks |= b2; + + // Enemy knights checks + b = pos.attacks_from(ksq) & attackedBy[Them][KNIGHT]; if (b & safe) - kingDanger += KnightCheck, score -= SafeCheck; - - else if (b & other) - score -= OtherCheck; - - // Compute the king danger score and subtract it from the evaluation + kingDanger += KnightSafeCheck; + else + unsafeChecks |= b; + + // Unsafe or occupied checking squares will also be considered, as long as + // the square is in the attacker's mobility area. + unsafeChecks &= mobilityArea[Them]; + + kingDanger += kingAttackersCount[Them] * kingAttackersWeight[Them] + + 102 * kingAdjacentZoneAttacksCount[Them] + + 191 * popcount(kingRing[Us] & weak) + + 143 * popcount(pos.pinned_pieces(Us) | unsafeChecks) + - 848 * !pos.count(Them) + - 9 * mg_value(score) / 8 + + 40; + + // Transform the kingDanger units into a Score, and subtract it from the evaluation if (kingDanger > 0) - score -= make_score(std::min(kingDanger * kingDanger / 4096, 2 * int(BishopValueMg)), 0); + { + int mobilityDanger = mg_value(mobility[Them] - mobility[Us]); + kingDanger = std::max(0, kingDanger + mobilityDanger); + score -= make_score(kingDanger * kingDanger / 4096, kingDanger / 16); + } } // King tropism: firstly, find squares that opponent attacks in our king flank File kf = file_of(ksq); - b = ei.attackedBy[Them][ALL_PIECES] & KingFlank[Us][kf]; + b = attackedBy[Them][ALL_PIECES] & KingFlank[kf] & Camp; assert(((Us == WHITE ? b << 4 : b >> 4) & b) == 0); assert(popcount(Us == WHITE ? b << 4 : b >> 4) == popcount(b)); @@ -493,15 +509,15 @@ namespace { // Secondly, add the squares which are attacked twice in that flank and // which are not defended by our pawns. b = (Us == WHITE ? b << 4 : b >> 4) - | (b & ei.attackedBy2[Them] & ~ei.attackedBy[Us][PAWN]); + | (b & attackedBy2[Them] & ~attackedBy[Us][PAWN]); score -= CloseEnemies * popcount(b); // Penalty when our king is on a pawnless flank - if (!(pos.pieces(PAWN) & (KingFlank[WHITE][kf] | KingFlank[BLACK][kf]))) + if (!(pos.pieces(PAWN) & KingFlank[kf])) score -= PawnlessFlank; - if (DoTrace) + if (T) Trace::add(KING, Us, score); return score; @@ -511,128 +527,149 @@ namespace { // evaluate_threats() assigns bonuses according to the types of the attacking // and the attacked pieces. - template - Score evaluate_threats(const Position& pos, const EvalInfo& ei) { - - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); - const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); - const Bitboard TRank2BB = (Us == WHITE ? Rank2BB : Rank7BB); - const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + template template + Score Evaluation::evaluate_threats() { - enum { Minor, Rook }; + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + const Direction Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); - Bitboard b, weak, defended, safeThreats; + Bitboard b, weak, defended, stronglyProtected, safeThreats; Score score = SCORE_ZERO; - // Small bonus if the opponent has loose pawns or pieces - if ( (pos.pieces(Them) ^ pos.pieces(Them, QUEEN, KING)) - & ~(ei.attackedBy[Us][ALL_PIECES] | ei.attackedBy[Them][ALL_PIECES])) - score += LooseEnemies; - // Non-pawn enemies attacked by a pawn - weak = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Us][PAWN]; + weak = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & attackedBy[Us][PAWN]; if (weak) { - b = pos.pieces(Us, PAWN) & ( ~ei.attackedBy[Them][ALL_PIECES] - | ei.attackedBy[Us][ALL_PIECES]); + b = pos.pieces(Us, PAWN) & ( ~attackedBy[Them][ALL_PIECES] + | attackedBy[Us][ALL_PIECES]); safeThreats = (shift(b) | shift(b)) & weak; - if (weak ^ safeThreats) - score += ThreatByHangingPawn; - - while (safeThreats) - score += ThreatBySafePawn[type_of(pos.piece_on(pop_lsb(&safeThreats)))]; + score += ThreatBySafePawn * popcount(safeThreats); } - // Non-pawn enemies defended by a pawn - defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) & ei.attackedBy[Them][PAWN]; + // Squares strongly protected by the opponent, either because they attack the + // square with a pawn, or because they attack the square twice and we don't. + stronglyProtected = attackedBy[Them][PAWN] + | (attackedBy2[Them] & ~attackedBy2[Us]); + + // Non-pawn enemies, strongly protected + defended = (pos.pieces(Them) ^ pos.pieces(Them, PAWN)) + & stronglyProtected; - // Enemies not defended by a pawn and under our attack + // Enemies not strongly protected and under our attack weak = pos.pieces(Them) - & ~ei.attackedBy[Them][PAWN] - & ei.attackedBy[Us][ALL_PIECES]; + & ~stronglyProtected + & attackedBy[Us][ALL_PIECES]; // Add a bonus according to the kind of attacking pieces if (defended | weak) { - b = (defended | weak) & (ei.attackedBy[Us][KNIGHT] | ei.attackedBy[Us][BISHOP]); + b = (defended | weak) & (attackedBy[Us][KNIGHT] | attackedBy[Us][BISHOP]); while (b) - score += Threat[Minor][type_of(pos.piece_on(pop_lsb(&b)))]; + { + Square s = pop_lsb(&b); + score += ThreatByMinor[type_of(pos.piece_on(s))]; + if (type_of(pos.piece_on(s)) != PAWN) + score += ThreatByRank * (int)relative_rank(Them, s); + } - b = (pos.pieces(Them, QUEEN) | weak) & ei.attackedBy[Us][ROOK]; + b = (pos.pieces(Them, QUEEN) | weak) & attackedBy[Us][ROOK]; while (b) - score += Threat[Rook ][type_of(pos.piece_on(pop_lsb(&b)))]; + { + Square s = pop_lsb(&b); + score += ThreatByRook[type_of(pos.piece_on(s))]; + if (type_of(pos.piece_on(s)) != PAWN) + score += ThreatByRank * (int)relative_rank(Them, s); + } - score += Hanging * popcount(weak & ~ei.attackedBy[Them][ALL_PIECES]); + score += Hanging * popcount(weak & ~attackedBy[Them][ALL_PIECES]); - b = weak & ei.attackedBy[Us][KING]; + b = weak & attackedBy[Us][KING]; if (b) score += ThreatByKing[more_than_one(b)]; } - // Bonus if some pawns can safely push and attack an enemy piece - b = pos.pieces(Us, PAWN) & ~TRank7BB; - b = shift(b | (shift(b & TRank2BB) & ~pos.pieces())); + // Bonus for opponent unopposed weak pawns + if (pos.pieces(Us, ROOK, QUEEN)) + score += WeakUnopposedPawn * pe->weak_unopposed(Them); + + // Find squares where our pawns can push on the next move + b = shift(pos.pieces(Us, PAWN)) & ~pos.pieces(); + b |= shift(b & TRank3BB) & ~pos.pieces(); - b &= ~pos.pieces() - & ~ei.attackedBy[Them][PAWN] - & (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]); + // Keep only the squares which are not completely unsafe + b &= ~attackedBy[Them][PAWN] + & (attackedBy[Us][ALL_PIECES] | ~attackedBy[Them][ALL_PIECES]); + // Add a bonus for each new pawn threats from those squares b = (shift(b) | shift(b)) & pos.pieces(Them) - & ~ei.attackedBy[Us][PAWN]; + & ~attackedBy[Us][PAWN]; score += ThreatByPawnPush * popcount(b); - if (DoTrace) + // Add a bonus for safe slider attack threats on opponent queen + safeThreats = ~pos.pieces(Us) & ~attackedBy2[Them] & attackedBy2[Us]; + b = (attackedBy[Us][BISHOP] & attackedBy[Them][QUEEN_DIAGONAL]) + | (attackedBy[Us][ROOK ] & attackedBy[Them][QUEEN] & ~attackedBy[Them][QUEEN_DIAGONAL]); + + score += ThreatByAttackOnQueen * popcount(b & safeThreats); + + if (T) Trace::add(THREAT, Us, score); return score; } + // helper used by evaluate_passed_pawns to cap the distance + template + int Evaluation::king_distance(Color c, Square s) { + return std::min(distance(pos.square(c), s), 5); + } - // evaluate_passed_pawns() evaluates the passed pawns of the given color + // evaluate_passed_pawns() evaluates the passed pawns and candidate passed + // pawns of the given color. - template - Score evaluate_passed_pawns(const Position& pos, const EvalInfo& ei) { + template template + Score Evaluation::evaluate_passed_pawns() { - const Color Them = (Us == WHITE ? BLACK : WHITE); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); Bitboard b, bb, squaresToQueen, defendedSquares, unsafeSquares; Score score = SCORE_ZERO; - b = ei.pi->passed_pawns(Us); + b = pe->passed_pawns(Us); while (b) { Square s = pop_lsb(&b); - assert(pos.pawn_passed(Us, s)); - assert(!(pos.pieces(PAWN) & forward_bb(Us, s))); + assert(!(pos.pieces(Them, PAWN) & forward_file_bb(Us, s + Up))); - bb = forward_bb(Us, s) & (ei.attackedBy[Them][ALL_PIECES] | pos.pieces(Them)); + bb = forward_file_bb(Us, s) & (attackedBy[Them][ALL_PIECES] | pos.pieces(Them)); score -= HinderPassedPawn * popcount(bb); - int r = relative_rank(Us, s) - RANK_2; - int rr = r * (r - 1); + int r = relative_rank(Us, s); + int rr = RankFactor[r]; Value mbonus = Passed[MG][r], ebonus = Passed[EG][r]; if (rr) { - Square blockSq = s + pawn_push(Us); + Square blockSq = s + Up; // Adjust bonus based on the king's proximity - ebonus += distance(pos.square(Them), blockSq) * 5 * rr - - distance(pos.square(Us ), blockSq) * 2 * rr; + ebonus += (king_distance(Them, blockSq) * 5 - king_distance(Us, blockSq) * 2) * rr; // If blockSq is not the queening square then consider also a second push - if (relative_rank(Us, blockSq) != RANK_8) - ebonus -= distance(pos.square(Us), blockSq + pawn_push(Us)) * rr; + if (r != RANK_7) + ebonus -= king_distance(Us, blockSq + Up) * rr; // If the pawn is free to advance, then increase the bonus if (pos.empty(blockSq)) @@ -640,15 +677,15 @@ namespace { // If there is a rook or queen attacking/defending the pawn from behind, // consider all the squaresToQueen. Otherwise consider only the squares // in the pawn's path attacked or occupied by the enemy. - defendedSquares = unsafeSquares = squaresToQueen = forward_bb(Us, s); + defendedSquares = unsafeSquares = squaresToQueen = forward_file_bb(Us, s); - bb = forward_bb(Them, s) & pos.pieces(ROOK, QUEEN) & pos.attacks_from(s); + bb = forward_file_bb(Them, s) & pos.pieces(ROOK, QUEEN) & pos.attacks_from(s); if (!(pos.pieces(Us) & bb)) - defendedSquares &= ei.attackedBy[Us][ALL_PIECES]; + defendedSquares &= attackedBy[Us][ALL_PIECES]; if (!(pos.pieces(Them) & bb)) - unsafeSquares &= ei.attackedBy[Them][ALL_PIECES] | pos.pieces(Them); + unsafeSquares &= attackedBy[Them][ALL_PIECES] | pos.pieces(Them); // If there aren't any enemy attacks, assign a big bonus. Otherwise // assign a smaller bonus if the block square isn't attacked. @@ -668,13 +705,17 @@ namespace { mbonus += rr + r * 2, ebonus += rr + r * 2; } // rr != 0 + // Scale down bonus for candidate passers which need more than one + // pawn push to become passed or have a pawn in front of them. + if (!pos.pawn_passed(Us, s + Up) || (pos.pieces(PAWN) & forward_file_bb(Us, s))) + mbonus /= 2, ebonus /= 2; + score += make_score(mbonus, ebonus) + PassedFile[file_of(s)]; } - if (DoTrace) + if (T) Trace::add(PASSED, Us, score); - // Add the scores to the middlegame and endgame eval return score; } @@ -685,21 +726,22 @@ namespace { // squares one, two or three squares behind a friendly pawn are counted // twice. Finally, the space bonus is multiplied by a weight. The aim is to // improve play on game opening. - template - Score evaluate_space(const Position& pos, const EvalInfo& ei) { + + template template + Score Evaluation::evaluate_space() { const Color Them = (Us == WHITE ? BLACK : WHITE); const Bitboard SpaceMask = - Us == WHITE ? (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank2BB | Rank3BB | Rank4BB) - : (FileCBB | FileDBB | FileEBB | FileFBB) & (Rank7BB | Rank6BB | Rank5BB); + Us == WHITE ? CenterFiles & (Rank2BB | Rank3BB | Rank4BB) + : CenterFiles & (Rank7BB | Rank6BB | Rank5BB); // Find the safe squares for our pieces inside the area defined by // SpaceMask. A square is unsafe if it is attacked by an enemy // pawn, or if it is undefended and attacked by an enemy piece. Bitboard safe = SpaceMask & ~pos.pieces(Us, PAWN) - & ~ei.attackedBy[Them][PAWN] - & (ei.attackedBy[Us][ALL_PIECES] | ~ei.attackedBy[Them][ALL_PIECES]); + & ~attackedBy[Them][PAWN] + & (attackedBy[Us][ALL_PIECES] | ~attackedBy[Them][ALL_PIECES]); // Find all squares which are at most three squares behind some friendly pawn Bitboard behind = pos.pieces(Us, PAWN); @@ -709,46 +751,51 @@ namespace { // Since SpaceMask[Us] is fully on our half of the board... assert(unsigned(safe >> (Us == WHITE ? 32 : 0)) == 0); - // ...count safe + (behind & safe) with a single popcount + // ...count safe + (behind & safe) with a single popcount. int bonus = popcount((Us == WHITE ? safe << 32 : safe >> 32) | (behind & safe)); - bonus = std::min(16, bonus); - int weight = pos.count(Us) - 2 * ei.pi->open_files(); + int weight = pos.count(Us) - 2 * pe->open_files(); - return make_score(bonus * weight * weight / 18, 0); + return make_score(bonus * weight * weight / 16, 0); } // evaluate_initiative() computes the initiative correction value for the // position, i.e., second order bonus/malus based on the known attacking/defending // status of the players. - Score evaluate_initiative(const Position& pos, int asymmetry, Value eg) { + + template + Score Evaluation::evaluate_initiative(Value eg) { int kingDistance = distance(pos.square(WHITE), pos.square(BLACK)) - distance(pos.square(WHITE), pos.square(BLACK)); - int pawns = pos.count(WHITE) + pos.count(BLACK); + bool bothFlanks = (pos.pieces(PAWN) & QueenSide) && (pos.pieces(PAWN) & KingSide); // Compute the initiative bonus for the attacking side - int initiative = 8 * (asymmetry + kingDistance - 15) + 12 * pawns; + int initiative = 8 * (pe->pawn_asymmetry() + kingDistance - 17) + 12 * pos.count() + 16 * bothFlanks; // Now apply the bonus: note that we find the attacking side by extracting // the sign of the endgame value, and that we carefully cap the bonus so - // that the endgame score will never be divided by more than two. - int value = ((eg > 0) - (eg < 0)) * std::max(initiative, -abs(eg / 2)); + // that the endgame score will never change sign after the bonus. + int v = ((eg > 0) - (eg < 0)) * std::max(initiative, -abs(eg)); + + if (T) + Trace::add(INITIATIVE, make_score(0, v)); - return make_score(0, value); + return make_score(0, v); } // evaluate_scale_factor() computes the scale factor for the winning side - ScaleFactor evaluate_scale_factor(const Position& pos, const EvalInfo& ei, Value eg) { + + template + ScaleFactor Evaluation::evaluate_scale_factor(Value eg) { Color strongSide = eg > VALUE_DRAW ? WHITE : BLACK; - ScaleFactor sf = ei.me->scale_factor(pos, strongSide); + ScaleFactor sf = me->scale_factor(pos, strongSide); // If we don't already have an unusual scale factor, check for certain // types of endgames, and use a lower scale for those. - if ( ei.me->game_phase() < PHASE_MIDGAME - && (sf == SCALE_FACTOR_NORMAL || sf == SCALE_FACTOR_ONEPAWN)) + if (sf == SCALE_FACTOR_NORMAL || sf == SCALE_FACTOR_ONEPAWN) { if (pos.opposite_bishops()) { @@ -756,139 +803,116 @@ namespace { // is almost a draw, in case of KBP vs KB, it is even more a draw. if ( pos.non_pawn_material(WHITE) == BishopValueMg && pos.non_pawn_material(BLACK) == BishopValueMg) - sf = more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9); + return more_than_one(pos.pieces(PAWN)) ? ScaleFactor(31) : ScaleFactor(9); // Endgame with opposite-colored bishops, but also other pieces. Still // a bit drawish, but not as drawish as with only the two bishops. - else - sf = ScaleFactor(46); + return ScaleFactor(46); } // Endings where weaker side can place his king in front of the opponent's // pawns are drawish. else if ( abs(eg) <= BishopValueEg && pos.count(strongSide) <= 2 && !pos.pawn_passed(~strongSide, pos.square(~strongSide))) - sf = ScaleFactor(37 + 7 * pos.count(strongSide)); + return ScaleFactor(37 + 7 * pos.count(strongSide)); } return sf; } -} // namespace + // value() is the main function of the class. It computes the various parts of + // the evaluation and returns the value of the position from the point of view + // of the side to move. -/// evaluate() is the main evaluation function. It returns a static evaluation -/// of the position from the point of view of the side to move. + template + Value Evaluation::value() { -template -Value Eval::evaluate(const Position& pos) { + assert(!pos.checkers()); - assert(!pos.checkers()); + // Probe the material hash table + me = Material::probe(pos); - Score mobility[COLOR_NB] = { SCORE_ZERO, SCORE_ZERO }; - EvalInfo ei; + // If we have a specialized evaluation function for the current material + // configuration, call it and return. + if (me->specialized_eval_exists()) + return me->evaluate(pos); - // Probe the material hash table - ei.me = Material::probe(pos); + // Initialize score by reading the incrementally updated scores included in + // the position object (material + piece square tables) and the material + // imbalance. Score is computed internally from the white point of view. + Score score = pos.psq_score() + me->imbalance() + Eval::Contempt; - // If we have a specialized evaluation function for the current material - // configuration, call it and return. - if (ei.me->specialized_eval_exists()) - return ei.me->evaluate(pos); + // Probe the pawn hash table + pe = Pawns::probe(pos); + score += pe->pawns_score(); - // Initialize score by reading the incrementally updated scores included in - // the position object (material + piece square tables) and the material - // imbalance. Score is computed internally from the white point of view. - Score score = pos.psq_score() + ei.me->imbalance(); + // Early exit if score is high + Value v = (mg_value(score) + eg_value(score)) / 2; + if (abs(v) > LazyThreshold) + return pos.side_to_move() == WHITE ? v : -v; - // Probe the pawn hash table - ei.pi = Pawns::probe(pos); - score += ei.pi->pawns_score(); + // Main evaluation begins here - // Initialize attack and king safety bitboards - ei.attackedBy[WHITE][ALL_PIECES] = ei.attackedBy[BLACK][ALL_PIECES] = 0; - ei.attackedBy[WHITE][KING] = pos.attacks_from(pos.square(WHITE)); - ei.attackedBy[BLACK][KING] = pos.attacks_from(pos.square(BLACK)); - eval_init(pos, ei); - eval_init(pos, ei); + initialize(); + initialize(); - // Pawns blocked or on ranks 2 and 3 will be excluded from the mobility area - Bitboard blockedPawns[] = { - pos.pieces(WHITE, PAWN) & (shift(pos.pieces()) | Rank2BB | Rank3BB), - pos.pieces(BLACK, PAWN) & (shift(pos.pieces()) | Rank7BB | Rank6BB) - }; + score += evaluate_pieces() - evaluate_pieces(); + score += evaluate_pieces() - evaluate_pieces(); + score += evaluate_pieces() - evaluate_pieces(); + score += evaluate_pieces() - evaluate_pieces(); - // Do not include in mobility area squares protected by enemy pawns, or occupied - // by our blocked pawns or king. - Bitboard mobilityArea[] = { - ~(ei.attackedBy[BLACK][PAWN] | blockedPawns[WHITE] | pos.square(WHITE)), - ~(ei.attackedBy[WHITE][PAWN] | blockedPawns[BLACK] | pos.square(BLACK)) - }; + score += mobility[WHITE] - mobility[BLACK]; - // Evaluate all pieces but king and pawns - score += evaluate_pieces(pos, ei, mobility, mobilityArea); - score += mobility[WHITE] - mobility[BLACK]; + score += evaluate_king() + - evaluate_king(); - // Evaluate kings after all other pieces because we need full attack - // information when computing the king safety evaluation. - score += evaluate_king(pos, ei) - - evaluate_king(pos, ei); + score += evaluate_threats() + - evaluate_threats(); - // Evaluate tactical threats, we need full attack information including king - score += evaluate_threats(pos, ei) - - evaluate_threats(pos, ei); + score += evaluate_passed_pawns() + - evaluate_passed_pawns(); - // Evaluate passed pawns, we need full attack information including king - score += evaluate_passed_pawns(pos, ei) - - evaluate_passed_pawns(pos, ei); + if (pos.non_pawn_material() >= SpaceThreshold) + score += evaluate_space() + - evaluate_space(); - // If both sides have only pawns, score for potential unstoppable pawns - if (!pos.non_pawn_material(WHITE) && !pos.non_pawn_material(BLACK)) - { - Bitboard b; - if ((b = ei.pi->passed_pawns(WHITE)) != 0) - score += Unstoppable * int(relative_rank(WHITE, frontmost_sq(WHITE, b))); + score += evaluate_initiative(eg_value(score)); - if ((b = ei.pi->passed_pawns(BLACK)) != 0) - score -= Unstoppable * int(relative_rank(BLACK, frontmost_sq(BLACK, b))); - } + // Interpolate between a middlegame and a (scaled by 'sf') endgame score + ScaleFactor sf = evaluate_scale_factor(eg_value(score)); + v = mg_value(score) * int(me->game_phase()) + + eg_value(score) * int(PHASE_MIDGAME - me->game_phase()) * sf / SCALE_FACTOR_NORMAL; + + v /= int(PHASE_MIDGAME); + + // In case of tracing add all remaining individual evaluation terms + if (T) + { + Trace::add(MATERIAL, pos.psq_score()); + Trace::add(IMBALANCE, me->imbalance()); + Trace::add(PAWN, pe->pawns_score()); + Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); + if (pos.non_pawn_material() >= SpaceThreshold) + Trace::add(SPACE, evaluate_space() + , evaluate_space()); + Trace::add(TOTAL, score); + } - // Evaluate space for both sides, only during opening - if (pos.non_pawn_material(WHITE) + pos.non_pawn_material(BLACK) >= 12222) - score += evaluate_space(pos, ei) - - evaluate_space(pos, ei); - - // Evaluate position potential for the winning side - score += evaluate_initiative(pos, ei.pi->pawn_asymmetry(), eg_value(score)); - - // Evaluate scale factor for the winning side - ScaleFactor sf = evaluate_scale_factor(pos, ei, eg_value(score)); - - // Interpolate between a middlegame and a (scaled by 'sf') endgame score - Value v = mg_value(score) * int(ei.me->game_phase()) - + eg_value(score) * int(PHASE_MIDGAME - ei.me->game_phase()) * sf / SCALE_FACTOR_NORMAL; - - v /= int(PHASE_MIDGAME); - - // In case of tracing add all remaining individual evaluation terms - if (DoTrace) - { - Trace::add(MATERIAL, pos.psq_score()); - Trace::add(IMBALANCE, ei.me->imbalance()); - Trace::add(PAWN, ei.pi->pawns_score()); - Trace::add(MOBILITY, mobility[WHITE], mobility[BLACK]); - Trace::add(SPACE, evaluate_space(pos, ei) - , evaluate_space(pos, ei)); - Trace::add(TOTAL, score); + return pos.side_to_move() == WHITE ? v : -v; // Side to move point of view } - return (pos.side_to_move() == WHITE ? v : -v) + Eval::Tempo; // Side to move point of view -} +} // namespace -// Explicit template instantiations -template Value Eval::evaluate(const Position&); -template Value Eval::evaluate(const Position&); +Score Eval::Contempt = SCORE_ZERO; +/// evaluate() is the evaluator for the outer world. It returns a static evaluation +/// of the position from the point of view of the side to move. + +Value Eval::evaluate(const Position& pos) +{ + return Evaluation<>(pos).value() + Eval::Tempo; +} /// trace() is like evaluate(), but instead of returning a value, it returns /// a string (suitable for outputting to stdout) that contains the detailed @@ -898,7 +922,7 @@ std::string Eval::trace(const Position& pos) { std::memset(scores, 0, sizeof(scores)); - Value v = evaluate(pos); + Value v = Evaluation(pos).value() + Eval::Tempo; v = pos.side_to_move() == WHITE ? v : -v; // White's point of view std::stringstream ss; @@ -910,7 +934,7 @@ std::string Eval::trace(const Position& pos) { << " Imbalance | " << Term(IMBALANCE) << " Pawns | " << Term(PAWN) << " Knights | " << Term(KNIGHT) - << " Bishop | " << Term(BISHOP) + << " Bishops | " << Term(BISHOP) << " Rooks | " << Term(ROOK) << " Queens | " << Term(QUEEN) << " Mobility | " << Term(MOBILITY) @@ -918,6 +942,7 @@ std::string Eval::trace(const Position& pos) { << " Threats | " << Term(THREAT) << " Passed pawns | " << Term(PASSED) << " Space | " << Term(SPACE) + << " Initiative | " << Term(INITIATIVE) << "----------------+-------------+-------------+-------------\n" << " Total | " << Term(TOTAL); diff --git a/Engines/Windows/stockfish/src/evaluate.h b/Engines/Windows/stockfish/src/evaluate.h index 7f655f6..eef888d 100644 --- a/Engines/Windows/stockfish/src/evaluate.h +++ b/Engines/Windows/stockfish/src/evaluate.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,9 +31,10 @@ namespace Eval { const Value Tempo = Value(20); // Must be visible to search +extern Score Contempt; + std::string trace(const Position& pos); -template Value evaluate(const Position& pos); } diff --git a/Engines/Windows/stockfish/src/main.cpp b/Engines/Windows/stockfish/src/main.cpp index 7187d30..aad09ce 100644 --- a/Engines/Windows/stockfish/src/main.cpp +++ b/Engines/Windows/stockfish/src/main.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -43,12 +43,13 @@ int main(int argc, char* argv[]) { Bitbases::init(); Search::init(); Pawns::init(); - Threads.init(); Tablebases::init(Options["SyzygyPath"]); TT.resize(Options["Hash"]); + Threads.set(Options["Threads"]); + Search::clear(); // After threads are up UCI::loop(argc, argv); - Threads.exit(); + Threads.set(0); return 0; } diff --git a/Engines/Windows/stockfish/src/material.cpp b/Engines/Windows/stockfish/src/material.cpp index f6c4e2d..fb39209 100644 --- a/Engines/Windows/stockfish/src/material.cpp +++ b/Engines/Windows/stockfish/src/material.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,11 +35,11 @@ namespace { // OUR PIECES // pair pawn knight bishop rook queen {1667 }, // Bishop pair - { 40, 2 }, // Pawn + { 40, 0 }, // Pawn { 32, 255, -3 }, // Knight OUR PIECES { 0, 104, 4, 0 }, // Bishop { -26, -2, 47, 105, -149 }, // Rook - {-185, 24, 122, 137, -134, 0 } // Queen + {-189, 24, 117, 133, -134, -10 } // Queen }; const int QuadraticTheirs[][PIECE_TYPE_NB] = { @@ -50,7 +50,7 @@ namespace { { 9, 63, 0 }, // Knight OUR PIECES { 59, 65, 42, 0 }, // Bishop { 46, 39, 24, -24, 0 }, // Rook - { 101, 100, -37, 141, 268, 0 } // Queen + { 97, 100, -42, 137, 268, 0 } // Queen }; // Endgame evaluation and scaling functions are accessed directly and not through @@ -91,7 +91,7 @@ namespace { int bonus = 0; - // Second-degree polynomial material imbalance by Tord Romstad + // Second-degree polynomial material imbalance, by Tord Romstad for (int pt1 = NO_PIECE_TYPE; pt1 <= QUEEN; ++pt1) { if (!pieceCount[Us][pt1]) @@ -129,7 +129,13 @@ Entry* probe(const Position& pos) { std::memset(e, 0, sizeof(Entry)); e->key = key; e->factor[WHITE] = e->factor[BLACK] = (uint8_t)SCALE_FACTOR_NORMAL; - e->gamePhase = pos.game_phase(); + + Value npm_w = pos.non_pawn_material(WHITE); + Value npm_b = pos.non_pawn_material(BLACK); + Value npm = std::max(EndgameLimit, std::min(npm_w + npm_b, MidgameLimit)); + + // Map total non-pawn material into [PHASE_ENDGAME, PHASE_MIDGAME] + e->gamePhase = Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); // Let's look if we have a specialized evaluation function for this particular // material configuration. Firstly we look for a fixed configuration one, then @@ -150,7 +156,7 @@ Entry* probe(const Position& pos) { if ((sf = pos.this_thread()->endgames.probe(key)) != nullptr) { - e->scalingFunction[sf->strong_side()] = sf; // Only strong color assigned + e->scalingFunction[sf->strongSide] = sf; // Only strong color assigned return e; } @@ -166,9 +172,6 @@ Entry* probe(const Position& pos) { e->scalingFunction[c] = &ScaleKQKRPs[c]; } - Value npm_w = pos.non_pawn_material(WHITE); - Value npm_b = pos.non_pawn_material(BLACK); - if (npm_w + npm_b == VALUE_ZERO && pos.pieces(PAWN)) // Only pawns on the board { if (!pos.count(BLACK)) diff --git a/Engines/Windows/stockfish/src/material.h b/Engines/Windows/stockfish/src/material.h index bec2d66..7fea5e7 100644 --- a/Engines/Windows/stockfish/src/material.h +++ b/Engines/Windows/stockfish/src/material.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -56,11 +56,11 @@ struct Entry { } Key key; - int16_t value; - uint8_t factor[COLOR_NB]; EndgameBase* evaluationFunction; EndgameBase* scalingFunction[COLOR_NB]; // Could be one for each // side (e.g. KPKP, KBPsKs) + int16_t value; + uint8_t factor[COLOR_NB]; Phase gamePhase; }; diff --git a/Engines/Windows/stockfish/src/misc.cpp b/Engines/Windows/stockfish/src/misc.cpp index 4c6c254..2eb62f3 100644 --- a/Engines/Windows/stockfish/src/misc.cpp +++ b/Engines/Windows/stockfish/src/misc.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,10 +18,29 @@ along with this program. If not, see . */ +#ifdef _WIN32 +#if _WIN32_WINNT < 0x0601 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0601 // Force to include needed API prototypes +#endif +#include +// The needed Windows API for processor groups could be missed from old Windows +// versions, so instead of calling them directly (forcing the linker to resolve +// the calls at compile time), try to load them at runtime. To do this we need +// first to define the corresponding function pointers. +extern "C" { +typedef bool(*fun1_t)(LOGICAL_PROCESSOR_RELATIONSHIP, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PDWORD); +typedef bool(*fun2_t)(USHORT, PGROUP_AFFINITY); +typedef bool(*fun3_t)(HANDLE, CONST GROUP_AFFINITY*, PGROUP_AFFINITY); +} +#endif + #include #include #include #include +#include #include "misc.h" #include "thread.h" @@ -32,7 +51,7 @@ namespace { /// Version number. If Version is left empty, then compile date in the format /// DD-MM-YY and show in engine_info. -const string Version = "8"; +const string Version = "9"; /// Our fancy logging facility. The trick here is to replace cin.rdbuf() and /// cout.rdbuf() with two Tie objects that tie cin and cout to a file stream. We @@ -44,10 +63,10 @@ struct Tie: public streambuf { // MSVC requires split streambuf for cin and cout Tie(streambuf* b, streambuf* l) : buf(b), logBuf(l) {} - int sync() { return logBuf->pubsync(), buf->pubsync(); } - int overflow(int c) { return log(buf->sputc((char)c), "<< "); } - int underflow() { return buf->sgetc(); } - int uflow() { return log(buf->sbumpc(), ">> "); } + int sync() override { return logBuf->pubsync(), buf->pubsync(); } + int overflow(int c) override { return log(buf->sputc((char)c), "<< "); } + int underflow() override { return buf->sgetc(); } + int uflow() override { return log(buf->sbumpc(), ">> "); } streambuf *buf, *logBuf; @@ -185,3 +204,114 @@ void prefetch(void* addr) { } #endif + +void prefetch2(void* addr) { + + prefetch(addr); + prefetch((uint8_t*)addr + 64); +} + +namespace WinProcGroup { + +#ifndef _WIN32 + +void bindThisThread(size_t) {} + +#else + +/// get_group() retrieves logical processor information using Windows specific +/// API and returns the best group id for the thread with index idx. Original +/// code from Texel by Peter Österlund. + +int get_group(size_t idx) { + + int threads = 0; + int nodes = 0; + int cores = 0; + DWORD returnLength = 0; + DWORD byteOffset = 0; + + // Early exit if the needed API is not available at runtime + HMODULE k32 = GetModuleHandle("Kernel32.dll"); + auto fun1 = (fun1_t)GetProcAddress(k32, "GetLogicalProcessorInformationEx"); + if (!fun1) + return -1; + + // First call to get returnLength. We expect it to fail due to null buffer + if (fun1(RelationAll, nullptr, &returnLength)) + return -1; + + // Once we know returnLength, allocate the buffer + SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *buffer, *ptr; + ptr = buffer = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)malloc(returnLength); + + // Second call, now we expect to succeed + if (!fun1(RelationAll, buffer, &returnLength)) + { + free(buffer); + return -1; + } + + while (ptr->Size > 0 && byteOffset + ptr->Size <= returnLength) + { + if (ptr->Relationship == RelationNumaNode) + nodes++; + + else if (ptr->Relationship == RelationProcessorCore) + { + cores++; + threads += (ptr->Processor.Flags == LTP_PC_SMT) ? 2 : 1; + } + + byteOffset += ptr->Size; + ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*)(((char*)ptr) + ptr->Size); + } + + free(buffer); + + std::vector groups; + + // Run as many threads as possible on the same node until core limit is + // reached, then move on filling the next node. + for (int n = 0; n < nodes; n++) + for (int i = 0; i < cores / nodes; i++) + groups.push_back(n); + + // In case a core has more than one logical processor (we assume 2) and we + // have still threads to allocate, then spread them evenly across available + // nodes. + for (int t = 0; t < threads - cores; t++) + groups.push_back(t % nodes); + + // If we still have more threads than the total number of logical processors + // then return -1 and let the OS to decide what to do. + return idx < groups.size() ? groups[idx] : -1; +} + + +/// bindThisThread() set the group affinity of the current thread + +void bindThisThread(size_t idx) { + + // Use only local variables to be thread-safe + int group = get_group(idx); + + if (group == -1) + return; + + // Early exit if the needed API are not available at runtime + HMODULE k32 = GetModuleHandle("Kernel32.dll"); + auto fun2 = (fun2_t)GetProcAddress(k32, "GetNumaNodeProcessorMaskEx"); + auto fun3 = (fun3_t)GetProcAddress(k32, "SetThreadGroupAffinity"); + + if (!fun2 || !fun3) + return; + + GROUP_AFFINITY affinity; + if (fun2(group, &affinity)) + fun3(GetCurrentThread(), &affinity, nullptr); +} + +#endif + +} // namespace WinProcGroup diff --git a/Engines/Windows/stockfish/src/misc.h b/Engines/Windows/stockfish/src/misc.h index a2307fe..563a58a 100644 --- a/Engines/Windows/stockfish/src/misc.h +++ b/Engines/Windows/stockfish/src/misc.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,6 +31,7 @@ const std::string engine_info(bool to_uci = false); void prefetch(void* addr); +void prefetch2(void* addr); void start_logger(const std::string& fname); void dbg_hit_on(bool b); @@ -97,4 +98,15 @@ class PRNG { { return T(rand64() & rand64() & rand64()); } }; + +/// Under Windows it is not possible for a process to run on more than one +/// logical processor group. This usually means to be limited to use max 64 +/// cores. To overcome this, some special platform specific API should be +/// called to set group affinity for each thread. Original code from Texel by +/// Peter Österlund. + +namespace WinProcGroup { + void bindThisThread(size_t idx); +} + #endif // #ifndef MISC_H_INCLUDED diff --git a/Engines/Windows/stockfish/src/movegen.cpp b/Engines/Windows/stockfish/src/movegen.cpp index 6a3cdc2..15de166 100644 --- a/Engines/Windows/stockfish/src/movegen.cpp +++ b/Engines/Windows/stockfish/src/movegen.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -42,8 +42,8 @@ namespace { assert(!pos.checkers()); - const Square K = Chess960 ? kto > kfrom ? WEST : EAST - : KingSide ? WEST : EAST; + const Direction K = Chess960 ? kto > kfrom ? WEST : EAST + : KingSide ? WEST : EAST; for (Square s = kto; s != kfrom; s += K) if (pos.attackers_to(s) & enemies) @@ -65,7 +65,7 @@ namespace { } - template + template ExtMove* make_promotions(ExtMove* moveList, Square to, Square ksq) { if (Type == CAPTURES || Type == EVASIONS || Type == NON_EVASIONS) @@ -80,7 +80,7 @@ namespace { // Knight promotion is the only promotion that can give a direct check // that's not already included in the queen promotion. - if (Type == QUIET_CHECKS && (StepAttacksBB[W_KNIGHT][to] & ksq)) + if (Type == QUIET_CHECKS && (PseudoAttacks[KNIGHT][to] & ksq)) *moveList++ = make(to - D, to, KNIGHT); else (void)ksq; // Silence a warning under MSVC @@ -94,13 +94,13 @@ namespace { // Compute our parametrized parameters at compile time, named according to // the point of view of white side. - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); - const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); - const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); - const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Bitboard TRank8BB = (Us == WHITE ? Rank8BB : Rank1BB); + const Bitboard TRank7BB = (Us == WHITE ? Rank7BB : Rank2BB); + const Bitboard TRank3BB = (Us == WHITE ? Rank3BB : Rank6BB); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Direction Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); Bitboard emptySquares; @@ -346,7 +346,7 @@ ExtMove* generate(const Position& pos, ExtMove* moveList) { if (pt == PAWN) continue; // Will be generated together with direct checks - Bitboard b = pos.attacks_from(Piece(pt), from) & ~pos.pieces(); + Bitboard b = pos.attacks_from(pt, from) & ~pos.pieces(); if (pt == KING) b &= ~PseudoAttacks[QUEEN][pos.square(~us)]; diff --git a/Engines/Windows/stockfish/src/movegen.h b/Engines/Windows/stockfish/src/movegen.h index 2721f15..9bf2a46 100644 --- a/Engines/Windows/stockfish/src/movegen.h +++ b/Engines/Windows/stockfish/src/movegen.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,6 +21,8 @@ #ifndef MOVEGEN_H_INCLUDED #define MOVEGEN_H_INCLUDED +#include + #include "types.h" class Position; @@ -36,10 +38,14 @@ enum GenType { struct ExtMove { Move move; - Value value; + int value; operator Move() const { return move; } void operator=(Move m) { move = m; } + + // Inhibit unwanted implicit conversions to Move + // with an ambiguity that yields to a compile error. + operator float() const = delete; }; inline bool operator<(const ExtMove& f, const ExtMove& s) { @@ -59,8 +65,7 @@ struct MoveList { const ExtMove* end() const { return last; } size_t size() const { return last - moveList; } bool contains(Move move) const { - for (const auto& m : *this) if (m == move) return true; - return false; + return std::find(begin(), end(), move) != end(); } private: diff --git a/Engines/Windows/stockfish/src/movepick.cpp b/Engines/Windows/stockfish/src/movepick.cpp index c187c3a..ab247b7 100644 --- a/Engines/Windows/stockfish/src/movepick.cpp +++ b/Engines/Windows/stockfish/src/movepick.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ #include #include "movepick.h" -#include "thread.h" namespace { @@ -34,27 +33,28 @@ namespace { QSEARCH_RECAPTURES, QRECAPTURES }; - // Our insertion sort, which is guaranteed to be stable, as it should be - void insertion_sort(ExtMove* begin, ExtMove* end) - { - ExtMove tmp, *p, *q; - - for (p = begin + 1; p < end; ++p) - { - tmp = *p; - for (q = p; q != begin && *(q-1) < tmp; --q) - *q = *(q-1); - *q = tmp; - } + // partial_insertion_sort() sorts moves in descending order up to and including + // a given limit. The order of moves smaller than the limit is left unspecified. + void partial_insertion_sort(ExtMove* begin, ExtMove* end, int limit) { + + for (ExtMove *sortedEnd = begin, *p = begin + 1; p < end; ++p) + if (p->value >= limit) + { + ExtMove tmp = *p, *q; + *p = *++sortedEnd; + for (q = sortedEnd; q != begin && *(q - 1) < tmp; --q) + *q = *(q - 1); + *q = tmp; + } } // pick_best() finds the best move in the range (begin, end) and moves it to // the front. It's faster than sorting all the moves in advance when there // are few moves, e.g., the possible captures. - Move pick_best(ExtMove* begin, ExtMove* end) - { - std::swap(*begin, *std::max_element(begin, end)); - return *begin; + Move pick_best(ExtMove* begin, ExtMove* end) { + + std::swap(*begin, *std::max_element(begin, end)); + return *begin; } } // namespace @@ -66,21 +66,22 @@ namespace { /// search captures, promotions, and some checks) and how important good move /// ordering is at the current node. -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Search::Stack* s) - : pos(p), ss(s), depth(d) { +/// MovePicker constructor for the main search +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, + const CapturePieceToHistory* cph, const PieceToHistory** ch, Move cm, Move* killers_p) + : pos(p), mainHistory(mh), captureHistory(cph), contHistory(ch), countermove(cm), + killers{killers_p[0], killers_p[1]}, depth(d){ assert(d > DEPTH_ZERO); - Square prevSq = to_sq((ss-1)->currentMove); - countermove = pos.this_thread()->counterMoves[pos.piece_on(prevSq)][prevSq]; - stage = pos.checkers() ? EVASION : MAIN_SEARCH; ttMove = ttm && pos.pseudo_legal(ttm) ? ttm : MOVE_NONE; stage += (ttMove == MOVE_NONE); } -MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Square s) - : pos(p) { +/// MovePicker constructor for quiescence search +MovePicker::MovePicker(const Position& p, Move ttm, Depth d, const ButterflyHistory* mh, const CapturePieceToHistory* cph, Square s) + : pos(p), mainHistory(mh), captureHistory(cph) { assert(d <= DEPTH_ZERO); @@ -104,81 +105,57 @@ MovePicker::MovePicker(const Position& p, Move ttm, Depth d, Square s) stage += (ttMove == MOVE_NONE); } -MovePicker::MovePicker(const Position& p, Move ttm, Value th) - : pos(p), threshold(th) { +/// MovePicker constructor for ProbCut: we generate captures with SEE higher +/// than or equal to the given threshold. +MovePicker::MovePicker(const Position& p, Move ttm, Value th, const CapturePieceToHistory* cph) + : pos(p), captureHistory(cph), threshold(th) { assert(!pos.checkers()); stage = PROBCUT; - - // In ProbCut we generate captures with SEE higher than the given threshold ttMove = ttm && pos.pseudo_legal(ttm) && pos.capture(ttm) - && pos.see_ge(ttm, threshold + 1)? ttm : MOVE_NONE; + && pos.see_ge(ttm, threshold) ? ttm : MOVE_NONE; stage += (ttMove == MOVE_NONE); } +/// score() assigns a numerical value to each move in a list, used for sorting. +/// Captures are ordered by Most Valuable Victim (MVV), preferring captures +/// with a good history. Quiets are ordered using the histories. +template +void MovePicker::score() { -/// score() assigns a numerical value to each move in a move list. The moves with -/// highest values will be picked first. -template<> -void MovePicker::score() { - // Winning and equal captures in the main search are ordered by MVV, preferring - // captures near our home rank. Surprisingly, this appears to perform slightly - // better than SEE-based move ordering: exchanging big pieces before capturing - // a hanging piece probably helps to reduce the subtree size. - // In the main search we want to push captures with negative SEE values to the - // badCaptures[] array, but instead of doing it now we delay until the move - // has been picked up, saving some SEE calls in case we get a cutoff. - for (auto& m : *this) - m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - - Value(200 * relative_rank(pos.side_to_move(), to_sq(m))); -} - -template<> -void MovePicker::score() { - - const HistoryStats& history = pos.this_thread()->history; - const FromToStats& fromTo = pos.this_thread()->fromTo; - - const CounterMoveStats* cm = (ss-1)->counterMoves; - const CounterMoveStats* fm = (ss-2)->counterMoves; - const CounterMoveStats* f2 = (ss-4)->counterMoves; - - Color c = pos.side_to_move(); + static_assert(Type == CAPTURES || Type == QUIETS || Type == EVASIONS, "Wrong type"); for (auto& m : *this) - m.value = history[pos.moved_piece(m)][to_sq(m)] - + (cm ? (*cm)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) - + (fm ? (*fm)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) - + (f2 ? (*f2)[pos.moved_piece(m)][to_sq(m)] : VALUE_ZERO) - + fromTo.get(c, m); -} + if (Type == CAPTURES) + m.value = PieceValue[MG][pos.piece_on(to_sq(m))] + + Value((*captureHistory)[pos.moved_piece(m)][to_sq(m)][type_of(pos.piece_on(to_sq(m)))]); -template<> -void MovePicker::score() { - // Try captures ordered by MVV/LVA, then non-captures ordered by history value - const HistoryStats& history = pos.this_thread()->history; - const FromToStats& fromTo = pos.this_thread()->fromTo; - Color c = pos.side_to_move(); + else if (Type == QUIETS) + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] + + (*contHistory[0])[pos.moved_piece(m)][to_sq(m)] + + (*contHistory[1])[pos.moved_piece(m)][to_sq(m)] + + (*contHistory[3])[pos.moved_piece(m)][to_sq(m)]; - for (auto& m : *this) - if (pos.capture(m)) - m.value = PieceValue[MG][pos.piece_on(to_sq(m))] - - Value(type_of(pos.moved_piece(m))) + HistoryStats::Max; - else - m.value = history[pos.moved_piece(m)][to_sq(m)] + fromTo.get(c, m); + else // Type == EVASIONS + { + if (pos.capture(m)) + m.value = PieceValue[MG][pos.piece_on(to_sq(m))] + - Value(type_of(pos.moved_piece(m))); + else + m.value = (*mainHistory)[pos.side_to_move()][from_to(m)] - (1 << 28); + } } - /// next_move() is the most important method of the MovePicker class. It returns /// a new pseudo legal move every time it is called, until there are no more moves /// left. It picks the move with the biggest value from a list of generated moves /// taking care not to return the ttMove if it has already been searched. -Move MovePicker::next_move() { +Move MovePicker::next_move(bool skipQuiets) { Move move; @@ -194,6 +171,7 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); ++stage; + /* fallthrough */ case GOOD_CAPTURES: while (cur < endMoves) @@ -201,7 +179,7 @@ Move MovePicker::next_move() { move = pick_best(cur++, endMoves); if (move != ttMove) { - if (pos.see_ge(move, VALUE_ZERO)) + if (pos.see_ge(move, Value(-55 * (cur-1)->value / 1024))) return move; // Losing capture, move it to the beginning of the array @@ -210,58 +188,59 @@ Move MovePicker::next_move() { } ++stage; - move = ss->killers[0]; // First killer move + move = killers[0]; // First killer move if ( move != MOVE_NONE && move != ttMove && pos.pseudo_legal(move) && !pos.capture(move)) return move; + /* fallthrough */ case KILLERS: ++stage; - move = ss->killers[1]; // Second killer move + move = killers[1]; // Second killer move if ( move != MOVE_NONE && move != ttMove && pos.pseudo_legal(move) && !pos.capture(move)) return move; + /* fallthrough */ case COUNTERMOVE: ++stage; move = countermove; if ( move != MOVE_NONE && move != ttMove - && move != ss->killers[0] - && move != ss->killers[1] + && move != killers[0] + && move != killers[1] && pos.pseudo_legal(move) && !pos.capture(move)) return move; + /* fallthrough */ case QUIET_INIT: cur = endBadCaptures; endMoves = generate(pos, cur); score(); - if (depth < 3 * ONE_PLY) - { - ExtMove* goodQuiet = std::partition(cur, endMoves, [](const ExtMove& m) - { return m.value > VALUE_ZERO; }); - insertion_sort(cur, goodQuiet); - } else - insertion_sort(cur, endMoves); + partial_insertion_sort(cur, endMoves, -4000 * depth / ONE_PLY); ++stage; + /* fallthrough */ case QUIET: - while (cur < endMoves) + while ( cur < endMoves + && (!skipQuiets || cur->value >= VALUE_ZERO)) { move = *cur++; + if ( move != ttMove - && move != ss->killers[0] - && move != ss->killers[1] + && move != killers[0] + && move != killers[1] && move != countermove) return move; } ++stage; cur = moves; // Point to beginning of bad captures + /* fallthrough */ case BAD_CAPTURES: if (cur < endBadCaptures) @@ -273,6 +252,7 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); ++stage; + /* fallthrough */ case ALL_EVASIONS: while (cur < endMoves) @@ -288,13 +268,14 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); ++stage; + /* fallthrough */ case PROBCUT_CAPTURES: while (cur < endMoves) { move = pick_best(cur++, endMoves); if ( move != ttMove - && pos.see_ge(move, threshold + 1)) + && pos.see_ge(move, threshold)) return move; } break; @@ -304,6 +285,7 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); ++stage; + /* fallthrough */ case QCAPTURES_1: case QCAPTURES_2: while (cur < endMoves) @@ -317,6 +299,7 @@ Move MovePicker::next_move() { cur = moves; endMoves = generate(pos, cur); ++stage; + /* fallthrough */ case QCHECKS: while (cur < endMoves) @@ -332,6 +315,7 @@ Move MovePicker::next_move() { endMoves = generate(pos, cur); score(); ++stage; + /* fallthrough */ case QRECAPTURES: while (cur < endMoves) diff --git a/Engines/Windows/stockfish/src/movepick.h b/Engines/Windows/stockfish/src/movepick.h index 6fbd8be..0aba61d 100644 --- a/Engines/Windows/stockfish/src/movepick.h +++ b/Engines/Windows/stockfish/src/movepick.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,68 +21,98 @@ #ifndef MOVEPICK_H_INCLUDED #define MOVEPICK_H_INCLUDED -#include // For std::max -#include // For std::memset +#include +#include #include "movegen.h" #include "position.h" #include "types.h" +/// StatBoards is a generic 2-dimensional array used to store various statistics +template +struct StatBoards : public std::array, Size1> { -/// The Stats struct stores moves statistics. According to the template parameter -/// the class can store History and Countermoves. History records how often -/// different moves have been successful or unsuccessful during the current search -/// and is used for reduction and move ordering decisions. -/// Countermoves store the move that refute a previous one. Entries are stored -/// using only the moving piece and destination square, hence two moves with -/// different origin but same destination and piece will be considered identical. -template -struct Stats { + void fill(const T& v) { + T* p = &(*this)[0][0]; + std::fill(p, p + sizeof(*this) / sizeof(*p), v); + } - static const Value Max = Value(1 << 28); + void update(T& entry, int bonus, const int D) { - const T* operator[](Piece pc) const { return table[pc]; } - T* operator[](Piece pc) { return table[pc]; } - void clear() { std::memset(table, 0, sizeof(table)); } - void update(Piece pc, Square to, Move m) { table[pc][to] = m; } - void update(Piece pc, Square to, Value v) { + assert(abs(bonus) <= D); // Ensure range is [-32 * D, 32 * D] + assert(abs(32 * D) < (std::numeric_limits::max)()); // Ensure we don't overflow - if (abs(int(v)) >= 324) - return; + entry += bonus * 32 - entry * abs(bonus) / D; - table[pc][to] -= table[pc][to] * abs(int(v)) / (CM ? 936 : 324); - table[pc][to] += int(v) * 32; + assert(abs(entry) <= 32 * D); } +}; -private: - T table[PIECE_NB][SQUARE_NB]; +/// StatCubes is a generic 3-dimensional array used to store various statistics +template +struct StatCubes : public std::array, Size2>, Size1> { + + void fill(const T& v) { + T* p = &(*this)[0][0][0]; + std::fill(p, p + sizeof(*this) / sizeof(*p), v); + } + + void update(T& entry, int bonus, const int D, const int W) { + + assert(abs(bonus) <= D); // Ensure range is [-W * D, W * D] + assert(abs(W * D) < (std::numeric_limits::max)()); // Ensure we don't overflow + + entry += bonus * W - entry * abs(bonus) / D; + + assert(abs(entry) <= W * D); + } }; -typedef Stats MoveStats; -typedef Stats HistoryStats; -typedef Stats CounterMoveStats; -typedef Stats CounterMoveHistoryStats; +/// ButterflyBoards are 2 tables (one for each color) indexed by the move's from +/// and to squares, see chessprogramming.wikispaces.com/Butterfly+Boards +typedef StatBoards ButterflyBoards; -struct FromToStats { +/// PieceToBoards are addressed by a move's [piece][to] information +typedef StatBoards PieceToBoards; - Value get(Color c, Move m) const { return table[c][from_sq(m)][to_sq(m)]; } - void clear() { std::memset(table, 0, sizeof(table)); } - void update(Color c, Move m, Value v) { +/// CapturePieceToBoards are addressed by a move's [piece][to][captured piece type] information +typedef StatCubes CapturePieceToBoards; - if (abs(int(v)) >= 324) - return; +/// ButterflyHistory records how often quiet moves have been successful or +/// unsuccessful during the current search, and is used for reduction and move +/// ordering decisions. It uses ButterflyBoards as backing store. +struct ButterflyHistory : public ButterflyBoards { - Square from = from_sq(m); - Square to = to_sq(m); + void update(Color c, Move m, int bonus) { + StatBoards::update((*this)[c][from_to(m)], bonus, 324); + } +}; + +/// PieceToHistory is like ButterflyHistory, but is based on PieceToBoards +struct PieceToHistory : public PieceToBoards { - table[c][from][to] -= table[c][from][to] * abs(int(v)) / 324; - table[c][from][to] += int(v) * 32; + void update(Piece pc, Square to, int bonus) { + StatBoards::update((*this)[pc][to], bonus, 936); } +}; -private: - Value table[COLOR_NB][SQUARE_NB][SQUARE_NB]; +/// CapturePieceToHistory is like PieceToHistory, but is based on CapturePieceToBoards +struct CapturePieceToHistory : public CapturePieceToBoards { + + void update(Piece pc, Square to, PieceType captured, int bonus) { + StatCubes::update((*this)[pc][to][captured], bonus, 324, 2); + } }; +/// CounterMoveHistory stores counter moves indexed by [piece][to] of the previous +/// move, see chessprogramming.wikispaces.com/Countermove+Heuristic +typedef StatBoards CounterMoveHistory; + +/// ContinuationHistory is the history of a given pair of moves, usually the +/// current one given a previous one. History table is based on PieceToBoards +/// instead of ButterflyBoards. +typedef StatBoards ContinuationHistory; + /// MovePicker class is used to pick one pseudo legal move at a time from the /// current position. The most important method is next_move(), which returns a @@ -90,18 +120,15 @@ struct FromToStats { /// when MOVE_NONE is returned. In order to improve the efficiency of the alpha /// beta algorithm, MovePicker attempts to return the moves which are most likely /// to get a cut-off first. -namespace Search { struct Stack; } class MovePicker { public: MovePicker(const MovePicker&) = delete; MovePicker& operator=(const MovePicker&) = delete; - - MovePicker(const Position&, Move, Value); - MovePicker(const Position&, Move, Depth, Square); - MovePicker(const Position&, Move, Depth, Search::Stack*); - - Move next_move(); + MovePicker(const Position&, Move, Value, const CapturePieceToHistory*); + MovePicker(const Position&, Move, Depth, const ButterflyHistory*, const CapturePieceToHistory*, Square); + MovePicker(const Position&, Move, Depth, const ButterflyHistory*, const CapturePieceToHistory*, const PieceToHistory**, Move, Move*); + Move next_move(bool skipQuiets = false); private: template void score(); @@ -109,14 +136,15 @@ class MovePicker { ExtMove* end() { return endMoves; } const Position& pos; - const Search::Stack* ss; - Move countermove; - Depth depth; - Move ttMove; + const ButterflyHistory* mainHistory; + const CapturePieceToHistory* captureHistory; + const PieceToHistory** contHistory; + Move ttMove, countermove, killers[2]; + ExtMove *cur, *endMoves, *endBadCaptures; + int stage; Square recaptureSquare; Value threshold; - int stage; - ExtMove *cur, *endMoves, *endBadCaptures; + Depth depth; ExtMove moves[MAX_MOVES]; }; diff --git a/Engines/Windows/stockfish/src/pawns.cpp b/Engines/Windows/stockfish/src/pawns.cpp index 80f5e49..ceacca8 100644 --- a/Engines/Windows/stockfish/src/pawns.cpp +++ b/Engines/Windows/stockfish/src/pawns.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,53 +31,51 @@ namespace { #define V Value #define S(mg, eg) make_score(mg, eg) - // Isolated pawn penalty by opposed flag - const Score Isolated[2] = { S(45, 40), S(30, 27) }; + // Isolated pawn penalty + const Score Isolated = S(13, 18); - // Backward pawn penalty by opposed flag - const Score Backward[2] = { S(56, 33), S(41, 19) }; + // Backward pawn penalty + const Score Backward = S(24, 12); - // Unsupported pawn penalty for pawns which are neither isolated or backward - const Score Unsupported = S(17, 8); - - // Connected pawn bonus by opposed, phalanx, twice supported and rank - Score Connected[2][2][2][RANK_NB]; + // Connected pawn bonus by opposed, phalanx, #support and rank + Score Connected[2][2][3][RANK_NB]; // Doubled pawn penalty - const Score Doubled = S(18,38); - - // Lever bonus by rank - const Score Lever[RANK_NB] = { - S( 0, 0), S( 0, 0), S(0, 0), S(0, 0), - S(17, 16), S(33, 32), S(0, 0), S(0, 0) - }; - - // Weakness of our pawn shelter in front of the king by [distance from edge][rank] - const Value ShelterWeakness[][RANK_NB] = { - { V( 97), V(21), V(26), V(51), V(87), V( 89), V( 99) }, - { V(120), V( 0), V(28), V(76), V(88), V(103), V(104) }, - { V(101), V( 7), V(54), V(78), V(77), V( 92), V(101) }, - { V( 80), V(11), V(44), V(68), V(87), V( 90), V(119) } + const Score Doubled = S(18, 38); + + // Weakness of our pawn shelter in front of the king by [isKingFile][distance from edge][rank]. + // RANK_1 = 0 is used for files where we have no pawns or our pawn is behind our king. + const Value ShelterWeakness[][int(FILE_NB) / 2][RANK_NB] = { + { { V( 97), V(17), V( 9), V(44), V( 84), V( 87), V( 99) }, // Not On King file + { V(106), V( 6), V(33), V(86), V( 87), V(104), V(112) }, + { V(101), V( 2), V(65), V(98), V( 58), V( 89), V(115) }, + { V( 73), V( 7), V(54), V(73), V( 84), V( 83), V(111) } }, + { { V(104), V(20), V( 6), V(27), V( 86), V( 93), V( 82) }, // On King file + { V(123), V( 9), V(34), V(96), V(112), V( 88), V( 75) }, + { V(120), V(25), V(65), V(91), V( 66), V( 78), V(117) }, + { V( 81), V( 2), V(47), V(63), V( 94), V( 93), V(104) } } }; - // Danger of enemy pawns moving toward our king by [type][distance from edge][rank] + // Danger of enemy pawns moving toward our king by [type][distance from edge][rank]. + // For the unopposed and unblocked cases, RANK_1 = 0 is used when opponent has + // no pawn on the given file, or their pawn is behind our king. const Value StormDanger[][4][RANK_NB] = { - { { V( 0), V( 67), V( 134), V(38), V(32) }, - { V( 0), V( 57), V( 139), V(37), V(22) }, - { V( 0), V( 43), V( 115), V(43), V(27) }, - { V( 0), V( 68), V( 124), V(57), V(32) } }, - { { V(20), V( 43), V( 100), V(56), V(20) }, - { V(23), V( 20), V( 98), V(40), V(15) }, - { V(23), V( 39), V( 103), V(36), V(18) }, - { V(28), V( 19), V( 108), V(42), V(26) } }, - { { V( 0), V( 0), V( 75), V(14), V( 2) }, - { V( 0), V( 0), V( 150), V(30), V( 4) }, - { V( 0), V( 0), V( 160), V(22), V( 5) }, - { V( 0), V( 0), V( 166), V(24), V(13) } }, - { { V( 0), V(-283), V(-281), V(57), V(31) }, - { V( 0), V( 58), V( 141), V(39), V(18) }, - { V( 0), V( 65), V( 142), V(48), V(32) }, - { V( 0), V( 60), V( 126), V(51), V(19) } } + { { V( 0), V(-290), V(-274), V(57), V(41) }, // BlockedByKing + { V( 0), V( 60), V( 144), V(39), V(13) }, + { V( 0), V( 65), V( 141), V(41), V(34) }, + { V( 0), V( 53), V( 127), V(56), V(14) } }, + { { V( 4), V( 73), V( 132), V(46), V(31) }, // Unopposed + { V( 1), V( 64), V( 143), V(26), V(13) }, + { V( 1), V( 47), V( 110), V(44), V(24) }, + { V( 0), V( 72), V( 127), V(50), V(31) } }, + { { V( 0), V( 0), V( 79), V(23), V( 1) }, // BlockedByPawn + { V( 0), V( 0), V( 148), V(27), V( 2) }, + { V( 0), V( 0), V( 161), V(16), V( 1) }, + { V( 0), V( 0), V( 171), V(22), V(15) } }, + { { V(22), V( 45), V( 104), V(62), V( 6) }, // Unblocked + { V(31), V( 30), V( 99), V(39), V(19) }, + { V(23), V( 29), V( 96), V(41), V(15) }, + { V(21), V( 23), V( 116), V(41), V(15) } } }; // Max bonus for king safety. Corresponds to start position with all the pawns @@ -90,22 +88,22 @@ namespace { template Score evaluate(const Position& pos, Pawns::Entry* e) { - const Color Them = (Us == WHITE ? BLACK : WHITE); - const Square Up = (Us == WHITE ? NORTH : SOUTH); - const Square Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); - const Square Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); + const Color Them = (Us == WHITE ? BLACK : WHITE); + const Direction Up = (Us == WHITE ? NORTH : SOUTH); + const Direction Right = (Us == WHITE ? NORTH_EAST : SOUTH_WEST); + const Direction Left = (Us == WHITE ? NORTH_WEST : SOUTH_EAST); Bitboard b, neighbours, stoppers, doubled, supported, phalanx; + Bitboard lever, leverPush; Square s; - bool opposed, lever, connected, backward; + bool opposed, backward; Score score = SCORE_ZERO; const Square* pl = pos.squares(Us); - const Bitboard* pawnAttacksBB = StepAttacksBB[make_piece(Us, PAWN)]; - Bitboard ourPawns = pos.pieces(Us , PAWN); + Bitboard ourPawns = pos.pieces( Us, PAWN); Bitboard theirPawns = pos.pieces(Them, PAWN); - e->passedPawns[Us] = e->pawnAttacksSpan[Us] = 0; + e->passedPawns[Us] = e->pawnAttacksSpan[Us] = e->weakUnopposed[Us] = 0; e->semiopenFiles[Us] = 0xFF; e->kingSquares[Us] = SQ_NONE; e->pawnAttacks[Us] = shift(ourPawns) | shift(ourPawns); @@ -123,14 +121,14 @@ namespace { e->pawnAttacksSpan[Us] |= pawn_attack_span(Us, s); // Flag the pawn - opposed = theirPawns & forward_bb(Us, s); + opposed = theirPawns & forward_file_bb(Us, s); stoppers = theirPawns & passed_pawn_mask(Us, s); - lever = theirPawns & pawnAttacksBB[s]; - doubled = ourPawns & (s + Up); + lever = theirPawns & PawnAttacks[Us][s]; + leverPush = theirPawns & PawnAttacks[Us][s + Up]; + doubled = ourPawns & (s - Up); neighbours = ourPawns & adjacent_files_bb(f); phalanx = neighbours & rank_bb(s); supported = neighbours & rank_bb(s - Up); - connected = supported | phalanx; // A pawn is backward when it is behind all pawns of the same color on the // adjacent files and cannot be safely advanced. @@ -146,32 +144,40 @@ namespace { // stopper on adjacent file which controls the way to that rank. backward = (b | shift(b & adjacent_files_bb(f))) & stoppers; - assert(!backward || !(pawn_attack_span(Them, s + Up) & neighbours)); + assert(!(backward && (forward_ranks_bb(Them, s + Up) & neighbours))); } // Passed pawns will be properly scored in evaluation because we need - // full attack info to evaluate them. - if (!stoppers && !(ourPawns & forward_bb(Us, s))) + // full attack info to evaluate them. Include also not passed pawns + // which could become passed after one or two pawn pushes when are + // not attacked more times than defended. + if ( !(stoppers ^ lever ^ leverPush) + && !(ourPawns & forward_file_bb(Us, s)) + && popcount(supported) >= popcount(lever) + && popcount(phalanx) >= popcount(leverPush)) e->passedPawns[Us] |= s; - // Score this pawn - if (!neighbours) - score -= Isolated[opposed]; + else if ( stoppers == SquareBB[s + Up] + && relative_rank(Us, s) >= RANK_5) + { + b = shift(supported) & ~theirPawns; + while (b) + if (!more_than_one(theirPawns & PawnAttacks[Us][pop_lsb(&b)])) + e->passedPawns[Us] |= s; + } - else if (backward) - score -= Backward[opposed]; + // Score this pawn + if (supported | phalanx) + score += Connected[opposed][bool(phalanx)][popcount(supported)][relative_rank(Us, s)]; - else if (!supported) - score -= Unsupported; + else if (!neighbours) + score -= Isolated, e->weakUnopposed[Us] += !opposed; - if (connected) - score += Connected[opposed][!!phalanx][more_than_one(supported)][relative_rank(Us, s)]; + else if (backward) + score -= Backward, e->weakUnopposed[Us] += !opposed; - if (doubled) + if (doubled && !supported) score -= Doubled; - - if (lever) - score += Lever[relative_rank(Us, s)]; } return score; @@ -187,16 +193,17 @@ namespace Pawns { void init() { - static const int Seed[RANK_NB] = { 0, 8, 19, 13, 71, 94, 169, 324 }; + static const int Seed[RANK_NB] = { 0, 13, 24, 18, 76, 100, 175, 330 }; for (int opposed = 0; opposed <= 1; ++opposed) for (int phalanx = 0; phalanx <= 1; ++phalanx) - for (int apex = 0; apex <= 1; ++apex) + for (int support = 0; support <= 2; ++support) for (Rank r = RANK_2; r < RANK_8; ++r) { - int v = (Seed[r] + (phalanx ? (Seed[r + 1] - Seed[r]) / 2 : 0)) >> opposed; - v += (apex ? v / 2 : 0); - Connected[opposed][phalanx][apex][r] = make_score(v, v * 5 / 8); + int v = 17 * support; + v += (Seed[r] + (phalanx ? (Seed[r + 1] - Seed[r]) / 2 : 0)) >> opposed; + + Connected[opposed][phalanx][support][r] = make_score(v, v * (r - 2) / 4); } } @@ -223,35 +230,36 @@ Entry* probe(const Position& pos) { /// Entry::shelter_storm() calculates shelter and storm penalties for the file -/// the king is on, as well as the two adjacent files. +/// the king is on, as well as the two closest files. template Value Entry::shelter_storm(const Position& pos, Square ksq) { const Color Them = (Us == WHITE ? BLACK : WHITE); - enum { NoFriendlyPawn, Unblocked, BlockedByPawn, BlockedByKing }; + enum { BlockedByKing, Unopposed, BlockedByPawn, Unblocked }; - Bitboard b = pos.pieces(PAWN) & (in_front_bb(Us, rank_of(ksq)) | rank_bb(ksq)); + Bitboard b = pos.pieces(PAWN) & (forward_ranks_bb(Us, ksq) | rank_bb(ksq)); Bitboard ourPawns = b & pos.pieces(Us); Bitboard theirPawns = b & pos.pieces(Them); Value safety = MaxSafetyBonus; File center = std::max(FILE_B, std::min(FILE_G, file_of(ksq))); - for (File f = center - File(1); f <= center + File(1); ++f) + for (File f = File(center - 1); f <= File(center + 1); ++f) { b = ourPawns & file_bb(f); Rank rkUs = b ? relative_rank(Us, backmost_sq(Us, b)) : RANK_1; - b = theirPawns & file_bb(f); + b = theirPawns & file_bb(f); Rank rkThem = b ? relative_rank(Us, frontmost_sq(Them, b)) : RANK_1; - safety -= ShelterWeakness[std::min(f, FILE_H - f)][rkUs] + int d = std::min(f, ~f); + safety -= ShelterWeakness[f == file_of(ksq)][d][rkUs] + StormDanger [f == file_of(ksq) && rkThem == relative_rank(Us, ksq) + 1 ? BlockedByKing : - rkUs == RANK_1 ? NoFriendlyPawn : + rkUs == RANK_1 ? Unopposed : rkThem == rkUs + 1 ? BlockedByPawn : Unblocked] - [std::min(f, FILE_H - f)][rkThem]; + [d][rkThem]; } return safety; diff --git a/Engines/Windows/stockfish/src/pawns.h b/Engines/Windows/stockfish/src/pawns.h index e26ae67..a9c21ff 100644 --- a/Engines/Windows/stockfish/src/pawns.h +++ b/Engines/Windows/stockfish/src/pawns.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -37,6 +37,7 @@ struct Entry { Bitboard pawn_attacks(Color c) const { return pawnAttacks[c]; } Bitboard passed_pawns(Color c) const { return passedPawns[c]; } Bitboard pawn_attacks_span(Color c) const { return pawnAttacksSpan[c]; } + int weak_unopposed(Color c) const { return weakUnopposed[c]; } int pawn_asymmetry() const { return asymmetry; } int open_files() const { return openFiles; } @@ -49,7 +50,7 @@ struct Entry { } int pawns_on_same_color_squares(Color c, Square s) const { - return pawnsOnSquares[c][!!(DarkSquares & s)]; + return pawnsOnSquares[c][bool(DarkSquares & s)]; } template @@ -71,6 +72,7 @@ struct Entry { Bitboard pawnAttacksSpan[COLOR_NB]; Square kingSquares[COLOR_NB]; Score kingSafety[COLOR_NB]; + int weakUnopposed[COLOR_NB]; int castlingRights[COLOR_NB]; int semiopenFiles[COLOR_NB]; int pawnsOnSquares[COLOR_NB][COLOR_NB]; // [color][light/dark squares] diff --git a/Engines/Windows/stockfish/src/position.cpp b/Engines/Windows/stockfish/src/position.cpp index c09a953..7e12fdc 100644 --- a/Engines/Windows/stockfish/src/position.cpp +++ b/Engines/Windows/stockfish/src/position.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,6 +32,7 @@ #include "thread.h" #include "tt.h" #include "uci.h" +#include "syzygy/tbprobe.h" using std::string; @@ -44,14 +45,17 @@ namespace Zobrist { Key psq[PIECE_NB][SQUARE_NB]; Key enpassant[FILE_NB]; Key castling[CASTLING_RIGHT_NB]; - Key side; + Key side, noPawns; } namespace { const string PieceToChar(" PNBRQK pnbrqk"); -// min_attacker() is a helper function used by see() to locate the least +const Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; + +// min_attacker() is a helper function used by see_ge() to locate the least // valuable attacker for the side to move, remove the attacker we just found // from the bitboards and scan for new X-ray attacks behind it. @@ -61,7 +65,7 @@ PieceType min_attacker(const Bitboard* bb, Square to, Bitboard stmAttackers, Bitboard b = stmAttackers & bb[Pt]; if (!b) - return min_attacker(bb, to, stmAttackers, occupied, attackers); + return min_attacker(bb, to, stmAttackers, occupied, attackers); occupied ^= b & ~(b - 1); @@ -98,11 +102,25 @@ std::ostream& operator<<(std::ostream& os, const Position& pos) { } os << "\nFen: " << pos.fen() << "\nKey: " << std::hex << std::uppercase - << std::setfill('0') << std::setw(16) << pos.key() << std::dec << "\nCheckers: "; + << std::setfill('0') << std::setw(16) << pos.key() + << std::setfill(' ') << std::dec << "\nCheckers: "; for (Bitboard b = pos.checkers(); b; ) os << UCI::square(pop_lsb(&b)) << " "; + if ( int(Tablebases::MaxCardinality) >= popcount(pos.pieces()) + && !pos.can_castle(ANY_CASTLING)) + { + StateInfo st; + Position p; + p.set(pos.fen(), pos.is_chess960(), &st, pos.this_thread()); + Tablebases::ProbeState s1, s2; + Tablebases::WDLScore wdl = Tablebases::probe_wdl(p, &s1); + int dtz = Tablebases::probe_dtz(p, &s2); + os << "\nTablebases WDL: " << std::setw(4) << wdl << " (" << s1 << ")" + << "\nTablebases DTZ: " << std::setw(4) << dtz << " (" << s2 << ")"; + } + return os; } @@ -133,6 +151,7 @@ void Position::init() { } Zobrist::side = rng.rand(); + Zobrist::noPawns = rng.rand(); } @@ -164,8 +183,9 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th 4) En passant target square (in algebraic notation). If there's no en passant target square, this is "-". If a pawn has just made a 2-square move, this - is the position "behind" the pawn. This is recorded regardless of whether - there is a pawn in position to make an en passant capture. + is the position "behind" the pawn. This is recorded only if there is a pawn + in position to make an en passant capture, and if there really is a pawn + that might have advanced two squares. 5) Halfmove clock. This is the number of halfmoves since the last pawn advance or capture. This is used to determine if a draw can be claimed under the @@ -191,10 +211,10 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th while ((ss >> token) && !isspace(token)) { if (isdigit(token)) - sq += Square(token - '0'); // Advance the given number of files + sq += (token - '0') * EAST; // Advance the given number of files else if (token == '/') - sq -= Square(16); + sq += 2 * SOUTH; else if ((idx = PieceToChar.find(token)) != string::npos) { @@ -242,7 +262,8 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th { st->epSquare = make_square(File(col - 'a'), Rank(row - '1')); - if (!(attackers_to(st->epSquare) & pieces(sideToMove, PAWN))) + if ( !(attackers_to(st->epSquare) & pieces(sideToMove, PAWN)) + || !(pieces(~sideToMove, PAWN) & (st->epSquare + pawn_push(~sideToMove)))) st->epSquare = SQ_NONE; } else @@ -251,7 +272,7 @@ Position& Position::set(const string& fenStr, bool isChess960, StateInfo* si, Th // 5-6. Halfmove clock and fullmove number ss >> std::skipws >> st->rule50 >> gamePly; - // Convert from fullmove starting from 1 to ply starting from 0, + // Convert from fullmove starting from 1 to gamePly starting from 0, // handle also common incorrect FEN with fullmove = 0. gamePly = std::max(2 * (gamePly - 1), 0) + (sideToMove == BLACK); @@ -317,7 +338,8 @@ void Position::set_check_info(StateInfo* si) const { void Position::set_state(StateInfo* si) const { - si->key = si->pawnKey = si->materialKey = 0; + si->key = si->materialKey = 0; + si->pawnKey = Zobrist::noPawns; si->nonPawnMaterial[WHITE] = si->nonPawnMaterial[BLACK] = VALUE_ZERO; si->psq = SCORE_ZERO; si->checkersBB = attackers_to(square(sideToMove)) & pieces(~sideToMove); @@ -357,6 +379,27 @@ void Position::set_state(StateInfo* si) const { } +/// Position::set() is an overload to initialize the position object with +/// the given endgame code string like "KBPKN". It is mainly a helper to +/// get the material key out of an endgame code. + +Position& Position::set(const string& code, Color c, StateInfo* si) { + + assert(code.length() > 0 && code.length() < 8); + assert(code[0] == 'K'); + + string sides[] = { code.substr(code.find('K', 1)), // Weak + code.substr(0, code.find('K', 1)) }; // Strong + + std::transform(sides[c].begin(), sides[c].end(), sides[c].begin(), tolower); + + string fenStr = "8/" + sides[0] + char(8 - sides[0].length() + '0') + "/8/8/8/8/" + + sides[1] + char(8 - sides[1].length() + '0') + "/8 w - - 0 10"; + + return set(fenStr, false, si, nullptr); +} + + /// Position::fen() returns a FEN representation of the position. In case of /// Chess960 the Shredder-FEN notation is used. This is mainly a debugging function. @@ -407,19 +450,6 @@ const string Position::fen() const { } -/// Position::game_phase() calculates the game phase interpolating total non-pawn -/// material between endgame and midgame limits. - -Phase Position::game_phase() const { - - Value npm = st->nonPawnMaterial[WHITE] + st->nonPawnMaterial[BLACK]; - - npm = std::max(EndgameLimit, std::min(npm, MidgameLimit)); - - return Phase(((npm - EndgameLimit) * PHASE_MIDGAME) / (MidgameLimit - EndgameLimit)); -} - - /// Position::slider_blockers() returns a bitboard of all the pieces (both colors) /// that are blocking attacks on the square 's' from 'sliders'. A piece blocks a /// slider if removing that piece from the board would result in a position where @@ -433,7 +463,7 @@ Bitboard Position::slider_blockers(Bitboard sliders, Square s, Bitboard& pinners pinners = 0; // Snipers are sliders that attack 's' when a piece is removed - Bitboard snipers = ( (PseudoAttacks[ROOK ][s] & pieces(QUEEN, ROOK)) + Bitboard snipers = ( (PseudoAttacks[ ROOK][s] & pieces(QUEEN, ROOK)) | (PseudoAttacks[BISHOP][s] & pieces(QUEEN, BISHOP))) & sliders; while (snipers) @@ -460,7 +490,7 @@ Bitboard Position::attackers_to(Square s, Bitboard occupied) const { return (attacks_from(s, BLACK) & pieces(WHITE, PAWN)) | (attacks_from(s, WHITE) & pieces(BLACK, PAWN)) | (attacks_from(s) & pieces(KNIGHT)) - | (attacks_bb(s, occupied) & pieces(ROOK, QUEEN)) + | (attacks_bb< ROOK>(s, occupied) & pieces( ROOK, QUEEN)) | (attacks_bb(s, occupied) & pieces(BISHOP, QUEEN)) | (attacks_from(s) & pieces(KING)); } @@ -554,7 +584,7 @@ bool Position::pseudo_legal(const Move m) const { && empty(to - pawn_push(us)))) return false; } - else if (!(attacks_from(pc, from) & to)) + else if (!(attacks_from(type_of(pc), from) & to)) return false; // Evasions generator already takes care to avoid some kind of illegal moves @@ -607,7 +637,7 @@ bool Position::gives_check(Move m) const { return false; case PROMOTION: - return attacks_bb(Piece(promotion_type(m)), to, pieces() ^ from) & square(~sideToMove); + return attacks_bb(promotion_type(m), to, pieces() ^ from) & square(~sideToMove); // En passant capture with check? We have already handled the case // of direct checks and ordinary discovered check, so the only case we @@ -647,7 +677,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { assert(is_ok(m)); assert(&newSt != st); - ++nodes; + thisThread->nodes.fetch_add(1, std::memory_order_relaxed); Key k = st->key ^ Zobrist::side; // Copy some fields of the old state to our new StateInfo object except the @@ -757,7 +787,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { if ( (int(to) ^ int(from)) == 16 && (attacks_from(to - pawn_push(us), us) & pieces(them, PAWN))) { - st->epSquare = (from + to) / 2; + st->epSquare = to - pawn_push(us); k ^= Zobrist::enpassant[file_of(st->epSquare)]; } @@ -786,7 +816,7 @@ void Position::do_move(Move m, StateInfo& newSt, bool givesCheck) { // Update pawn hash key and prefetch access to pawnsTable st->pawnKey ^= Zobrist::psq[pc][from] ^ Zobrist::psq[pc][to]; - prefetch(thisThread->pawnsTable[st->pawnKey]); + prefetch2(thisThread->pawnsTable[st->pawnKey]); // Reset rule 50 draw counter st->rule50 = 0; @@ -956,18 +986,16 @@ Key Position::key_after(Move m) const { /// Position::see_ge (Static Exchange Evaluation Greater or Equal) tests if the -/// SEE value of move is greater or equal to the given value. We'll use an +/// SEE value of move is greater or equal to the given threshold. We'll use an /// algorithm similar to alpha-beta pruning with a null window. -bool Position::see_ge(Move m, Value v) const { +bool Position::see_ge(Move m, Value threshold) const { assert(is_ok(m)); - // Castling moves are implemented as king capturing the rook so cannot be - // handled correctly. Simply assume the SEE value is VALUE_ZERO that is always - // correct unless in the rare case the rook ends up under attack. - if (type_of(m) == CASTLING) - return VALUE_ZERO >= v; + // Only deal with normal moves, assume others pass a simple see + if (type_of(m) != NORMAL) + return VALUE_ZERO >= threshold; Square from = from_sq(m), to = to_sq(m); PieceType nextVictim = type_of(piece_on(from)); @@ -975,30 +1003,22 @@ bool Position::see_ge(Move m, Value v) const { Value balance; // Values of the pieces taken by us minus opponent's ones Bitboard occupied, stmAttackers; - if (type_of(m) == ENPASSANT) - { - occupied = SquareBB[to - pawn_push(~stm)]; // Remove the captured pawn - balance = PieceValue[MG][PAWN]; - } - else - { - balance = PieceValue[MG][piece_on(to)]; - occupied = 0; - } + // The opponent may be able to recapture so this is the best result + // we can hope for. + balance = PieceValue[MG][piece_on(to)] - threshold; - if (balance < v) + if (balance < VALUE_ZERO) return false; - if (nextVictim == KING) - return true; - + // Now assume the worst possible result: that the opponent can + // capture our piece for free. balance -= PieceValue[MG][nextVictim]; - if (balance >= v) + if (balance >= VALUE_ZERO) // Always true if nextVictim == KING return true; - bool relativeStm = true; // True if the opponent is to move - occupied ^= pieces() ^ from ^ to; + bool opponentToMove = true; + occupied = pieces() ^ from ^ to; // Find all attackers to the destination square, with the moving piece removed, // but possibly an X-ray attacker added behind it. @@ -1006,6 +1026,12 @@ bool Position::see_ge(Move m, Value v) const { while (true) { + // The balance is negative only because we assumed we could win + // the last piece for free. We are truly winning only if we can + // win the last piece _cheaply enough_. Test if we can actually + // do this otherwise "give up". + assert(balance < VALUE_ZERO); + stmAttackers = attackers & pieces(stm); // Don't allow pinned pieces to attack pieces except the king as long all @@ -1013,43 +1039,68 @@ bool Position::see_ge(Move m, Value v) const { if (!(st->pinnersForKing[stm] & ~occupied)) stmAttackers &= ~st->blockersForKing[stm]; + // If we have no more attackers we must give up if (!stmAttackers) - return relativeStm; + break; // Locate and remove the next least valuable attacker nextVictim = min_attacker(byTypeBB, to, stmAttackers, occupied, attackers); if (nextVictim == KING) - return relativeStm == bool(attackers & pieces(~stm)); - - balance += relativeStm ? PieceValue[MG][nextVictim] - : -PieceValue[MG][nextVictim]; + { + // Our only attacker is the king. If the opponent still has + // attackers we must give up. Otherwise we make the move and + // (having no more attackers) the opponent must give up. + if (!(attackers & pieces(~stm))) + opponentToMove = !opponentToMove; + break; + } - relativeStm = !relativeStm; + // Assume the opponent can win the next piece for free and switch sides + balance += PieceValue[MG][nextVictim]; + opponentToMove = !opponentToMove; - if (relativeStm == (balance >= v)) - return relativeStm; + // If balance is negative after receiving a free piece then give up + if (balance < VALUE_ZERO) + break; + // Complete the process of switching sides. The first line swaps + // all negative numbers with non-negative numbers. The compiler + // probably knows that it is just the bitwise negation ~balance. + balance = -balance-1; stm = ~stm; } + + // If the opponent gave up we win, otherwise we lose. + return opponentToMove; } /// Position::is_draw() tests whether the position is drawn by 50-move rule /// or by repetition. It does not detect stalemates. -bool Position::is_draw() const { +bool Position::is_draw(int ply) const { if (st->rule50 > 99 && (!checkers() || MoveList(*this).size())) return true; - StateInfo* stp = st; - for (int i = 2, e = std::min(st->rule50, st->pliesFromNull); i <= e; i += 2) + int end = std::min(st->rule50, st->pliesFromNull); + + if (end < 4) + return false; + + StateInfo* stp = st->previous->previous; + int cnt = 0; + + for (int i = 4; i <= end; i += 2) { stp = stp->previous->previous; - if (stp->key == st->key) - return true; // Draw at first repetition + // Return a draw score if a position repeats once earlier but strictly + // after the root, or repeats twice before or at the root. + if ( stp->key == st->key + && ++cnt + (ply > i) == 2) + return true; } return false; @@ -1091,78 +1142,72 @@ void Position::flip() { } -/// Position::pos_is_ok() performs some consistency checks for the position object. +/// Position::pos_is_ok() performs some consistency checks for the +/// position object and raises an asserts if something wrong is detected. /// This is meant to be helpful when debugging. -bool Position::pos_is_ok(int* failedStep) const { +bool Position::pos_is_ok() const { const bool Fast = true; // Quick (default) or full check? - enum { Default, King, Bitboards, State, Lists, Castling }; + if ( (sideToMove != WHITE && sideToMove != BLACK) + || piece_on(square(WHITE)) != W_KING + || piece_on(square(BLACK)) != B_KING + || ( ep_square() != SQ_NONE + && relative_rank(sideToMove, ep_square()) != RANK_6)) + assert(0 && "pos_is_ok: Default"); - for (int step = Default; step <= (Fast ? Default : Castling); step++) - { - if (failedStep) - *failedStep = step; - - if (step == Default) - if ( (sideToMove != WHITE && sideToMove != BLACK) - || piece_on(square(WHITE)) != W_KING - || piece_on(square(BLACK)) != B_KING - || ( ep_square() != SQ_NONE - && relative_rank(sideToMove, ep_square()) != RANK_6)) - return false; + if (Fast) + return true; - if (step == King) - if ( std::count(board, board + SQUARE_NB, W_KING) != 1 - || std::count(board, board + SQUARE_NB, B_KING) != 1 - || attackers_to(square(~sideToMove)) & pieces(sideToMove)) - return false; + if ( pieceCount[W_KING] != 1 + || pieceCount[B_KING] != 1 + || attackers_to(square(~sideToMove)) & pieces(sideToMove)) + assert(0 && "pos_is_ok: Kings"); - if (step == Bitboards) - { - if ( (pieces(WHITE) & pieces(BLACK)) - ||(pieces(WHITE) | pieces(BLACK)) != pieces()) - return false; + if ( (pieces(PAWN) & (Rank1BB | Rank8BB)) + || pieceCount[W_PAWN] > 8 + || pieceCount[B_PAWN] > 8) + assert(0 && "pos_is_ok: Pawns"); - for (PieceType p1 = PAWN; p1 <= KING; ++p1) - for (PieceType p2 = PAWN; p2 <= KING; ++p2) - if (p1 != p2 && (pieces(p1) & pieces(p2))) - return false; - } + if ( (pieces(WHITE) & pieces(BLACK)) + || (pieces(WHITE) | pieces(BLACK)) != pieces() + || popcount(pieces(WHITE)) > 16 + || popcount(pieces(BLACK)) > 16) + assert(0 && "pos_is_ok: Bitboards"); - if (step == State) - { - StateInfo si = *st; - set_state(&si); - if (std::memcmp(&si, st, sizeof(StateInfo))) - return false; - } + for (PieceType p1 = PAWN; p1 <= KING; ++p1) + for (PieceType p2 = PAWN; p2 <= KING; ++p2) + if (p1 != p2 && (pieces(p1) & pieces(p2))) + assert(0 && "pos_is_ok: Bitboards"); - if (step == Lists) - for (Piece pc : Pieces) - { - if (pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc)))) - return false; + StateInfo si = *st; + set_state(&si); + if (std::memcmp(&si, st, sizeof(StateInfo))) + assert(0 && "pos_is_ok: State"); - for (int i = 0; i < pieceCount[pc]; ++i) - if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i) - return false; - } + for (Piece pc : Pieces) + { + if ( pieceCount[pc] != popcount(pieces(color_of(pc), type_of(pc))) + || pieceCount[pc] != std::count(board, board + SQUARE_NB, pc)) + assert(0 && "pos_is_ok: Pieces"); - if (step == Castling) - for (Color c = WHITE; c <= BLACK; ++c) - for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1)) - { - if (!can_castle(c | s)) - continue; - - if ( piece_on(castlingRookSquare[c | s]) != make_piece(c, ROOK) - || castlingRightsMask[castlingRookSquare[c | s]] != (c | s) - ||(castlingRightsMask[square(c)] & (c | s)) != (c | s)) - return false; - } + for (int i = 0; i < pieceCount[pc]; ++i) + if (board[pieceList[pc][i]] != pc || index[pieceList[pc][i]] != i) + assert(0 && "pos_is_ok: Index"); } + for (Color c = WHITE; c <= BLACK; ++c) + for (CastlingSide s = KING_SIDE; s <= QUEEN_SIDE; s = CastlingSide(s + 1)) + { + if (!can_castle(c | s)) + continue; + + if ( piece_on(castlingRookSquare[c | s]) != make_piece(c, ROOK) + || castlingRightsMask[castlingRookSquare[c | s]] != (c | s) + || (castlingRightsMask[square(c)] & (c | s)) != (c | s)) + assert(0 && "pos_is_ok: Castling"); + } + return true; } diff --git a/Engines/Windows/stockfish/src/position.h b/Engines/Windows/stockfish/src/position.h index 9aa4c44..34e2f7e 100644 --- a/Engines/Windows/stockfish/src/position.h +++ b/Engines/Windows/stockfish/src/position.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -56,7 +56,10 @@ struct StateInfo { Bitboard checkSquares[PIECE_TYPE_NB]; }; -// In a std::deque references to elements are unaffected upon resizing +/// A list to keep track of the position states along the setup moves (from the +/// start position to the position just before the search starts). Needed by +/// 'draw by repetition' detection. Use a std::deque because pointers to +/// elements are not invalidated upon list resizing. typedef std::unique_ptr> StateListPtr; @@ -76,6 +79,7 @@ class Position { // FEN string input/output Position& set(const std::string& fenStr, bool isChess960, StateInfo* si, Thread* th); + Position& set(const std::string& code, Color c, StateInfo* si); const std::string fen() const; // Position representation @@ -89,6 +93,7 @@ class Position { Square ep_square() const; bool empty(Square s) const; template int count(Color c) const; + template int count() const; template const Square* squares(Color c) const; template Square square(Color c) const; @@ -107,7 +112,7 @@ class Position { // Attacks to/from a given square Bitboard attackers_to(Square s) const; Bitboard attackers_to(Square s, Bitboard occupied) const; - Bitboard attacks_from(Piece pc, Square s) const; + Bitboard attacks_from(PieceType pt, Square s) const; template Bitboard attacks_from(Square s) const; template Bitboard attacks_from(Square s, Color c) const; Bitboard slider_blockers(Bitboard sliders, Square s, Bitboard& pinners) const; @@ -127,13 +132,14 @@ class Position { bool opposite_bishops() const; // Doing and undoing moves - void do_move(Move m, StateInfo& st, bool givesCheck); + void do_move(Move m, StateInfo& newSt); + void do_move(Move m, StateInfo& newSt, bool givesCheck); void undo_move(Move m); - void do_null_move(StateInfo& st); + void do_null_move(StateInfo& newSt); void undo_null_move(); // Static Exchange Evaluation - bool see_ge(Move m, Value value) const; + bool see_ge(Move m, Value threshold = VALUE_ZERO) const; // Accessing hash keys Key key() const; @@ -143,18 +149,17 @@ class Position { // Other properties of the position Color side_to_move() const; - Phase game_phase() const; int game_ply() const; bool is_chess960() const; Thread* this_thread() const; - uint64_t nodes_searched() const; - bool is_draw() const; + bool is_draw(int ply) const; int rule50_count() const; Score psq_score() const; Value non_pawn_material(Color c) const; + Value non_pawn_material() const; // Position consistency check, for debugging - bool pos_is_ok(int* failedStep = nullptr) const; + bool pos_is_ok() const; void flip(); private: @@ -180,7 +185,6 @@ class Position { int castlingRightsMask[SQUARE_NB]; Square castlingRookSquare[CASTLING_RIGHT_NB]; Bitboard castlingPath[CASTLING_RIGHT_NB]; - uint64_t nodes; int gamePly; Color sideToMove; Thread* thisThread; @@ -234,6 +238,10 @@ template inline int Position::count(Color c) const { return pieceCount[make_piece(c, Pt)]; } +template inline int Position::count() const { + return pieceCount[make_piece(WHITE, Pt)] + pieceCount[make_piece(BLACK, Pt)]; +} + template inline const Square* Position::squares(Color c) const { return pieceList[make_piece(c, Pt)]; } @@ -265,18 +273,19 @@ inline Square Position::castling_rook_square(CastlingRight cr) const { template inline Bitboard Position::attacks_from(Square s) const { + assert(Pt != PAWN); return Pt == BISHOP || Pt == ROOK ? attacks_bb(s, byTypeBB[ALL_PIECES]) : Pt == QUEEN ? attacks_from(s) | attacks_from(s) - : StepAttacksBB[Pt][s]; + : PseudoAttacks[Pt][s]; } template<> inline Bitboard Position::attacks_from(Square s, Color c) const { - return StepAttacksBB[make_piece(c, PAWN)][s]; + return PawnAttacks[c][s]; } -inline Bitboard Position::attacks_from(Piece pc, Square s) const { - return attacks_bb(pc, s, byTypeBB[ALL_PIECES]); +inline Bitboard Position::attacks_from(PieceType pt, Square s) const { + return attacks_bb(pt, s, byTypeBB[ALL_PIECES]); } inline Bitboard Position::attackers_to(Square s) const { @@ -328,6 +337,10 @@ inline Value Position::non_pawn_material(Color c) const { return st->nonPawnMaterial[c]; } +inline Value Position::non_pawn_material() const { + return st->nonPawnMaterial[WHITE] + st->nonPawnMaterial[BLACK]; +} + inline int Position::game_ply() const { return gamePly; } @@ -336,10 +349,6 @@ inline int Position::rule50_count() const { return st->rule50; } -inline uint64_t Position::nodes_searched() const { - return nodes; -} - inline bool Position::opposite_bishops() const { return pieceCount[W_BISHOP] == 1 && pieceCount[B_BISHOP] == 1 @@ -411,4 +420,8 @@ inline void Position::move_piece(Piece pc, Square from, Square to) { pieceList[pc][index[to]] = to; } +inline void Position::do_move(Move m, StateInfo& newSt) { + do_move(m, newSt, gives_check(m)); +} + #endif // #ifndef POSITION_H_INCLUDED diff --git a/Engines/Windows/stockfish/src/psqt.cpp b/Engines/Windows/stockfish/src/psqt.cpp index 41a9b49..3f447dc 100644 --- a/Engines/Windows/stockfish/src/psqt.cpp +++ b/Engines/Windows/stockfish/src/psqt.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,32 +39,32 @@ const Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { { }, { // Pawn { S( 0, 0), S( 0, 0), S( 0, 0), S( 0, 0) }, - { S(-16, 7), S( 1,-4), S( 7, 8), S( 3,-2) }, - { S(-23,-4), S( -7,-5), S( 19, 5), S(24, 4) }, - { S(-22, 3), S(-14, 3), S( 20,-8), S(35,-3) }, - { S(-11, 8), S( 0, 9), S( 3, 7), S(21,-6) }, - { S(-11, 8), S(-13,-5), S( -6, 2), S(-2, 4) }, - { S( -9, 3), S( 15,-9), S( -8, 1), S(-4,18) } + { S(-11, 7), S( 6,-4), S( 7, 8), S( 3,-2) }, + { S(-18,-4), S( -2,-5), S( 19, 5), S(24, 4) }, + { S(-17, 3), S( -9, 3), S( 20,-8), S(35,-3) }, + { S( -6, 8), S( 5, 9), S( 3, 7), S(21,-6) }, + { S( -6, 8), S( -8,-5), S( -6, 2), S(-2, 4) }, + { S( -4, 3), S( 20,-9), S( -8, 1), S(-4,18) } }, { // Knight - { S(-143, -97), S(-96,-82), S(-80,-46), S(-73,-14) }, - { S( -83, -69), S(-43,-55), S(-21,-17), S(-10, 9) }, - { S( -71, -50), S(-22,-39), S( 0, -8), S( 9, 28) }, - { S( -25, -41), S( 18,-25), S( 43, 7), S( 47, 38) }, - { S( -26, -46), S( 16,-25), S( 38, 2), S( 50, 41) }, - { S( -11, -55), S( 37,-38), S( 56, -8), S( 71, 27) }, - { S( -62, -64), S(-17,-50), S( 5,-24), S( 14, 13) }, - { S(-195,-110), S(-66,-90), S(-42,-50), S(-29,-13) } + { S(-161,-105), S(-96,-82), S(-80,-46), S(-73,-14) }, + { S( -83, -69), S(-43,-54), S(-21,-17), S(-10, 9) }, + { S( -71, -50), S(-22,-39), S( 0, -7), S( 9, 28) }, + { S( -25, -41), S( 18,-25), S( 43, 6), S( 47, 38) }, + { S( -26, -46), S( 16,-25), S( 38, 3), S( 50, 40) }, + { S( -11, -54), S( 37,-38), S( 56, -7), S( 65, 27) }, + { S( -63, -65), S(-19,-50), S( 5,-24), S( 14, 13) }, + { S(-195,-109), S(-67,-89), S(-42,-50), S(-29,-13) } }, { // Bishop - { S(-54,-68), S(-23,-40), S(-35,-46), S(-44,-28) }, - { S(-30,-43), S( 10,-17), S( 2,-23), S( -9, -5) }, - { S(-19,-32), S( 17, -9), S( 11,-13), S( 1, 8) }, - { S(-21,-36), S( 18,-13), S( 11,-15), S( 0, 7) }, - { S(-21,-36), S( 14,-14), S( 6,-17), S( -1, 3) }, - { S(-27,-35), S( 6,-13), S( 2,-10), S( -8, 1) }, - { S(-33,-44), S( 7,-21), S( -4,-22), S(-12, -4) }, - { S(-45,-65), S(-21,-42), S(-29,-46), S(-39,-27) } + { S(-44,-58), S(-13,-31), S(-25,-37), S(-34,-19) }, + { S(-20,-34), S( 20, -9), S( 12,-14), S( 1, 4) }, + { S( -9,-23), S( 27, 0), S( 21, -3), S( 11, 16) }, + { S(-11,-26), S( 28, -3), S( 21, -5), S( 10, 16) }, + { S(-11,-26), S( 27, -4), S( 16, -7), S( 9, 14) }, + { S(-17,-24), S( 16, -2), S( 12, 0), S( 2, 13) }, + { S(-23,-34), S( 17,-10), S( 6,-12), S( -2, 6) }, + { S(-35,-55), S(-11,-32), S(-19,-36), S(-29,-17) } }, { // Rook { S(-25, 0), S(-16, 0), S(-16, 0), S(-9, 0) }, @@ -77,24 +77,24 @@ const Score Bonus[][RANK_NB][int(FILE_NB) / 2] = { { S(-23, 0), S(-15, 0), S(-11, 0), S(-5, 0) } }, { // Queen - { S( 0,-70), S(-3,-57), S(-4,-41), S(-1,-29) }, - { S(-4,-58), S( 6,-30), S( 9,-21), S( 8, -4) }, - { S(-2,-39), S( 6,-17), S( 9, -7), S( 9, 5) }, - { S(-1,-29), S( 8, -5), S(10, 9), S( 7, 17) }, - { S(-3,-27), S( 9, -5), S( 8, 10), S( 7, 23) }, - { S(-2,-40), S( 6,-16), S( 8,-11), S(10, 3) }, - { S(-2,-54), S( 7,-30), S( 7,-21), S( 6, -7) }, - { S(-1,-75), S(-4,-54), S(-1,-44), S( 0,-30) } + { S( 0,-71), S(-4,-56), S(-3,-42), S(-1,-29) }, + { S(-4,-56), S( 6,-30), S( 9,-21), S( 8, -5) }, + { S(-2,-39), S( 6,-17), S( 9, -8), S( 9, 5) }, + { S(-1,-29), S( 8, -5), S(10, 9), S( 7, 19) }, + { S(-3,-27), S( 9, -5), S( 8, 10), S( 7, 21) }, + { S(-2,-40), S( 6,-16), S( 8,-10), S(10, 3) }, + { S(-2,-55), S( 7,-30), S( 7,-21), S( 6, -6) }, + { S(-1,-74), S(-4,-55), S(-1,-43), S( 0,-30) } }, { // King - { S(291, 28), S(344, 76), S(294,103), S(219,112) }, - { S(289, 70), S(329,119), S(263,170), S(205,159) }, - { S(226,109), S(271,164), S(202,195), S(136,191) }, - { S(204,131), S(212,194), S(175,194), S(137,204) }, - { S(177,132), S(205,187), S(143,224), S( 94,227) }, - { S(147,118), S(188,178), S(113,199), S( 70,197) }, - { S(116, 72), S(158,121), S( 93,142), S( 48,161) }, - { S( 94, 30), S(120, 76), S( 78,101), S( 31,111) } + { S(267, 0), S(320, 48), S(270, 75), S(195, 84) }, + { S(264, 43), S(304, 92), S(238,143), S(180,132) }, + { S(200, 83), S(245,138), S(176,167), S(110,165) }, + { S(177,106), S(185,169), S(148,169), S(110,179) }, + { S(149,108), S(177,163), S(115,200), S( 66,203) }, + { S(118, 95), S(159,155), S( 84,176), S( 41,174) }, + { S( 87, 50), S(128, 99), S( 63,122), S( 20,139) }, + { S( 63, 9), S( 88, 55), S( 47, 80), S( 0, 90) } } }; @@ -116,7 +116,7 @@ void init() { for (Square s = SQ_A1; s <= SQ_H8; ++s) { - File f = std::min(file_of(s), FILE_H - file_of(s)); + File f = std::min(file_of(s), ~file_of(s)); psq[ pc][ s] = v + Bonus[pc][rank_of(s)][f]; psq[~pc][~s] = -psq[pc][s]; } diff --git a/Engines/Windows/stockfish/src/search.cpp b/Engines/Windows/stockfish/src/search.cpp index 19749b6..b9ce9a6 100644 --- a/Engines/Windows/stockfish/src/search.cpp +++ b/Engines/Windows/stockfish/src/search.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,7 +39,6 @@ namespace Search { - SignalsType Signals; LimitsType Limits; } @@ -63,8 +62,12 @@ namespace { // Different node types, used as a template parameter enum NodeType { NonPV, PV }; + // Sizes and phases of the skip-blocks, used for distributing search depths across the threads + const int skipSize[] = { 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4 }; + const int skipPhase[] = { 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6, 7 }; + // Razoring and futility margin based on depth - const int razor_margin[4] = { 483, 570, 603, 554 }; + const int razor_margin = 600; Value futility_margin(Depth d) { return Value(150 * d / ONE_PLY); } // Futility and reductions lookup tables, initialized at startup @@ -75,101 +78,62 @@ namespace { return Reductions[PvNode][i][std::min(d / ONE_PLY, 63)][std::min(mn, 63)] * ONE_PLY; } + // History and stats update bonus, based on depth + int stat_bonus(Depth depth) { + int d = depth / ONE_PLY; + return d > 17 ? 0 : d * d + 2 * d - 2; + } + // Skill structure is used to implement strength limit struct Skill { - Skill(int l) : level(l) {} + explicit Skill(int l) : level(l) {} bool enabled() const { return level < 20; } bool time_to_pick(Depth depth) const { return depth / ONE_PLY == 1 + level; } - Move best_move(size_t multiPV) { return best ? best : pick_best(multiPV); } Move pick_best(size_t multiPV); int level; Move best = MOVE_NONE; }; - // EasyMoveManager structure is used to detect an 'easy move'. When the PV is - // stable across multiple search iterations, we can quickly return the best move. - struct EasyMoveManager { - - void clear() { - stableCnt = 0; - expectedPosKey = 0; - pv[0] = pv[1] = pv[2] = MOVE_NONE; - } - - Move get(Key key) const { - return expectedPosKey == key ? pv[2] : MOVE_NONE; - } - - void update(Position& pos, const std::vector& newPv) { - - assert(newPv.size() >= 3); - - // Keep track of how many times in a row the 3rd ply remains stable - stableCnt = (newPv[2] == pv[2]) ? stableCnt + 1 : 0; - - if (!std::equal(newPv.begin(), newPv.begin() + 3, pv)) - { - std::copy(newPv.begin(), newPv.begin() + 3, pv); - - StateInfo st[2]; - pos.do_move(newPv[0], st[0], pos.gives_check(newPv[0])); - pos.do_move(newPv[1], st[1], pos.gives_check(newPv[1])); - expectedPosKey = pos.key(); - pos.undo_move(newPv[1]); - pos.undo_move(newPv[0]); - } - } - - int stableCnt; - Key expectedPosKey; - Move pv[3]; - }; - - // Set of rows with half bits set to 1 and half to 0. It is used to allocate - // the search depths across the threads. - typedef std::vector Row; - - const Row HalfDensity[] = { - {0, 1}, - {1, 0}, - {0, 0, 1, 1}, - {0, 1, 1, 0}, - {1, 1, 0, 0}, - {1, 0, 0, 1}, - {0, 0, 0, 1, 1, 1}, - {0, 0, 1, 1, 1, 0}, - {0, 1, 1, 1, 0, 0}, - {1, 1, 1, 0, 0, 0}, - {1, 1, 0, 0, 0, 1}, - {1, 0, 0, 0, 1, 1}, - {0, 0, 0, 0, 1, 1, 1, 1}, - {0, 0, 0, 1, 1, 1, 1, 0}, - {0, 0, 1, 1, 1, 1, 0 ,0}, - {0, 1, 1, 1, 1, 0, 0 ,0}, - {1, 1, 1, 1, 0, 0, 0 ,0}, - {1, 1, 1, 0, 0, 0, 0 ,1}, - {1, 1, 0, 0, 0, 0, 1 ,1}, - {1, 0, 0, 0, 0, 1, 1 ,1}, - }; - - const size_t HalfDensitySize = std::extent::value; - - EasyMoveManager EasyMove; - Value DrawValue[COLOR_NB]; - template - Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode); + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning); template - Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth); + Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth = DEPTH_ZERO); Value value_to_tt(Value v, int ply); Value value_from_tt(Value v, int ply); void update_pv(Move* pv, Move move, Move* childPv); - void update_cm_stats(Stack* ss, Piece pc, Square s, Value bonus); - void update_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietsCnt, Value bonus); - void check_time(); + void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus); + void update_stats(const Position& pos, Stack* ss, Move move, Move* quiets, int quietsCnt, int bonus); + void update_capture_stats(const Position& pos, Move move, Move* captures, int captureCnt, int bonus); + bool pv_is_draw(Position& pos); + + // perft() is our utility to verify move generation. All the leaf nodes up + // to the given depth are generated and counted, and the sum is returned. + template + uint64_t perft(Position& pos, Depth depth) { + + StateInfo st; + uint64_t cnt, nodes = 0; + const bool leaf = (depth == 2 * ONE_PLY); + + for (const auto& m : MoveList(pos)) + { + if (Root && depth <= ONE_PLY) + cnt = 1, nodes++; + else + { + pos.do_move(m, st); + cnt = leaf ? MoveList(pos).size() : perft(pos, depth - ONE_PLY); + nodes += cnt; + pos.undo_move(m); + } + if (Root) + sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; + } + return nodes; + } } // namespace @@ -182,9 +146,7 @@ void Search::init() { for (int d = 1; d < 64; ++d) for (int mc = 1; mc < 64; ++mc) { - double r = log(d) * log(mc) / 2; - if (r < 0.80) - continue; + double r = log(d) * log(mc) / 1.95; Reductions[NonPV][imp][d][mc] = int(std::round(r)); Reductions[PV][imp][d][mc] = std::max(Reductions[NonPV][imp][d][mc] - 1, 0); @@ -196,74 +158,48 @@ void Search::init() { for (int d = 0; d < 16; ++d) { - FutilityMoveCounts[0][d] = int(2.4 + 0.773 * pow(d + 0.00, 1.8)); - FutilityMoveCounts[1][d] = int(2.9 + 1.045 * pow(d + 0.49, 1.8)); + FutilityMoveCounts[0][d] = int(2.4 + 0.74 * pow(d, 1.78)); + FutilityMoveCounts[1][d] = int(5.0 + 1.00 * pow(d, 2.00)); } } -/// Search::clear() resets search state to zero, to obtain reproducible results +/// Search::clear() resets search state to its initial value void Search::clear() { - TT.clear(); - - for (Thread* th : Threads) - { - th->history.clear(); - th->counterMoves.clear(); - th->fromTo.clear(); - th->counterMoveHistory.clear(); - } - - Threads.main()->previousScore = VALUE_INFINITE; -} - - -/// Search::perft() is our utility to verify move generation. All the leaf nodes -/// up to the given depth are generated and counted, and the sum is returned. -template -uint64_t Search::perft(Position& pos, Depth depth) { - - StateInfo st; - uint64_t cnt, nodes = 0; - const bool leaf = (depth == 2 * ONE_PLY); + Threads.main()->wait_for_search_finished(); - for (const auto& m : MoveList(pos)) - { - if (Root && depth <= ONE_PLY) - cnt = 1, nodes++; - else - { - pos.do_move(m, st, pos.gives_check(m)); - cnt = leaf ? MoveList(pos).size() : perft(pos, depth - ONE_PLY); - nodes += cnt; - pos.undo_move(m); - } - if (Root) - sync_cout << UCI::move(m, pos.is_chess960()) << ": " << cnt << sync_endl; - } - return nodes; + Time.availableNodes = 0; + TT.clear(); + Threads.clear(); } -template uint64_t Search::perft(Position&, Depth); - /// MainThread::search() is called by the main thread when the program receives /// the UCI 'go' command. It searches from the root position and outputs the "bestmove". void MainThread::search() { + if (Limits.perft) + { + nodes = perft(rootPos, Limits.perft * ONE_PLY); + sync_cout << "\nNodes searched: " << nodes << "\n" << sync_endl; + return; + } + Color us = rootPos.side_to_move(); Time.init(Limits, us, rootPos.game_ply()); + TT.new_search(); int contempt = Options["Contempt"] * PawnValueEg / 100; // From centipawns - DrawValue[ us] = VALUE_DRAW - Value(contempt); - DrawValue[~us] = VALUE_DRAW + Value(contempt); + + Eval::Contempt = (us == WHITE ? make_score(contempt, contempt / 2) + : -make_score(contempt, contempt / 2)); if (rootMoves.empty()) { - rootMoves.push_back(RootMove(MOVE_NONE)); + rootMoves.emplace_back(MOVE_NONE); sync_cout << "info depth 0 score " << UCI::value(rootPos.checkers() ? -VALUE_MATE : VALUE_DRAW) << sync_endl; @@ -277,42 +213,47 @@ void MainThread::search() { Thread::search(); // Let's start searching! } - // When playing in 'nodes as time' mode, subtract the searched nodes from - // the available ones before exiting. - if (Limits.npmsec) - Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); - // When we reach the maximum depth, we can arrive here without a raise of - // Signals.stop. However, if we are pondering or in an infinite search, + // Threads.stop. However, if we are pondering or in an infinite search, // the UCI protocol states that we shouldn't print the best move before the // GUI sends a "stop" or "ponderhit" command. We therefore simply wait here - // until the GUI sends one of those commands (which also raises Signals.stop). - if (!Signals.stop && (Limits.ponder || Limits.infinite)) - { - Signals.stopOnPonderhit = true; - wait(Signals.stop); - } + // until the GUI sends one of those commands (which also raises Threads.stop). + Threads.stopOnPonderhit = true; + + while (!Threads.stop && (Threads.ponder || Limits.infinite)) + {} // Busy wait for a stop or a ponder reset - // Stop the threads if not already stopped - Signals.stop = true; + // Stop the threads if not already stopped (also raise the stop if + // "ponderhit" just reset Threads.ponder). + Threads.stop = true; // Wait until all threads have finished for (Thread* th : Threads) if (th != this) th->wait_for_search_finished(); + // When playing in 'nodes as time' mode, subtract the searched nodes from + // the available ones before exiting. + if (Limits.npmsec) + Time.availableNodes += Limits.inc[us] - Threads.nodes_searched(); + // Check if there are threads with a better score than main thread Thread* bestThread = this; - if ( !this->easyMovePlayed - && Options["MultiPV"] == 1 + if ( Options["MultiPV"] == 1 && !Limits.depth && !Skill(Options["Skill Level"]).enabled() && rootMoves[0].pv[0] != MOVE_NONE) { for (Thread* th : Threads) - if ( th->completedDepth > bestThread->completedDepth - && th->rootMoves[0].score > bestThread->rootMoves[0].score) + { + Depth depthDiff = th->completedDepth - bestThread->completedDepth; + Value scoreDiff = th->rootMoves[0].score - bestThread->rootMoves[0].score; + + // Select the thread with the best score, always if it is a mate + if ( scoreDiff > 0 + && (depthDiff >= 0 || th->rootMoves[0].score >= VALUE_MATE_IN_MAX_PLY)) bestThread = th; + } } previousScore = bestThread->rootMoves[0].score; @@ -330,30 +271,30 @@ void MainThread::search() { } -// Thread::search() is the main iterative deepening loop. It calls search() -// repeatedly with increasing depth until the allocated thinking time has been -// consumed, the user stops the search, or the maximum search depth is reached. +/// Thread::search() is the main iterative deepening loop. It calls search() +/// repeatedly with increasing depth until the allocated thinking time has been +/// consumed, the user stops the search, or the maximum search depth is reached. void Thread::search() { - Stack stack[MAX_PLY+7], *ss = stack+5; // To allow referencing (ss-5) and (ss+2) + Stack stack[MAX_PLY+7], *ss = stack+4; // To reference from (ss-4) to (ss+2) Value bestValue, alpha, beta, delta; - Move easyMove = MOVE_NONE; + Move lastBestMove = MOVE_NONE; + Depth lastBestMoveDepth = DEPTH_ZERO; MainThread* mainThread = (this == Threads.main() ? Threads.main() : nullptr); + double timeReduction = 1.0; - std::memset(ss-5, 0, 8 * sizeof(Stack)); + std::memset(ss-4, 0, 7 * sizeof(Stack)); + for (int i = 4; i > 0; i--) + (ss-i)->contHistory = &this->contHistory[NO_PIECE][0]; // Use as sentinel bestValue = delta = alpha = -VALUE_INFINITE; beta = VALUE_INFINITE; - completedDepth = DEPTH_ZERO; if (mainThread) { - easyMove = EasyMove.get(rootPos.key()); - EasyMove.clear(); - mainThread->easyMovePlayed = mainThread->failedLow = false; + mainThread->failedLow = false; mainThread->bestMoveChanges = 0; - TT.new_search(); } size_t multiPV = Options["MultiPV"]; @@ -368,16 +309,15 @@ void Thread::search() { // Iterative deepening loop until requested to stop or the target depth is reached while ( (rootDepth += ONE_PLY) < DEPTH_MAX - && !Signals.stop - && (!Limits.depth || Threads.main()->rootDepth / ONE_PLY <= Limits.depth)) + && !Threads.stop + && !(Limits.depth && mainThread && rootDepth / ONE_PLY > Limits.depth)) { - // Set up the new depths for the helper threads skipping on average every - // 2nd ply (using a half-density matrix). - if (!mainThread) + // Distribute search depths across the threads + if (idx) { - const Row& row = HalfDensity[(idx - 1) % HalfDensitySize]; - if (row[(rootDepth / ONE_PLY + rootPos.game_ply()) % row.size()]) - continue; + int i = (idx - 1) % 20; + if (((rootDepth / ONE_PLY + rootPos.game_ply() + skipPhase[i]) / skipSize[i]) % 2) + continue; } // Age out PV variability metric @@ -390,8 +330,11 @@ void Thread::search() { rm.previousScore = rm.score; // MultiPV loop. We perform a full root search for each PV line - for (PVIdx = 0; PVIdx < multiPV && !Signals.stop; ++PVIdx) + for (PVIdx = 0; PVIdx < multiPV && !Threads.stop; ++PVIdx) { + // Reset UCI info selDepth for each depth and each PV line + selDepth = 0; + // Reset aspiration window starting size if (rootDepth >= 5 * ONE_PLY) { @@ -405,7 +348,7 @@ void Thread::search() { // high/low anymore. while (true) { - bestValue = ::search(rootPos, ss, alpha, beta, rootDepth, false); + bestValue = ::search(rootPos, ss, alpha, beta, rootDepth, false, false); // Bring the best move to the front. It is critical that sorting // is done with a stable algorithm because all the values but the @@ -415,10 +358,10 @@ void Thread::search() { // search the already searched PV lines are preserved. std::stable_sort(rootMoves.begin() + PVIdx, rootMoves.end()); - // If search has been stopped, break immediately. Sorting and + // If search has been stopped, we break immediately. Sorting and // writing PV back to TT is safe because RootMoves is still // valid, although it refers to the previous iteration. - if (Signals.stop) + if (Threads.stop) break; // When failing high/low give some update (without cluttering @@ -439,14 +382,11 @@ void Thread::search() { if (mainThread) { mainThread->failedLow = true; - Signals.stopOnPonderhit = false; + Threads.stopOnPonderhit = false; } } else if (bestValue >= beta) - { - alpha = (alpha + beta) / 2; beta = std::min(bestValue + delta, VALUE_INFINITE); - } else break; @@ -458,16 +398,25 @@ void Thread::search() { // Sort the PV lines searched so far and update the GUI std::stable_sort(rootMoves.begin(), rootMoves.begin() + PVIdx + 1); - if (!mainThread) - continue; - - if (Signals.stop || PVIdx + 1 == multiPV || Time.elapsed() > 3000) + if ( mainThread + && (Threads.stop || PVIdx + 1 == multiPV || Time.elapsed() > 3000)) sync_cout << UCI::pv(rootPos, rootDepth, alpha, beta) << sync_endl; } - if (!Signals.stop) + if (!Threads.stop) completedDepth = rootDepth; + if (rootMoves[0].pv[0] != lastBestMove) { + lastBestMove = rootMoves[0].pv[0]; + lastBestMoveDepth = rootDepth; + } + + // Have we found a "mate in x"? + if ( Limits.mate + && bestValue >= VALUE_MATE_IN_MAX_PLY + && VALUE_MATE - bestValue <= 2 * Limits.mate) + Threads.stop = true; + if (!mainThread) continue; @@ -475,62 +424,56 @@ void Thread::search() { if (skill.enabled() && skill.time_to_pick(rootDepth)) skill.pick_best(multiPV); - // Have we found a "mate in x"? - if ( Limits.mate - && bestValue >= VALUE_MATE_IN_MAX_PLY - && VALUE_MATE - bestValue <= 2 * Limits.mate) - Signals.stop = true; - // Do we have time for the next iteration? Can we stop searching now? if (Limits.use_time_management()) { - if (!Signals.stop && !Signals.stopOnPonderhit) + if (!Threads.stop && !Threads.stopOnPonderhit) { // Stop the search if only one legal move is available, or if all - // of the available time has been used, or if we matched an easyMove - // from the previous search and just did a fast verification. + // of the available time has been used const int F[] = { mainThread->failedLow, bestValue - mainThread->previousScore }; - int improvingFactor = std::max(229, std::min(715, 357 + 119 * F[0] - 6 * F[1])); - double unstablePvFactor = 1 + mainThread->bestMoveChanges; - bool doEasyMove = rootMoves[0].pv[0] == easyMove - && mainThread->bestMoveChanges < 0.03 - && Time.elapsed() > Time.optimum() * 5 / 42; + Color us = rootPos.side_to_move(); + bool thinkHard = bestValue == VALUE_DRAW + && Limits.time[us] - Time.elapsed() > Limits.time[~us] + && ::pv_is_draw(rootPos); + + double unstablePvFactor = 1 + mainThread->bestMoveChanges + thinkHard; + + // if the bestMove is stable over several iterations, reduce time for this move, + // the longer the move has been stable, the more. + // Use part of the gained time from a previous stable move for the current move. + timeReduction = 1; + for (int i : {3, 4, 5}) + if (lastBestMoveDepth * i < completedDepth && !thinkHard) + timeReduction *= 1.3; + unstablePvFactor *= std::pow(mainThread->previousTimeReduction, 0.51) / timeReduction; if ( rootMoves.size() == 1 - || Time.elapsed() > Time.optimum() * unstablePvFactor * improvingFactor / 628 - || (mainThread->easyMovePlayed = doEasyMove, doEasyMove)) + || Time.elapsed() > Time.optimum() * unstablePvFactor * improvingFactor / 628) { // If we are allowed to ponder do not stop the search now but // keep pondering until the GUI sends "ponderhit" or "stop". - if (Limits.ponder) - Signals.stopOnPonderhit = true; + if (Threads.ponder) + Threads.stopOnPonderhit = true; else - Signals.stop = true; + Threads.stop = true; } } - - if (rootMoves[0].pv.size() >= 3) - EasyMove.update(rootPos, rootMoves[0].pv); - else - EasyMove.clear(); } } if (!mainThread) return; - // Clear any candidate easy move that wasn't stable for the last search - // iterations; the second condition prevents consecutive fast moves. - if (EasyMove.stableCnt < 6 || mainThread->easyMovePlayed) - EasyMove.clear(); + mainThread->previousTimeReduction = timeReduction; // If skill level is enabled, swap best PV line with the sub-optimal one if (skill.enabled()) - std::swap(rootMoves[0], *std::find(rootMoves.begin(), - rootMoves.end(), skill.best_move(multiPV))); + std::swap(rootMoves[0], *std::find(rootMoves.begin(), rootMoves.end(), + skill.best ? skill.best : skill.pick_best(multiPV))); } @@ -539,10 +482,10 @@ namespace { // search<>() is the main search function for both PV and non-PV nodes template - Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode) { + Value search(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth, bool cutNode, bool skipEarlyPruning) { const bool PvNode = NT == PV; - const bool rootNode = PvNode && (ss-1)->ply == 0; + const bool rootNode = PvNode && ss->ply == 0; assert(-VALUE_INFINITE <= alpha && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); @@ -550,50 +493,39 @@ namespace { assert(!(PvNode && cutNode)); assert(depth / ONE_PLY * ONE_PLY == depth); - Move pv[MAX_PLY+1], quietsSearched[64]; + Move pv[MAX_PLY+1], capturesSearched[32], quietsSearched[64]; StateInfo st; TTEntry* tte; Key posKey; Move ttMove, move, excludedMove, bestMove; Depth extension, newDepth; - Value bestValue, value, ttValue, eval, nullValue; + Value bestValue, value, ttValue, eval, maxValue; bool ttHit, inCheck, givesCheck, singularExtensionNode, improving; - bool captureOrPromotion, doFullDepthSearch, moveCountPruning; - Piece moved_piece; - int moveCount, quietCount; + bool captureOrPromotion, doFullDepthSearch, moveCountPruning, skipQuiets, ttCapture, pvExact; + Piece movedPiece; + int moveCount, captureCount, quietCount; // Step 1. Initialize node Thread* thisThread = pos.this_thread(); inCheck = pos.checkers(); - moveCount = quietCount = ss->moveCount = 0; - ss->history = VALUE_ZERO; + moveCount = captureCount = quietCount = ss->moveCount = 0; + ss->statScore = 0; bestValue = -VALUE_INFINITE; - ss->ply = (ss-1)->ply + 1; + maxValue = VALUE_INFINITE; // Check for the available remaining time - if (thisThread->resetCalls.load(std::memory_order_relaxed)) - { - thisThread->resetCalls = false; - thisThread->callsCnt = 0; - } - if (++thisThread->callsCnt > 4096) - { - for (Thread* th : Threads) - th->resetCalls = true; - - check_time(); - } + if (thisThread == Threads.main()) + static_cast(thisThread)->check_time(); - // Used to send selDepth info to GUI - if (PvNode && thisThread->maxPly < ss->ply) - thisThread->maxPly = ss->ply; + // Used to send selDepth info to GUI (selDepth counts from 1, ply from 0) + if (PvNode && thisThread->selDepth < ss->ply + 1) + thisThread->selDepth = ss->ply + 1; if (!rootNode) { // Step 2. Check for aborted search and immediate draw - if (Signals.stop.load(std::memory_order_relaxed) || pos.is_draw() || ss->ply >= MAX_PLY) - return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) - : DrawValue[pos.side_to_move()]; + if (Threads.stop.load(std::memory_order_relaxed) || pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) + return ss->ply >= MAX_PLY && !inCheck ? evaluate(pos) : VALUE_DRAW; // Step 3. Mate distance pruning. Even if we mate at the next move our score // would be at best mate_in(ss->ply+1), but if alpha is already bigger because @@ -609,16 +541,17 @@ namespace { assert(0 <= ss->ply && ss->ply < MAX_PLY); + (ss+1)->ply = ss->ply + 1; ss->currentMove = (ss+1)->excludedMove = bestMove = MOVE_NONE; - ss->counterMoves = nullptr; - (ss+1)->skipEarlyPruning = false; + ss->contHistory = &thisThread->contHistory[NO_PIECE][0]; (ss+2)->killers[0] = (ss+2)->killers[1] = MOVE_NONE; + Square prevSq = to_sq((ss-1)->currentMove); // Step 4. Transposition table lookup. We don't want the score of a partial // search to overwrite a previous full search TT value, so we use a different // position key in case of an excluded move. excludedMove = ss->excludedMove; - posKey = pos.key() ^ Key(excludedMove); + posKey = pos.key() ^ Key(excludedMove << 16); // isn't a very good hash tte = TT.probe(posKey, ttHit); ttValue = ttHit ? value_from_tt(tte->value(), ss->ply) : VALUE_NONE; ttMove = rootNode ? thisThread->rootMoves[thisThread->PVIdx].pv[0] @@ -632,23 +565,24 @@ namespace { && (ttValue >= beta ? (tte->bound() & BOUND_LOWER) : (tte->bound() & BOUND_UPPER))) { - // If ttMove is quiet, update killers, history, counter move on TT hit - if (ttValue >= beta && ttMove) + // If ttMove is quiet, update move sorting heuristics on TT hit + if (ttMove) { - int d = depth / ONE_PLY; - - if (!pos.capture_or_promotion(ttMove)) + if (ttValue >= beta) { - Value bonus = Value(d * d + 2 * d - 2); - update_stats(pos, ss, ttMove, nullptr, 0, bonus); - } + if (!pos.capture_or_promotion(ttMove)) + update_stats(pos, ss, ttMove, nullptr, 0, stat_bonus(depth)); - // Extra penalty for a quiet TT move in previous ply when it gets refuted - if ((ss-1)->moveCount == 1 && !pos.captured_piece()) + // Extra penalty for a quiet TT move in previous ply when it gets refuted + if ((ss-1)->moveCount == 1 && !pos.captured_piece()) + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); + } + // Penalty for a quiet ttMove that fails low + else if (!pos.capture_or_promotion(ttMove)) { - Value penalty = Value(d * d + 4 * d + 1); - Square prevSq = to_sq((ss-1)->currentMove); - update_cm_stats(ss-1, pos.piece_on(prevSq), prevSq, -penalty); + int penalty = -stat_bonus(depth); + thisThread->mainHistory.update(pos.side_to_move(), ttMove, penalty); + update_continuation_histories(ss, pos.moved_piece(ttMove), to_sq(ttMove), penalty); } } return ttValue; @@ -657,30 +591,46 @@ namespace { // Step 4a. Tablebase probe if (!rootNode && TB::Cardinality) { - int piecesCnt = pos.count(WHITE) + pos.count(BLACK); + int piecesCount = pos.count(); - if ( piecesCnt <= TB::Cardinality - && (piecesCnt < TB::Cardinality || depth >= TB::ProbeDepth) + if ( piecesCount <= TB::Cardinality + && (piecesCount < TB::Cardinality || depth >= TB::ProbeDepth) && pos.rule50_count() == 0 && !pos.can_castle(ANY_CASTLING)) { - int found, v = Tablebases::probe_wdl(pos, &found); + TB::ProbeState err; + TB::WDLScore wdl = Tablebases::probe_wdl(pos, &err); - if (found) + if (err != TB::ProbeState::FAIL) { - thisThread->tbHits++; + thisThread->tbHits.fetch_add(1, std::memory_order_relaxed); int drawScore = TB::UseRule50 ? 1 : 0; - value = v < -drawScore ? -VALUE_MATE + MAX_PLY + ss->ply - : v > drawScore ? VALUE_MATE - MAX_PLY - ss->ply - : VALUE_DRAW + 2 * v * drawScore; + value = wdl < -drawScore ? -VALUE_MATE + MAX_PLY + ss->ply + 1 + : wdl > drawScore ? VALUE_MATE - MAX_PLY - ss->ply - 1 + : VALUE_DRAW + 2 * wdl * drawScore; + + Bound b = wdl < -drawScore ? BOUND_UPPER + : wdl > drawScore ? BOUND_LOWER : BOUND_EXACT; - tte->save(posKey, value_to_tt(value, ss->ply), BOUND_EXACT, - std::min(DEPTH_MAX - ONE_PLY, depth + 6 * ONE_PLY), - MOVE_NONE, VALUE_NONE, TT.generation()); + if ( b == BOUND_EXACT + || (b == BOUND_LOWER ? value >= beta : value <= alpha)) + { + tte->save(posKey, value_to_tt(value, ss->ply), b, + std::min(DEPTH_MAX - ONE_PLY, depth + 6 * ONE_PLY), + MOVE_NONE, VALUE_NONE, TT.generation()); - return value; + return value; + } + + if (PvNode) + { + if (b == BOUND_LOWER) + bestValue = value, alpha = std::max(alpha, bestValue); + else + maxValue = value; + } } } } @@ -699,9 +649,9 @@ namespace { eval = ss->staticEval = evaluate(pos); // Can ttValue be used as a better position evaluation? - if (ttValue != VALUE_NONE) - if (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER)) - eval = ttValue; + if ( ttValue != VALUE_NONE + && (tte->bound() & (ttValue > eval ? BOUND_LOWER : BOUND_UPPER))) + eval = ttValue; } else { @@ -713,20 +663,19 @@ namespace { ss->staticEval, TT.generation()); } - if (ss->skipEarlyPruning) + if (skipEarlyPruning || !pos.non_pawn_material(pos.side_to_move())) goto moves_loop; // Step 6. Razoring (skipped when in check) if ( !PvNode && depth < 4 * ONE_PLY - && ttMove == MOVE_NONE - && eval + razor_margin[depth / ONE_PLY] <= alpha) + && eval + razor_margin <= alpha) { if (depth <= ONE_PLY) - return qsearch(pos, ss, alpha, beta, DEPTH_ZERO); + return qsearch(pos, ss, alpha, alpha+1); - Value ralpha = alpha - razor_margin[depth / ONE_PLY]; - Value v = qsearch(pos, ss, ralpha, ralpha+1, DEPTH_ZERO); + Value ralpha = alpha - razor_margin; + Value v = qsearch(pos, ss, ralpha, ralpha+1); if (v <= ralpha) return v; } @@ -735,29 +684,27 @@ namespace { if ( !rootNode && depth < 7 * ONE_PLY && eval - futility_margin(depth) >= beta - && eval < VALUE_KNOWN_WIN // Do not return unproven wins - && pos.non_pawn_material(pos.side_to_move())) + && eval < VALUE_KNOWN_WIN) // Do not return unproven wins return eval; // Step 8. Null move search with verification search (is omitted in PV nodes) if ( !PvNode && eval >= beta - && (ss->staticEval >= beta - 35 * (depth / ONE_PLY - 6) || depth >= 13 * ONE_PLY) - && pos.non_pawn_material(pos.side_to_move())) + && ss->staticEval >= beta - 36 * depth / ONE_PLY + 225 + && (ss->ply >= thisThread->nmp_ply || ss->ply % 2 != thisThread->nmp_odd)) { - ss->currentMove = MOVE_NULL; - ss->counterMoves = nullptr; assert(eval - beta >= 0); // Null move dynamic reduction based on depth and value Depth R = ((823 + 67 * depth / ONE_PLY) / 256 + std::min((eval - beta) / PawnValueMg, 3)) * ONE_PLY; + ss->currentMove = MOVE_NULL; + ss->contHistory = &thisThread->contHistory[NO_PIECE][0]; + pos.do_null_move(st); - (ss+1)->skipEarlyPruning = true; - nullValue = depth-R < ONE_PLY ? -qsearch(pos, ss+1, -beta, -beta+1, DEPTH_ZERO) - : - search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode); - (ss+1)->skipEarlyPruning = false; + Value nullValue = depth-R < ONE_PLY ? -qsearch(pos, ss+1, -beta, -beta+1) + : - search(pos, ss+1, -beta, -beta+1, depth-R, !cutNode, true); pos.undo_null_move(); if (nullValue >= beta) @@ -766,14 +713,18 @@ namespace { if (nullValue >= VALUE_MATE_IN_MAX_PLY) nullValue = beta; - if (depth < 12 * ONE_PLY && abs(beta) < VALUE_KNOWN_WIN) + if (abs(beta) < VALUE_KNOWN_WIN && (depth < 12 * ONE_PLY || thisThread->nmp_ply)) return nullValue; // Do verification search at high depths - ss->skipEarlyPruning = true; - Value v = depth-R < ONE_PLY ? qsearch(pos, ss, beta-1, beta, DEPTH_ZERO) - : search(pos, ss, beta-1, beta, depth-R, false); - ss->skipEarlyPruning = false; + // disable null move pruning for side to move for the first part of the remaining search tree + thisThread->nmp_ply = ss->ply + 3 * (depth-R) / 4; + thisThread->nmp_odd = ss->ply % 2; + + Value v = depth-R < ONE_PLY ? qsearch(pos, ss, beta-1, beta) + : search(pos, ss, beta-1, beta, depth-R, false, true); + + thisThread->nmp_odd = thisThread->nmp_ply = 0; if (v >= beta) return nullValue; @@ -788,21 +739,20 @@ namespace { && abs(beta) < VALUE_MATE_IN_MAX_PLY) { Value rbeta = std::min(beta + 200, VALUE_INFINITE); - Depth rdepth = depth - 4 * ONE_PLY; - assert(rdepth >= ONE_PLY); - assert((ss-1)->currentMove != MOVE_NONE); - assert((ss-1)->currentMove != MOVE_NULL); + assert(is_ok((ss-1)->currentMove)); - MovePicker mp(pos, ttMove, rbeta - ss->staticEval); + MovePicker mp(pos, ttMove, rbeta - ss->staticEval, &thisThread->captureHistory); while ((move = mp.next_move()) != MOVE_NONE) if (pos.legal(move)) { ss->currentMove = move; - ss->counterMoves = &thisThread->counterMoveHistory[pos.moved_piece(move)][to_sq(move)]; - pos.do_move(move, st, pos.gives_check(move)); - value = -search(pos, ss+1, -rbeta, -rbeta+1, rdepth, !cutNode); + ss->contHistory = &thisThread->contHistory[pos.moved_piece(move)][to_sq(move)]; + + assert(depth >= 5 * ONE_PLY); + pos.do_move(move, st); + value = -search(pos, ss+1, -rbeta, -rbeta+1, depth - 4 * ONE_PLY, !cutNode, false); pos.undo_move(move); if (value >= rbeta) return value; @@ -815,9 +765,7 @@ namespace { && (PvNode || ss->staticEval + 256 >= beta)) { Depth d = (3 * depth / (4 * ONE_PLY) - 2) * ONE_PLY; - ss->skipEarlyPruning = true; - search(pos, ss, alpha, beta, d, cutNode); - ss->skipEarlyPruning = false; + search(pos, ss, alpha, beta, d, cutNode, true); tte = TT.probe(posKey, ttHit); ttMove = ttHit ? tte->move() : MOVE_NONE; @@ -825,11 +773,10 @@ namespace { moves_loop: // When in check search starts from here - const CounterMoveStats* cmh = (ss-1)->counterMoves; - const CounterMoveStats* fmh = (ss-2)->counterMoves; - const CounterMoveStats* fmh2 = (ss-4)->counterMoves; + const PieceToHistory* contHist[] = { (ss-1)->contHistory, (ss-2)->contHistory, nullptr, (ss-4)->contHistory }; + Move countermove = thisThread->counterMoves[pos.piece_on(prevSq)][prevSq]; - MovePicker mp(pos, ttMove, depth, ss); + MovePicker mp(pos, ttMove, depth, &thisThread->mainHistory, &thisThread->captureHistory, contHist, countermove, ss->killers); value = bestValue; // Workaround a bogus 'uninitialized' warning under gcc improving = ss->staticEval >= (ss-2)->staticEval /* || ss->staticEval == VALUE_NONE Already implicit in the previous condition */ @@ -842,10 +789,13 @@ namespace { && !excludedMove // Recursive singular search is not allowed && (tte->bound() & BOUND_LOWER) && tte->depth() >= depth - 3 * ONE_PLY; + skipQuiets = false; + ttCapture = false; + pvExact = PvNode && ttHit && tte->bound() == BOUND_EXACT; // Step 11. Loop through moves // Loop through all pseudo-legal moves until no moves remain or a beta cutoff occurs - while ((move = mp.next_move()) != MOVE_NONE) + while ((move = mp.next_move(skipQuiets)) != MOVE_NONE) { assert(is_ok(move)); @@ -871,66 +821,66 @@ namespace { extension = DEPTH_ZERO; captureOrPromotion = pos.capture_or_promotion(move); - moved_piece = pos.moved_piece(move); + movedPiece = pos.moved_piece(move); givesCheck = type_of(move) == NORMAL && !pos.discovered_check_candidates() - ? pos.check_squares(type_of(pos.piece_on(from_sq(move)))) & to_sq(move) + ? pos.check_squares(type_of(movedPiece)) & to_sq(move) : pos.gives_check(move); moveCountPruning = depth < 16 * ONE_PLY && moveCount >= FutilityMoveCounts[improving][depth / ONE_PLY]; - // Step 12. Extend checks - if ( givesCheck - && !moveCountPruning - && pos.see_ge(move, VALUE_ZERO)) - extension = ONE_PLY; + // Step 12. Singular and Gives Check Extensions // Singular extension search. If all moves but one fail low on a search of // (alpha-s, beta-s), and just one fails high on (alpha, beta), then that move // is singular and should be extended. To verify this we do a reduced search // on all the other moves but the ttMove and if the result is lower than - // ttValue minus a margin then we extend the ttMove. + // ttValue minus a margin then we will extend the ttMove. if ( singularExtensionNode && move == ttMove - && !extension && pos.legal(move)) { Value rBeta = std::max(ttValue - 2 * depth / ONE_PLY, -VALUE_MATE); Depth d = (depth / (2 * ONE_PLY)) * ONE_PLY; ss->excludedMove = move; - ss->skipEarlyPruning = true; - value = search(pos, ss, rBeta - 1, rBeta, d, cutNode); - ss->skipEarlyPruning = false; + value = search(pos, ss, rBeta - 1, rBeta, d, cutNode, true); ss->excludedMove = MOVE_NONE; if (value < rBeta) extension = ONE_PLY; } + else if ( givesCheck + && !moveCountPruning + && pos.see_ge(move)) + extension = ONE_PLY; - // Update the current move (this must be done after singular extension search) + // Calculate new depth for this move newDepth = depth - ONE_PLY + extension; // Step 13. Pruning at shallow depth if ( !rootNode + && pos.non_pawn_material(pos.side_to_move()) && bestValue > VALUE_MATED_IN_MAX_PLY) { if ( !captureOrPromotion && !givesCheck - && !pos.advanced_pawn_push(move)) + && (!pos.advanced_pawn_push(move) || pos.non_pawn_material() >= Value(5000))) { // Move count based pruning if (moveCountPruning) + { + skipQuiets = true; continue; + } // Reduced depth of the next LMR search int lmrDepth = std::max(newDepth - reduction(improving, depth, moveCount), DEPTH_ZERO) / ONE_PLY; // Countermoves based pruning if ( lmrDepth < 3 - && (!cmh || (*cmh )[moved_piece][to_sq(move)] < VALUE_ZERO) - && (!fmh || (*fmh )[moved_piece][to_sq(move)] < VALUE_ZERO) - && (!fmh2 || (*fmh2)[moved_piece][to_sq(move)] < VALUE_ZERO || (cmh && fmh))) + && (*contHist[0])[movedPiece][to_sq(move)] < CounterMovePruneThreshold + && (*contHist[1])[movedPiece][to_sq(move)] < CounterMovePruneThreshold) continue; // Futility pruning: parent node @@ -944,9 +894,9 @@ namespace { && !pos.see_ge(move, Value(-35 * lmrDepth * lmrDepth))) continue; } - else if ( depth < 7 * ONE_PLY + else if ( depth < 7 * ONE_PLY && !extension - && !pos.see_ge(move, Value(-35 * depth / ONE_PLY * depth / ONE_PLY))) + && !pos.see_ge(move, -PawnValueEg * (depth / ONE_PLY))) continue; } @@ -960,8 +910,12 @@ namespace { continue; } + if (move == ttMove && captureOrPromotion) + ttCapture = true; + + // Update the current move (this must be done after singular extension search) ss->currentMove = move; - ss->counterMoves = &thisThread->counterMoveHistory[moved_piece][to_sq(move)]; + ss->contHistory = &thisThread->contHistory[movedPiece][to_sq(move)]; // Step 14. Make the move pos.do_move(move, st, givesCheck); @@ -978,40 +932,49 @@ namespace { r -= r ? ONE_PLY : DEPTH_ZERO; else { + // Decrease reduction if opponent's move count is high + if ((ss-1)->moveCount > 15) + r -= ONE_PLY; + + // Decrease reduction for exact PV nodes + if (pvExact) + r -= ONE_PLY; + + // Increase reduction if ttMove is a capture + if (ttCapture) + r += ONE_PLY; + // Increase reduction for cut nodes if (cutNode) r += 2 * ONE_PLY; // Decrease reduction for moves that escape a capture. Filter out // castling moves, because they are coded as "king captures rook" and - // hence break make_move(). Also use see() instead of see_sign(), - // because the destination square is empty. - else if ( type_of(move) == NORMAL - && type_of(pos.piece_on(to_sq(move))) != PAWN - && !pos.see_ge(make_move(to_sq(move), from_sq(move)), VALUE_ZERO)) + // hence break make_move(). + else if ( type_of(move) == NORMAL + && !pos.see_ge(make_move(to_sq(move), from_sq(move)))) r -= 2 * ONE_PLY; - ss->history = thisThread->history[moved_piece][to_sq(move)] - + (cmh ? (*cmh )[moved_piece][to_sq(move)] : VALUE_ZERO) - + (fmh ? (*fmh )[moved_piece][to_sq(move)] : VALUE_ZERO) - + (fmh2 ? (*fmh2)[moved_piece][to_sq(move)] : VALUE_ZERO) - + thisThread->fromTo.get(~pos.side_to_move(), move) - - 8000; // Correction factor + ss->statScore = thisThread->mainHistory[~pos.side_to_move()][from_to(move)] + + (*contHist[0])[movedPiece][to_sq(move)] + + (*contHist[1])[movedPiece][to_sq(move)] + + (*contHist[3])[movedPiece][to_sq(move)] + - 4000; // Decrease/increase reduction by comparing opponent's stat score - if (ss->history > VALUE_ZERO && (ss-1)->history < VALUE_ZERO) + if (ss->statScore >= 0 && (ss-1)->statScore < 0) r -= ONE_PLY; - else if (ss->history < VALUE_ZERO && (ss-1)->history > VALUE_ZERO) + else if ((ss-1)->statScore >= 0 && ss->statScore < 0) r += ONE_PLY; // Decrease/increase reduction for moves with a good/bad history - r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->history / 20000) * ONE_PLY); + r = std::max(DEPTH_ZERO, (r / ONE_PLY - ss->statScore / 20000) * ONE_PLY); } Depth d = std::max(newDepth - r, ONE_PLY); - value = -search(pos, ss+1, -(alpha+1), -alpha, d, true); + value = -search(pos, ss+1, -(alpha+1), -alpha, d, true, false); doFullDepthSearch = (value > alpha && d != newDepth); } @@ -1021,9 +984,9 @@ namespace { // Step 16. Full depth search when LMR is skipped or fails high if (doFullDepthSearch) value = newDepth < ONE_PLY ? - givesCheck ? -qsearch(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO) - : -qsearch(pos, ss+1, -(alpha+1), -alpha, DEPTH_ZERO) - : - search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode); + givesCheck ? -qsearch(pos, ss+1, -(alpha+1), -alpha) + : -qsearch(pos, ss+1, -(alpha+1), -alpha) + : - search(pos, ss+1, -(alpha+1), -alpha, newDepth, !cutNode, false); // For PV nodes only, do a full PV search on the first move or after a fail // high (in the latter case search only if value < beta), otherwise let the @@ -1034,9 +997,9 @@ namespace { (ss+1)->pv[0] = MOVE_NONE; value = newDepth < ONE_PLY ? - givesCheck ? -qsearch(pos, ss+1, -beta, -alpha, DEPTH_ZERO) - : -qsearch(pos, ss+1, -beta, -alpha, DEPTH_ZERO) - : - search(pos, ss+1, -beta, -alpha, newDepth, false); + givesCheck ? -qsearch(pos, ss+1, -beta, -alpha) + : -qsearch(pos, ss+1, -beta, -alpha) + : - search(pos, ss+1, -beta, -alpha, newDepth, false, false); } // Step 17. Undo move @@ -1048,7 +1011,7 @@ namespace { // Finished searching the move. If a stop occurred, the return value of // the search cannot be trusted, and we return immediately without // updating best move, PV and TT. - if (Signals.stop.load(std::memory_order_relaxed)) + if (Threads.stop.load(std::memory_order_relaxed)) return VALUE_ZERO; if (rootNode) @@ -1060,6 +1023,7 @@ namespace { if (moveCount == 1 || value > alpha) { rm.score = value; + rm.selDepth = thisThread->selDepth; rm.pv.resize(1); assert((ss+1)->pv); @@ -1074,8 +1038,8 @@ namespace { ++static_cast(thisThread)->bestMoveChanges; } else - // All other moves but the PV are set to the lowest value: this is - // not a problem when sorting because the sort is stable and the + // All other moves but the PV are set to the lowest value: this + // is not a problem when sorting because the sort is stable and the // move position in the list is preserved - just the PV is pushed up. rm.score = -VALUE_INFINITE; } @@ -1086,13 +1050,6 @@ namespace { if (value > alpha) { - // If there is an easy move for this position, clear it if unstable - if ( PvNode - && thisThread == Threads.main() - && EasyMove.get(pos.key()) - && (move != EasyMove.get(pos.key()) || moveCount > 1)) - EasyMove.clear(); - bestMove = move; if (PvNode && !rootNode) // Update pv even in fail-high case @@ -1110,13 +1067,15 @@ namespace { if (!captureOrPromotion && move != bestMove && quietCount < 64) quietsSearched[quietCount++] = move; + else if (captureOrPromotion && move != bestMove && captureCount < 32) + capturesSearched[captureCount++] = move; } // The following condition would detect a stop only after move loop has been // completed. But in this case bestValue is valid because we have fully // searched our subtree, and we can anyhow save the result in TT. /* - if (Signals.stop) + if (Threads.stop) return VALUE_DRAW; */ @@ -1129,41 +1088,33 @@ namespace { if (!moveCount) bestValue = excludedMove ? alpha - : inCheck ? mated_in(ss->ply) : DrawValue[pos.side_to_move()]; + : inCheck ? mated_in(ss->ply) : VALUE_DRAW; else if (bestMove) { - int d = depth / ONE_PLY; - - // Quiet best move: update killers, history and countermoves + // Quiet best move: update move sorting heuristics if (!pos.capture_or_promotion(bestMove)) - { - Value bonus = Value(d * d + 2 * d - 2); - update_stats(pos, ss, bestMove, quietsSearched, quietCount, bonus); - } + update_stats(pos, ss, bestMove, quietsSearched, quietCount, stat_bonus(depth)); + else + update_capture_stats(pos, bestMove, capturesSearched, captureCount, stat_bonus(depth)); // Extra penalty for a quiet TT move in previous ply when it gets refuted if ((ss-1)->moveCount == 1 && !pos.captured_piece()) - { - Value penalty = Value(d * d + 4 * d + 1); - Square prevSq = to_sq((ss-1)->currentMove); - update_cm_stats(ss-1, pos.piece_on(prevSq), prevSq, -penalty); - } + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, -stat_bonus(depth + ONE_PLY)); } // Bonus for prior countermove that caused the fail low else if ( depth >= 3 * ONE_PLY && !pos.captured_piece() && is_ok((ss-1)->currentMove)) - { - int d = depth / ONE_PLY; - Value bonus = Value(d * d + 2 * d - 2); - Square prevSq = to_sq((ss-1)->currentMove); - update_cm_stats(ss-1, pos.piece_on(prevSq), prevSq, bonus); - } + update_continuation_histories(ss-1, pos.piece_on(prevSq), prevSq, stat_bonus(depth)); - tte->save(posKey, value_to_tt(bestValue, ss->ply), - bestValue >= beta ? BOUND_LOWER : - PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, - depth, bestMove, ss->staticEval, TT.generation()); + if (PvNode) + bestValue = std::min(bestValue, maxValue); + + if (!excludedMove) + tte->save(posKey, value_to_tt(bestValue, ss->ply), + bestValue >= beta ? BOUND_LOWER : + PvNode && bestMove ? BOUND_EXACT : BOUND_UPPER, + depth, bestMove, ss->staticEval, TT.generation()); assert(bestValue > -VALUE_INFINITE && bestValue < VALUE_INFINITE); @@ -1172,15 +1123,14 @@ namespace { // qsearch() is the quiescence search function, which is called by the main - // search function when the remaining depth is zero (or, to be more precise, - // less than ONE_PLY). + // search function with depth zero, or recursively with depth less than ONE_PLY. template Value qsearch(Position& pos, Stack* ss, Value alpha, Value beta, Depth depth) { const bool PvNode = NT == PV; - assert(InCheck == !!pos.checkers()); + assert(InCheck == bool(pos.checkers())); assert(alpha >= -VALUE_INFINITE && alpha < beta && beta <= VALUE_INFINITE); assert(PvNode || (alpha == beta - 1)); assert(depth <= DEPTH_ZERO); @@ -1194,6 +1144,7 @@ namespace { Value bestValue, value, ttValue, futilityValue, futilityBase, oldAlpha; bool ttHit, givesCheck, evasionPrunable; Depth ttDepth; + int moveCount; if (PvNode) { @@ -1203,12 +1154,12 @@ namespace { } ss->currentMove = bestMove = MOVE_NONE; - ss->ply = (ss-1)->ply + 1; + (ss+1)->ply = ss->ply + 1; + moveCount = 0; // Check for an instant draw or if the maximum ply has been reached - if (pos.is_draw() || ss->ply >= MAX_PLY) - return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) - : DrawValue[pos.side_to_move()]; + if (pos.is_draw(ss->ply) || ss->ply >= MAX_PLY) + return ss->ply >= MAX_PLY && !InCheck ? evaluate(pos) : VALUE_DRAW; assert(0 <= ss->ply && ss->ply < MAX_PLY); @@ -1217,7 +1168,6 @@ namespace { // only two types of depth in TT: DEPTH_QS_CHECKS or DEPTH_QS_NO_CHECKS. ttDepth = InCheck || depth >= DEPTH_QS_CHECKS ? DEPTH_QS_CHECKS : DEPTH_QS_NO_CHECKS; - // Transposition table lookup posKey = pos.key(); tte = TT.probe(posKey, ttHit); @@ -1247,9 +1197,9 @@ namespace { ss->staticEval = bestValue = evaluate(pos); // Can ttValue be used as a better position evaluation? - if (ttValue != VALUE_NONE) - if (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER)) - bestValue = ttValue; + if ( ttValue != VALUE_NONE + && (tte->bound() & (ttValue > bestValue ? BOUND_LOWER : BOUND_UPPER))) + bestValue = ttValue; } else ss->staticEval = bestValue = @@ -1260,7 +1210,7 @@ namespace { if (bestValue >= beta) { if (!ttHit) - tte->save(pos.key(), value_to_tt(bestValue, ss->ply), BOUND_LOWER, + tte->save(posKey, value_to_tt(bestValue, ss->ply), BOUND_LOWER, DEPTH_NONE, MOVE_NONE, ss->staticEval, TT.generation()); return bestValue; @@ -1276,7 +1226,7 @@ namespace { // to search the moves. Because the depth is <= 0 here, only captures, // queen promotions and checks (only if depth >= DEPTH_QS_CHECKS) will // be generated. - MovePicker mp(pos, ttMove, depth, to_sq((ss-1)->currentMove)); + MovePicker mp(pos, ttMove, depth, &pos.this_thread()->mainHistory, &pos.this_thread()->captureHistory, to_sq((ss-1)->currentMove)); // Loop through the moves until no moves remain or a beta cutoff occurs while ((move = mp.next_move()) != MOVE_NONE) @@ -1284,9 +1234,11 @@ namespace { assert(is_ok(move)); givesCheck = type_of(move) == NORMAL && !pos.discovered_check_candidates() - ? pos.check_squares(type_of(pos.piece_on(from_sq(move)))) & to_sq(move) + ? pos.check_squares(type_of(pos.moved_piece(move))) & to_sq(move) : pos.gives_check(move); + moveCount++; + // Futility pruning if ( !InCheck && !givesCheck @@ -1312,13 +1264,13 @@ namespace { // Detect non-capture evasions that are candidates to be pruned evasionPrunable = InCheck + && (depth != DEPTH_ZERO || moveCount > 2) && bestValue > VALUE_MATED_IN_MAX_PLY && !pos.capture(move); // Don't search moves with negative SEE values if ( (!InCheck || evasionPrunable) - && type_of(move) != PROMOTION - && !pos.see_ge(move, VALUE_ZERO)) + && !pos.see_ge(move)) continue; // Speculative prefetch as early as possible @@ -1326,7 +1278,10 @@ namespace { // Check for legality just before making the move if (!pos.legal(move)) + { + moveCount--; continue; + } ss->currentMove = move; @@ -1414,30 +1369,41 @@ namespace { } - // update_cm_stats() updates countermove and follow-up move history + // update_continuation_histories() updates histories of the move pairs formed + // by moves at ply -1, -2, and -4 with current move. + + void update_continuation_histories(Stack* ss, Piece pc, Square to, int bonus) { + + for (int i : {1, 2, 4}) + if (is_ok((ss-i)->currentMove)) + (ss-i)->contHistory->update(pc, to, bonus); + } - void update_cm_stats(Stack* ss, Piece pc, Square s, Value bonus) { - CounterMoveStats* cmh = (ss-1)->counterMoves; - CounterMoveStats* fmh1 = (ss-2)->counterMoves; - CounterMoveStats* fmh2 = (ss-4)->counterMoves; + // update_capture_stats() updates move sorting heuristics when a new capture best move is found - if (cmh) - cmh->update(pc, s, bonus); + void update_capture_stats(const Position& pos, Move move, + Move* captures, int captureCnt, int bonus) { - if (fmh1) - fmh1->update(pc, s, bonus); + CapturePieceToHistory& captureHistory = pos.this_thread()->captureHistory; + Piece moved_piece = pos.moved_piece(move); + PieceType captured = type_of(pos.piece_on(to_sq(move))); + captureHistory.update(moved_piece, to_sq(move), captured, bonus); - if (fmh2) - fmh2->update(pc, s, bonus); + // Decrease all the other played capture moves + for (int i = 0; i < captureCnt; ++i) + { + moved_piece = pos.moved_piece(captures[i]); + captured = type_of(pos.piece_on(to_sq(captures[i]))); + captureHistory.update(moved_piece, to_sq(captures[i]), captured, -bonus); + } } - // update_stats() updates killers, history, countermove and countermove plus - // follow-up move history when a new quiet best move is found. + // update_stats() updates move sorting heuristics when a new quiet best move is found void update_stats(const Position& pos, Stack* ss, Move move, - Move* quiets, int quietsCnt, Value bonus) { + Move* quiets, int quietsCnt, int bonus) { if (ss->killers[0] != move) { @@ -1447,26 +1413,42 @@ namespace { Color c = pos.side_to_move(); Thread* thisThread = pos.this_thread(); - thisThread->fromTo.update(c, move, bonus); - thisThread->history.update(pos.moved_piece(move), to_sq(move), bonus); - update_cm_stats(ss, pos.moved_piece(move), to_sq(move), bonus); + thisThread->mainHistory.update(c, move, bonus); + update_continuation_histories(ss, pos.moved_piece(move), to_sq(move), bonus); - if ((ss-1)->counterMoves) + if (is_ok((ss-1)->currentMove)) { Square prevSq = to_sq((ss-1)->currentMove); - thisThread->counterMoves.update(pos.piece_on(prevSq), prevSq, move); + thisThread->counterMoves[pos.piece_on(prevSq)][prevSq] = move; } // Decrease all the other played quiet moves for (int i = 0; i < quietsCnt; ++i) { - thisThread->fromTo.update(c, quiets[i], -bonus); - thisThread->history.update(pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); - update_cm_stats(ss, pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); + thisThread->mainHistory.update(c, quiets[i], -bonus); + update_continuation_histories(ss, pos.moved_piece(quiets[i]), to_sq(quiets[i]), -bonus); } } + // Is the PV leading to a draw position? Assumes all pv moves are legal + bool pv_is_draw(Position& pos) { + + StateInfo st[MAX_PLY]; + auto& pv = pos.this_thread()->rootMoves[0].pv; + + for (size_t i = 0; i < pv.size(); ++i) + pos.do_move(pv[i], st[i]); + + bool isDraw = pos.is_draw(pv.size()); + + for (size_t i = pv.size(); i > 0; --i) + pos.undo_move(pv[i-1]); + + return isDraw; + } + + // When playing with strength handicap, choose best move among a set of RootMoves // using a statistical rule dependent on 'level'. Idea by Heinz van Saanen. @@ -1490,7 +1472,7 @@ namespace { int push = ( weakness * int(topScore - rootMoves[i].score) + delta * (rng.rand() % weakness)) / 128; - if (rootMoves[i].score + push > maxScore) + if (rootMoves[i].score + push >= maxScore) { maxScore = rootMoves[i].score + push; best = rootMoves[i].pv[0]; @@ -1500,11 +1482,19 @@ namespace { return best; } +} // namespace // check_time() is used to print debug info and, more importantly, to detect // when we are out of available time and thus stop the search. - void check_time() { + void MainThread::check_time() { + + if (--callsCnt > 0) + return; + + // At low node count increase the checking rate to about 0.1% of nodes + // otherwise use a default value. + callsCnt = Limits.nodes ? std::min(4096, int(Limits.nodes / 1024)) : 4096; static TimePoint lastInfoTime = now(); @@ -1518,17 +1508,15 @@ namespace { } // An engine may not stop pondering until told so by the GUI - if (Limits.ponder) + if (Threads.ponder) return; if ( (Limits.use_time_management() && elapsed > Time.maximum() - 10) || (Limits.movetime && elapsed >= Limits.movetime) || (Limits.nodes && Threads.nodes_searched() >= (uint64_t)Limits.nodes)) - Signals.stop = true; + Threads.stop = true; } -} // namespace - /// UCI::pv() formats PV information according to the UCI protocol. UCI requires /// that all (if any) unsearched PV lines are sent using a previous search score. @@ -1545,7 +1533,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { for (size_t i = 0; i < multiPV; ++i) { - bool updated = (i <= PVIdx); + bool updated = (i <= PVIdx && rootMoves[i].score != -VALUE_INFINITE); if (depth == ONE_PLY && !updated) continue; @@ -1561,7 +1549,7 @@ string UCI::pv(const Position& pos, Depth depth, Value alpha, Value beta) { ss << "info" << " depth " << d / ONE_PLY - << " seldepth " << pos.this_thread()->maxPly + << " seldepth " << rootMoves[i].selDepth << " multipv " << i + 1 << " score " << UCI::value(v); @@ -1601,7 +1589,7 @@ bool RootMove::extract_ponder_from_tt(Position& pos) { if (!pv[0]) return false; - pos.do_move(pv[0], st, pos.gives_check(pv[0])); + pos.do_move(pv[0], st); TTEntry* tte = TT.probe(pos.key(), ttHit); if (ttHit) @@ -1632,6 +1620,10 @@ void Tablebases::filter_root_moves(Position& pos, Search::RootMoves& rootMoves) if (Cardinality < popcount(pos.pieces()) || pos.can_castle(ANY_CASTLING)) return; + // Don't filter any moves if the user requested analysis on multiple + if (Options["MultiPV"] != 1) + return; + // If the current root position is in the tablebases, then RootMoves // contains only moves that preserve the draw or the win. RootInTB = root_probe(pos, rootMoves, TB::Score); @@ -1653,4 +1645,9 @@ void Tablebases::filter_root_moves(Position& pos, Search::RootMoves& rootMoves) TB::Score = TB::Score > VALUE_DRAW ? VALUE_MATE - MAX_PLY - 1 : TB::Score < VALUE_DRAW ? -VALUE_MATE + MAX_PLY + 1 : VALUE_DRAW; + + // Since root_probe() and root_probe_wdl() dirty the root move scores, + // we reset them to -VALUE_INFINITE + for (RootMove& rm : rootMoves) + rm.score = -VALUE_INFINITE; } diff --git a/Engines/Windows/stockfish/src/search.h b/Engines/Windows/stockfish/src/search.h index d8051ec..1274b96 100644 --- a/Engines/Windows/stockfish/src/search.h +++ b/Engines/Windows/stockfish/src/search.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,7 +21,6 @@ #ifndef SEARCH_H_INCLUDED #define SEARCH_H_INCLUDED -#include #include #include "misc.h" @@ -32,21 +31,24 @@ class Position; namespace Search { +/// Threshold used for countermoves based pruning +const int CounterMovePruneThreshold = 0; + + /// Stack struct keeps track of the information we need to remember from nodes /// shallower and deeper in the tree during the search. Each search thread has /// its own array of Stack objects, indexed by the current ply. struct Stack { Move* pv; + PieceToHistory* contHistory; int ply; Move currentMove; Move excludedMove; Move killers[2]; Value staticEval; - Value history; - bool skipEarlyPruning; + int statScore; int moveCount; - CounterMoveStats* counterMoves; }; @@ -57,13 +59,16 @@ struct Stack { struct RootMove { explicit RootMove(Move m) : pv(1, m) {} - - bool operator<(const RootMove& m) const { return m.score < score; } // Descending sort - bool operator==(const Move& m) const { return pv[0] == m; } bool extract_ponder_from_tt(Position& pos); + bool operator==(const Move& m) const { return pv[0] == m; } + bool operator<(const RootMove& m) const { // Sort in descending order + return m.score != score ? m.score < score + : m.previousScore < previousScore; + } Value score = -VALUE_INFINITE; Value previousScore = -VALUE_INFINITE; + int selDepth = 0; std::vector pv; }; @@ -71,40 +76,30 @@ typedef std::vector RootMoves; /// LimitsType struct stores information sent by GUI about available time to -/// search the current move, maximum depth/time, if we are in analysis mode or -/// if we have to ponder while it's our opponent's turn to move. +/// search the current move, maximum depth/time, or if we are in analysis mode. struct LimitsType { LimitsType() { // Init explicitly due to broken value-initialization of non POD in MSVC nodes = time[WHITE] = time[BLACK] = inc[WHITE] = inc[BLACK] = - npmsec = movestogo = depth = movetime = mate = infinite = ponder = 0; + npmsec = movestogo = depth = movetime = mate = perft = infinite = 0; } bool use_time_management() const { - return !(mate | movetime | depth | nodes | infinite); + return !(mate | movetime | depth | nodes | perft | infinite); } std::vector searchmoves; - int time[COLOR_NB], inc[COLOR_NB], npmsec, movestogo, depth, movetime, mate, infinite, ponder; + int time[COLOR_NB], inc[COLOR_NB], npmsec, movestogo, depth, + movetime, mate, perft, infinite; int64_t nodes; TimePoint startTime; }; - -/// SignalsType struct stores atomic flags updated during the search, typically -/// in an async fashion e.g. to stop the search by the GUI. - -struct SignalsType { - std::atomic_bool stop, stopOnPonderhit; -}; - -extern SignalsType Signals; extern LimitsType Limits; void init(); void clear(); -template uint64_t perft(Position& pos, Depth depth); } // namespace Search diff --git a/Engines/Windows/stockfish/src/syzygy/tbprobe.cpp b/Engines/Windows/stockfish/src/syzygy/tbprobe.cpp index 0281ccc..b50275e 100644 --- a/Engines/Windows/stockfish/src/syzygy/tbprobe.cpp +++ b/Engines/Windows/stockfish/src/syzygy/tbprobe.cpp @@ -1,560 +1,1416 @@ /* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (c) 2013 Ronald de Man - This file may be redistributed and/or modified without restrictions. + Copyright (C) 2016-2018 Marco Costalba, Lucas Braesch - tbprobe.cpp contains the Stockfish-specific routines of the - tablebase probing code. It should be relatively easy to adapt - this code to other chess engines. -*/ + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. -#define NOMINMAX + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ #include +#include +#include +#include // For std::memset +#include +#include +#include +#include +#include +#include -#include "../position.h" -#include "../movegen.h" #include "../bitboard.h" +#include "../movegen.h" +#include "../position.h" #include "../search.h" +#include "../thread_win32.h" +#include "../types.h" #include "tbprobe.h" -#include "tbcore.h" -#include "tbcore.cpp" +#ifndef _WIN32 +#include +#include +#include +#include +#else +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#endif -namespace Zobrist { - extern Key psq[PIECE_NB][SQUARE_NB]; -} +using namespace Tablebases; -int Tablebases::MaxCardinality = 0; +int Tablebases::MaxCardinality; -// Given a position with 6 or fewer pieces, produce a text string -// of the form KQPvKRP, where "KQP" represents the white pieces if -// mirror == 0 and the black pieces if mirror == 1. -static void prt_str(Position& pos, char *str, int mirror) -{ - Color color; - PieceType pt; - int i; - - color = !mirror ? WHITE : BLACK; - for (pt = KING; pt >= PAWN; --pt) - for (i = popcount(pos.pieces(color, pt)); i > 0; i--) - *str++ = pchr[6 - pt]; - *str++ = 'v'; - color = ~color; - for (pt = KING; pt >= PAWN; --pt) - for (i = popcount(pos.pieces(color, pt)); i > 0; i--) - *str++ = pchr[6 - pt]; - *str++ = 0; -} +namespace { -// Given a position, produce a 64-bit material signature key. -// If the engine supports such a key, it should equal the engine's key. -static uint64 calc_key(Position& pos, int mirror) -{ - Color color; - PieceType pt; - int i; - uint64 key = 0; - - color = !mirror ? WHITE : BLACK; - for (pt = PAWN; pt <= KING; ++pt) - for (i = popcount(pos.pieces(color, pt)); i > 0; i--) - key ^= Zobrist::psq[make_piece(WHITE, pt)][i - 1]; - color = ~color; - for (pt = PAWN; pt <= KING; ++pt) - for (i = popcount(pos.pieces(color, pt)); i > 0; i--) - key ^= Zobrist::psq[make_piece(BLACK, pt)][i - 1]; - - return key; -} +// Each table has a set of flags: all of them refer to DTZ tables, the last one to WDL tables +enum TBFlag { STM = 1, Mapped = 2, WinPlies = 4, LossPlies = 8, SingleValue = 128 }; -// Produce a 64-bit material key corresponding to the material combination -// defined by pcs[16], where pcs[1], ..., pcs[6] is the number of white -// pawns, ..., kings and pcs[9], ..., pcs[14] is the number of black -// pawns, ..., kings. -static uint64 calc_key_from_pcs(int *pcs, int mirror) -{ - int color; - PieceType pt; - int i; - uint64 key = 0; - - color = !mirror ? 0 : 8; - for (pt = PAWN; pt <= KING; ++pt) - for (i = 0; i < pcs[color + pt]; i++) - key ^= Zobrist::psq[make_piece(WHITE, pt)][i]; - color ^= 8; - for (pt = PAWN; pt <= KING; ++pt) - for (i = 0; i < pcs[color + pt]; i++) - key ^= Zobrist::psq[make_piece(BLACK, pt)][i]; - - return key; +inline WDLScore operator-(WDLScore d) { return WDLScore(-int(d)); } +inline Square operator^=(Square& s, int i) { return s = Square(int(s) ^ i); } +inline Square operator^(Square s, int i) { return Square(int(s) ^ i); } + +// DTZ tables don't store valid scores for moves that reset the rule50 counter +// like captures and pawn moves but we can easily recover the correct dtz of the +// previous move if we know the position's WDL score. +int dtz_before_zeroing(WDLScore wdl) { + return wdl == WDLWin ? 1 : + wdl == WDLCursedWin ? 101 : + wdl == WDLBlessedLoss ? -101 : + wdl == WDLLoss ? -1 : 0; } -bool is_little_endian() { - union { - int i; - char c[sizeof(int)]; - } x; - x.i = 1; - return x.c[0] == 1; +// Return the sign of a number (-1, 0, 1) +template int sign_of(T val) { + return (T(0) < val) - (val < T(0)); } -static ubyte decompress_pairs(struct PairsData *d, uint64 idx) +// Numbers in little endian used by sparseIndex[] to point into blockLength[] +struct SparseEntry { + char block[4]; // Number of block + char offset[2]; // Offset within the block +}; + +static_assert(sizeof(SparseEntry) == 6, "SparseEntry must be 6 bytes"); + +typedef uint16_t Sym; // Huffman symbol + +struct LR { + enum Side { Left, Right, Value }; + + uint8_t lr[3]; // The first 12 bits is the left-hand symbol, the second 12 + // bits is the right-hand symbol. If symbol has length 1, + // then the first byte is the stored value. + template + Sym get() { + return S == Left ? ((lr[1] & 0xF) << 8) | lr[0] : + S == Right ? (lr[2] << 4) | (lr[1] >> 4) : + S == Value ? lr[0] : (assert(false), Sym(-1)); + } +}; + +static_assert(sizeof(LR) == 3, "LR tree entry must be 3 bytes"); + +const int TBPIECES = 6; + +struct PairsData { + int flags; + size_t sizeofBlock; // Block size in bytes + size_t span; // About every span values there is a SparseIndex[] entry + int blocksNum; // Number of blocks in the TB file + int maxSymLen; // Maximum length in bits of the Huffman symbols + int minSymLen; // Minimum length in bits of the Huffman symbols + Sym* lowestSym; // lowestSym[l] is the symbol of length l with the lowest value + LR* btree; // btree[sym] stores the left and right symbols that expand sym + uint16_t* blockLength; // Number of stored positions (minus one) for each block: 1..65536 + int blockLengthSize; // Size of blockLength[] table: padded so it's bigger than blocksNum + SparseEntry* sparseIndex; // Partial indices into blockLength[] + size_t sparseIndexSize; // Size of SparseIndex[] table + uint8_t* data; // Start of Huffman compressed data + std::vector base64; // base64[l - min_sym_len] is the 64bit-padded lowest symbol of length l + std::vector symlen; // Number of values (-1) represented by a given Huffman symbol: 1..256 + Piece pieces[TBPIECES]; // Position pieces: the order of pieces defines the groups + uint64_t groupIdx[TBPIECES+1]; // Start index used for the encoding of the group's pieces + int groupLen[TBPIECES+1]; // Number of pieces in a given group: KRKN -> (3, 1) +}; + +// Helper struct to avoid manually defining entry copy constructor as we +// should because the default one is not compatible with std::atomic_bool. +struct Atomic { + Atomic() = default; + Atomic(const Atomic& e) { ready = e.ready.load(); } // MSVC 2013 wants assignment within body + std::atomic_bool ready; +}; + +// We define types for the different parts of the WDLEntry and DTZEntry with +// corresponding specializations for pieces or pawns. + +struct WDLEntryPiece { + PairsData* precomp; +}; + +struct WDLEntryPawn { + uint8_t pawnCount[2]; // [Lead color / other color] + WDLEntryPiece file[2][4]; // [wtm / btm][FILE_A..FILE_D] +}; + +struct DTZEntryPiece { + PairsData* precomp; + uint16_t map_idx[4]; // WDLWin, WDLLoss, WDLCursedWin, WDLBlessedLoss + uint8_t* map; +}; + +struct DTZEntryPawn { + uint8_t pawnCount[2]; + DTZEntryPiece file[4]; + uint8_t* map; +}; + +struct TBEntry : public Atomic { + void* baseAddress; + uint64_t mapping; + Key key; + Key key2; + int pieceCount; + bool hasPawns; + bool hasUniquePieces; +}; + +// Now the main types: WDLEntry and DTZEntry +struct WDLEntry : public TBEntry { + WDLEntry(const std::string& code); + ~WDLEntry(); + union { + WDLEntryPiece pieceTable[2]; // [wtm / btm] + WDLEntryPawn pawnTable; + }; +}; + +struct DTZEntry : public TBEntry { + DTZEntry(const WDLEntry& wdl); + ~DTZEntry(); + union { + DTZEntryPiece pieceTable; + DTZEntryPawn pawnTable; + }; +}; + +typedef decltype(WDLEntry::pieceTable) WDLPieceTable; +typedef decltype(DTZEntry::pieceTable) DTZPieceTable; +typedef decltype(WDLEntry::pawnTable ) WDLPawnTable; +typedef decltype(DTZEntry::pawnTable ) DTZPawnTable; + +auto item(WDLPieceTable& e, int stm, int ) -> decltype(e[stm])& { return e[stm]; } +auto item(DTZPieceTable& e, int , int ) -> decltype(e)& { return e; } +auto item(WDLPawnTable& e, int stm, int f) -> decltype(e.file[stm][f])& { return e.file[stm][f]; } +auto item(DTZPawnTable& e, int , int f) -> decltype(e.file[f])& { return e.file[f]; } + +template struct Ret { typedef int type; }; +template<> struct Ret { typedef WDLScore type; }; + +int MapPawns[SQUARE_NB]; +int MapB1H1H7[SQUARE_NB]; +int MapA1D1D4[SQUARE_NB]; +int MapKK[10][SQUARE_NB]; // [MapA1D1D4][SQUARE_NB] + +// Comparison function to sort leading pawns in ascending MapPawns[] order +bool pawns_comp(Square i, Square j) { return MapPawns[i] < MapPawns[j]; } +int off_A1H8(Square sq) { return int(rank_of(sq)) - file_of(sq); } + +const Value WDL_to_value[] = { + -VALUE_MATE + MAX_PLY + 1, + VALUE_DRAW - 2, + VALUE_DRAW, + VALUE_DRAW + 2, + VALUE_MATE - MAX_PLY - 1 +}; + +const std::string PieceToChar = " PNBRQK pnbrqk"; + +int Binomial[6][SQUARE_NB]; // [k][n] k elements from a set of n elements +int LeadPawnIdx[5][SQUARE_NB]; // [leadPawnsCnt][SQUARE_NB] +int LeadPawnsSize[5][4]; // [leadPawnsCnt][FILE_A..FILE_D] + +enum { BigEndian, LittleEndian }; + +template +inline void swap_byte(T& x) { - static const bool isLittleEndian = is_little_endian(); - return isLittleEndian ? decompress_pairs(d, idx) - : decompress_pairs(d, idx); + char tmp, *c = (char*)&x; + for (int i = 0; i < Half; ++i) + tmp = c[i], c[i] = c[End - i], c[End - i] = tmp; } +template<> inline void swap_byte(uint8_t&) {} -// probe_wdl_table and probe_dtz_table require similar adaptations. -static int probe_wdl_table(Position& pos, int *success) +template T number(void* addr) { - struct TBEntry *ptr; - struct TBHashEntry *ptr2; - uint64 idx; - uint64 key; - int i; - ubyte res; - int p[TBPIECES]; - - // Obtain the position's material signature key. - key = pos.material_key(); - - // Test for KvK. - if (key == (Zobrist::psq[W_KING][0] ^ Zobrist::psq[B_KING][0])) - return 0; - - ptr2 = TB_hash[key >> (64 - TBHASHBITS)]; - for (i = 0; i < HSHMAX; i++) - if (ptr2[i].key == key) break; - if (i == HSHMAX) { - *success = 0; - return 0; + const union { uint32_t i; char c[4]; } Le = { 0x01020304 }; + const bool IsLittleEndian = (Le.c[0] == 4); + + T v; + + if ((uintptr_t)addr & (alignof(T) - 1)) // Unaligned pointer (very rare) + std::memcpy(&v, addr, sizeof(T)); + else + v = *((T*)addr); + + if (LE != IsLittleEndian) + swap_byte(v); + return v; +} + +class HashTable { + + typedef std::pair EntryPair; + typedef std::pair Entry; + + static const int TBHASHBITS = 10; + static const int HSHMAX = 5; + + Entry hashTable[1 << TBHASHBITS][HSHMAX]; + + std::deque wdlTable; + std::deque dtzTable; + + void insert(Key key, WDLEntry* wdl, DTZEntry* dtz) { + Entry* entry = hashTable[key >> (64 - TBHASHBITS)]; + + for (int i = 0; i < HSHMAX; ++i, ++entry) + if (!entry->second.first || entry->first == key) { + *entry = std::make_pair(key, std::make_pair(wdl, dtz)); + return; + } + + std::cerr << "HSHMAX too low!" << std::endl; + exit(1); + } + +public: + template::value ? 0 : 1> + E* get(Key key) { + Entry* entry = hashTable[key >> (64 - TBHASHBITS)]; + + for (int i = 0; i < HSHMAX; ++i, ++entry) + if (entry->first == key) + return std::get(entry->second); + + return nullptr; } - ptr = ptr2[i].ptr; - if (!ptr->ready) { - LOCK(TB_mutex); - if (!ptr->ready) { - char str[16]; - prt_str(pos, str, ptr->key != key); - if (!init_table_wdl(ptr, str)) { - ptr2[i].key = 0ULL; - *success = 0; - UNLOCK(TB_mutex); - return 0; - } - // Memory barrier to ensure ptr->ready = 1 is not reordered. -#ifdef _MSC_VER - _ReadWriteBarrier(); + void clear() { + std::memset(hashTable, 0, sizeof(hashTable)); + wdlTable.clear(); + dtzTable.clear(); + } + size_t size() const { return wdlTable.size(); } + void insert(const std::vector& pieces); +}; + +HashTable EntryTable; + +class TBFile : public std::ifstream { + + std::string fname; + +public: + // Look for and open the file among the Paths directories where the .rtbw + // and .rtbz files can be found. Multiple directories are separated by ";" + // on Windows and by ":" on Unix-based operating systems. + // + // Example: + // C:\tb\wdl345;C:\tb\wdl6;D:\tb\dtz345;D:\tb\dtz6 + static std::string Paths; + + TBFile(const std::string& f) { + +#ifndef _WIN32 + const char SepChar = ':'; #else - __asm__ __volatile__ ("" ::: "memory"); + const char SepChar = ';'; #endif - ptr->ready = 1; + std::stringstream ss(Paths); + std::string path; + + while (std::getline(ss, path, SepChar)) { + fname = path + "/" + f; + std::ifstream::open(fname); + if (is_open()) + return; + } } - UNLOCK(TB_mutex); - } - int bside, mirror, cmirror; - if (!ptr->symmetric) { - if (key != ptr->key) { - cmirror = 8; - mirror = 0x38; - bside = (pos.side_to_move() == WHITE); - } else { - cmirror = mirror = 0; - bside = !(pos.side_to_move() == WHITE); + // Memory map the file and check it. File should be already open and will be + // closed after mapping. + uint8_t* map(void** baseAddress, uint64_t* mapping, const uint8_t* TB_MAGIC) { + + assert(is_open()); + + close(); // Need to re-open to get native file descriptor + +#ifndef _WIN32 + struct stat statbuf; + int fd = ::open(fname.c_str(), O_RDONLY); + + if (fd == -1) + return *baseAddress = nullptr, nullptr; + + fstat(fd, &statbuf); + *mapping = statbuf.st_size; + *baseAddress = mmap(nullptr, statbuf.st_size, PROT_READ, MAP_SHARED, fd, 0); + ::close(fd); + + if (*baseAddress == MAP_FAILED) { + std::cerr << "Could not mmap() " << fname << std::endl; + exit(1); + } +#else + HANDLE fd = CreateFile(fname.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (fd == INVALID_HANDLE_VALUE) + return *baseAddress = nullptr, nullptr; + + DWORD size_high; + DWORD size_low = GetFileSize(fd, &size_high); + HANDLE mmap = CreateFileMapping(fd, nullptr, PAGE_READONLY, size_high, size_low, nullptr); + CloseHandle(fd); + + if (!mmap) { + std::cerr << "CreateFileMapping() failed" << std::endl; + exit(1); + } + + *mapping = (uint64_t)mmap; + *baseAddress = MapViewOfFile(mmap, FILE_MAP_READ, 0, 0, 0); + + if (!*baseAddress) { + std::cerr << "MapViewOfFile() failed, name = " << fname + << ", error = " << GetLastError() << std::endl; + exit(1); + } +#endif + uint8_t* data = (uint8_t*)*baseAddress; + + if ( *data++ != *TB_MAGIC++ + || *data++ != *TB_MAGIC++ + || *data++ != *TB_MAGIC++ + || *data++ != *TB_MAGIC) { + std::cerr << "Corrupted table in file " << fname << std::endl; + unmap(*baseAddress, *mapping); + return *baseAddress = nullptr, nullptr; + } + + return data; } - } else { - cmirror = pos.side_to_move() == WHITE ? 0 : 8; - mirror = pos.side_to_move() == WHITE ? 0 : 0x38; - bside = 0; - } - // p[i] is to contain the square 0-63 (A1-H8) for a piece of type - // pc[i] ^ cmirror, where 1 = white pawn, ..., 14 = black king. - // Pieces of the same type are guaranteed to be consecutive. - if (!ptr->has_pawns) { - struct TBEntry_piece *entry = (struct TBEntry_piece *)ptr; - ubyte *pc = entry->pieces[bside]; - for (i = 0; i < entry->num;) { - Bitboard bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), - (PieceType)(pc[i] & 0x07)); - do { - p[i++] = pop_lsb(&bb); - } while (bb); + static void unmap(void* baseAddress, uint64_t mapping) { + +#ifndef _WIN32 + munmap(baseAddress, mapping); +#else + UnmapViewOfFile(baseAddress); + CloseHandle((HANDLE)mapping); +#endif } - idx = encode_piece(entry, entry->norm[bside], p, entry->factor[bside]); - res = decompress_pairs(entry->precomp[bside], idx); - } else { - struct TBEntry_pawn *entry = (struct TBEntry_pawn *)ptr; - int k = entry->file[0].pieces[0][0] ^ cmirror; - Bitboard bb = pos.pieces((Color)(k >> 3), (PieceType)(k & 0x07)); - i = 0; - do { - p[i++] = pop_lsb(&bb) ^ mirror; - } while (bb); - int f = pawn_file(entry, p); - ubyte *pc = entry->file[f].pieces[bside]; - for (; i < entry->num;) { - bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), - (PieceType)(pc[i] & 0x07)); - do { - p[i++] = pop_lsb(&bb) ^ mirror; - } while (bb); +}; + +std::string TBFile::Paths; + +WDLEntry::WDLEntry(const std::string& code) { + + StateInfo st; + Position pos; + + memset(this, 0, sizeof(WDLEntry)); + + ready = false; + key = pos.set(code, WHITE, &st).material_key(); + pieceCount = popcount(pos.pieces()); + hasPawns = pos.pieces(PAWN); + + for (Color c = WHITE; c <= BLACK; ++c) + for (PieceType pt = PAWN; pt < KING; ++pt) + if (popcount(pos.pieces(c, pt)) == 1) + hasUniquePieces = true; + + if (hasPawns) { + // Set the leading color. In case both sides have pawns the leading color + // is the side with less pawns because this leads to better compression. + bool c = !pos.count(BLACK) + || ( pos.count(WHITE) + && pos.count(BLACK) >= pos.count(WHITE)); + + pawnTable.pawnCount[0] = pos.count(c ? WHITE : BLACK); + pawnTable.pawnCount[1] = pos.count(c ? BLACK : WHITE); } - idx = encode_pawn(entry, entry->file[f].norm[bside], p, entry->file[f].factor[bside]); - res = decompress_pairs(entry->file[f].precomp[bside], idx); - } - return ((int)res) - 2; + key2 = pos.set(code, BLACK, &st).material_key(); } -static int probe_dtz_table(Position& pos, int wdl, int *success) -{ - struct TBEntry *ptr; - uint64 idx; - int i, res; - int p[TBPIECES]; - - // Obtain the position's material signature key. - uint64 key = pos.material_key(); - - if (DTZ_table[0].key1 != key && DTZ_table[0].key2 != key) { - for (i = 1; i < DTZ_ENTRIES; i++) - if (DTZ_table[i].key1 == key) break; - if (i < DTZ_ENTRIES) { - struct DTZTableEntry table_entry = DTZ_table[i]; - for (; i > 0; i--) - DTZ_table[i] = DTZ_table[i - 1]; - DTZ_table[0] = table_entry; - } else { - struct TBHashEntry *ptr2 = TB_hash[key >> (64 - TBHASHBITS)]; - for (i = 0; i < HSHMAX; i++) - if (ptr2[i].key == key) break; - if (i == HSHMAX) { - *success = 0; - return 0; - } - ptr = ptr2[i].ptr; - char str[16]; - int mirror = (ptr->key != key); - prt_str(pos, str, mirror); - if (DTZ_table[DTZ_ENTRIES - 1].entry) - free_dtz_entry(DTZ_table[DTZ_ENTRIES-1].entry); - for (i = DTZ_ENTRIES - 1; i > 0; i--) - DTZ_table[i] = DTZ_table[i - 1]; - load_dtz_table(str, calc_key(pos, mirror), calc_key(pos, !mirror)); - } - } +WDLEntry::~WDLEntry() { - ptr = DTZ_table[0].entry; - if (!ptr) { - *success = 0; - return 0; - } + if (baseAddress) + TBFile::unmap(baseAddress, mapping); + + for (int i = 0; i < 2; ++i) + if (hasPawns) + for (File f = FILE_A; f <= FILE_D; ++f) + delete pawnTable.file[i][f].precomp; + else + delete pieceTable[i].precomp; +} + +DTZEntry::DTZEntry(const WDLEntry& wdl) { + + memset(this, 0, sizeof(DTZEntry)); - int bside, mirror, cmirror; - if (!ptr->symmetric) { - if (key != ptr->key) { - cmirror = 8; - mirror = 0x38; - bside = (pos.side_to_move() == WHITE); - } else { - cmirror = mirror = 0; - bside = !(pos.side_to_move() == WHITE); + ready = false; + key = wdl.key; + key2 = wdl.key2; + pieceCount = wdl.pieceCount; + hasPawns = wdl.hasPawns; + hasUniquePieces = wdl.hasUniquePieces; + + if (hasPawns) { + pawnTable.pawnCount[0] = wdl.pawnTable.pawnCount[0]; + pawnTable.pawnCount[1] = wdl.pawnTable.pawnCount[1]; } - } else { - cmirror = pos.side_to_move() == WHITE ? 0 : 8; - mirror = pos.side_to_move() == WHITE ? 0 : 0x38; - bside = 0; - } +} + +DTZEntry::~DTZEntry() { + + if (baseAddress) + TBFile::unmap(baseAddress, mapping); + + if (hasPawns) + for (File f = FILE_A; f <= FILE_D; ++f) + delete pawnTable.file[f].precomp; + else + delete pieceTable.precomp; +} + +void HashTable::insert(const std::vector& pieces) { + + std::string code; + + for (PieceType pt : pieces) + code += PieceToChar[pt]; + + TBFile file(code.insert(code.find('K', 1), "v") + ".rtbw"); // KRK -> KRvK + + if (!file.is_open()) // Only WDL file is checked + return; + + file.close(); + + MaxCardinality = std::max((int)pieces.size(), MaxCardinality); + + wdlTable.emplace_back(code); + dtzTable.emplace_back(wdlTable.back()); + + insert(wdlTable.back().key , &wdlTable.back(), &dtzTable.back()); + insert(wdlTable.back().key2, &wdlTable.back(), &dtzTable.back()); +} + +// TB tables are compressed with canonical Huffman code. The compressed data is divided into +// blocks of size d->sizeofBlock, and each block stores a variable number of symbols. +// Each symbol represents either a WDL or a (remapped) DTZ value, or a pair of other symbols +// (recursively). If you keep expanding the symbols in a block, you end up with up to 65536 +// WDL or DTZ values. Each symbol represents up to 256 values and will correspond after +// Huffman coding to at least 1 bit. So a block of 32 bytes corresponds to at most +// 32 x 8 x 256 = 65536 values. This maximum is only reached for tables that consist mostly +// of draws or mostly of wins, but such tables are actually quite common. In principle, the +// blocks in WDL tables are 64 bytes long (and will be aligned on cache lines). But for +// mostly-draw or mostly-win tables this can leave many 64-byte blocks only half-filled, so +// in such cases blocks are 32 bytes long. The blocks of DTZ tables are up to 1024 bytes long. +// The generator picks the size that leads to the smallest table. The "book" of symbols and +// Huffman codes is the same for all blocks in the table. A non-symmetric pawnless TB file +// will have one table for wtm and one for btm, a TB file with pawns will have tables per +// file a,b,c,d also in this case one set for wtm and one for btm. +int decompress_pairs(PairsData* d, uint64_t idx) { + + // Special case where all table positions store the same value + if (d->flags & TBFlag::SingleValue) + return d->minSymLen; + + // First we need to locate the right block that stores the value at index "idx". + // Because each block n stores blockLength[n] + 1 values, the index i of the block + // that contains the value at position idx is: + // + // for (i = -1, sum = 0; sum <= idx; i++) + // sum += blockLength[i + 1] + 1; + // + // This can be slow, so we use SparseIndex[] populated with a set of SparseEntry that + // point to known indices into blockLength[]. Namely SparseIndex[k] is a SparseEntry + // that stores the blockLength[] index and the offset within that block of the value + // with index I(k), where: + // + // I(k) = k * d->span + d->span / 2 (1) + + // First step is to get the 'k' of the I(k) nearest to our idx, using definition (1) + uint32_t k = idx / d->span; + + // Then we read the corresponding SparseIndex[] entry + uint32_t block = number(&d->sparseIndex[k].block); + int offset = number(&d->sparseIndex[k].offset); + + // Now compute the difference idx - I(k). From definition of k we know that + // + // idx = k * d->span + idx % d->span (2) + // + // So from (1) and (2) we can compute idx - I(K): + int diff = idx % d->span - d->span / 2; + + // Sum the above to offset to find the offset corresponding to our idx + offset += diff; + + // Move to previous/next block, until we reach the correct block that contains idx, + // that is when 0 <= offset <= d->blockLength[block] + while (offset < 0) + offset += d->blockLength[--block] + 1; + + while (offset > d->blockLength[block]) + offset -= d->blockLength[block++] + 1; + + // Finally, we find the start address of our block of canonical Huffman symbols + uint32_t* ptr = (uint32_t*)(d->data + block * d->sizeofBlock); + + // Read the first 64 bits in our block, this is a (truncated) sequence of + // unknown number of symbols of unknown length but we know the first one + // is at the beginning of this 64 bits sequence. + uint64_t buf64 = number(ptr); ptr += 2; + int buf64Size = 64; + Sym sym; + + while (true) { + int len = 0; // This is the symbol length - d->min_sym_len + + // Now get the symbol length. For any symbol s64 of length l right-padded + // to 64 bits we know that d->base64[l-1] >= s64 >= d->base64[l] so we + // can find the symbol length iterating through base64[]. + while (buf64 < d->base64[len]) + ++len; + + // All the symbols of a given length are consecutive integers (numerical + // sequence property), so we can compute the offset of our symbol of + // length len, stored at the beginning of buf64. + sym = (buf64 - d->base64[len]) >> (64 - len - d->minSymLen); + + // Now add the value of the lowest symbol of length len to get our symbol + sym += number(&d->lowestSym[len]); + + // If our offset is within the number of values represented by symbol sym + // we are done... + if (offset < d->symlen[sym] + 1) + break; - if (!ptr->has_pawns) { - struct DTZEntry_piece *entry = (struct DTZEntry_piece *)ptr; - if ((entry->flags & 1) != bside && !entry->symmetric) { - *success = -1; - return 0; + // ...otherwise update the offset and continue to iterate + offset -= d->symlen[sym] + 1; + len += d->minSymLen; // Get the real length + buf64 <<= len; // Consume the just processed symbol + buf64Size -= len; + + if (buf64Size <= 32) { // Refill the buffer + buf64Size += 32; + buf64 |= (uint64_t)number(ptr++) << (64 - buf64Size); + } } - ubyte *pc = entry->pieces; - for (i = 0; i < entry->num;) { - Bitboard bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), - (PieceType)(pc[i] & 0x07)); - do { - p[i++] = pop_lsb(&bb); - } while (bb); + + // Ok, now we have our symbol that expands into d->symlen[sym] + 1 symbols. + // We binary-search for our value recursively expanding into the left and + // right child symbols until we reach a leaf node where symlen[sym] + 1 == 1 + // that will store the value we need. + while (d->symlen[sym]) { + + Sym left = d->btree[sym].get(); + + // If a symbol contains 36 sub-symbols (d->symlen[sym] + 1 = 36) and + // expands in a pair (d->symlen[left] = 23, d->symlen[right] = 11), then + // we know that, for instance the ten-th value (offset = 10) will be on + // the left side because in Recursive Pairing child symbols are adjacent. + if (offset < d->symlen[left] + 1) + sym = left; + else { + offset -= d->symlen[left] + 1; + sym = d->btree[sym].get(); + } } - idx = encode_piece((struct TBEntry_piece *)entry, entry->norm, p, entry->factor); - res = decompress_pairs(entry->precomp, idx); - - if (entry->flags & 2) - res = entry->map[entry->map_idx[wdl_to_map[wdl + 2]] + res]; - - if (!(entry->flags & pa_flags[wdl + 2]) || (wdl & 1)) - res *= 2; - } else { - struct DTZEntry_pawn *entry = (struct DTZEntry_pawn *)ptr; - int k = entry->file[0].pieces[0] ^ cmirror; - Bitboard bb = pos.pieces((Color)(k >> 3), (PieceType)(k & 0x07)); - i = 0; + + return d->btree[sym].get(); +} + +bool check_dtz_stm(WDLEntry*, int, File) { return true; } + +bool check_dtz_stm(DTZEntry* entry, int stm, File f) { + + int flags = entry->hasPawns ? entry->pawnTable.file[f].precomp->flags + : entry->pieceTable.precomp->flags; + + return (flags & TBFlag::STM) == stm + || ((entry->key == entry->key2) && !entry->hasPawns); +} + +// DTZ scores are sorted by frequency of occurrence and then assigned the +// values 0, 1, 2, ... in order of decreasing frequency. This is done for each +// of the four WDLScore values. The mapping information necessary to reconstruct +// the original values is stored in the TB file and read during map[] init. +WDLScore map_score(WDLEntry*, File, int value, WDLScore) { return WDLScore(value - 2); } + +int map_score(DTZEntry* entry, File f, int value, WDLScore wdl) { + + const int WDLMap[] = { 1, 3, 0, 2, 0 }; + + int flags = entry->hasPawns ? entry->pawnTable.file[f].precomp->flags + : entry->pieceTable.precomp->flags; + + uint8_t* map = entry->hasPawns ? entry->pawnTable.map + : entry->pieceTable.map; + + uint16_t* idx = entry->hasPawns ? entry->pawnTable.file[f].map_idx + : entry->pieceTable.map_idx; + if (flags & TBFlag::Mapped) + value = map[idx[WDLMap[wdl + 2]] + value]; + + // DTZ tables store distance to zero in number of moves or plies. We + // want to return plies, so we have convert to plies when needed. + if ( (wdl == WDLWin && !(flags & TBFlag::WinPlies)) + || (wdl == WDLLoss && !(flags & TBFlag::LossPlies)) + || wdl == WDLCursedWin + || wdl == WDLBlessedLoss) + value *= 2; + + return value + 1; +} + +// Compute a unique index out of a position and use it to probe the TB file. To +// encode k pieces of same type and color, first sort the pieces by square in +// ascending order s1 <= s2 <= ... <= sk then compute the unique index as: +// +// idx = Binomial[1][s1] + Binomial[2][s2] + ... + Binomial[k][sk] +// +template::type> +T do_probe_table(const Position& pos, Entry* entry, WDLScore wdl, ProbeState* result) { + + const bool IsWDL = std::is_same::value; + + Square squares[TBPIECES]; + Piece pieces[TBPIECES]; + uint64_t idx; + int next = 0, size = 0, leadPawnsCnt = 0; + PairsData* d; + Bitboard b, leadPawns = 0; + File tbFile = FILE_A; + + // A given TB entry like KRK has associated two material keys: KRvk and Kvkr. + // If both sides have the same pieces keys are equal. In this case TB tables + // only store the 'white to move' case, so if the position to lookup has black + // to move, we need to switch the color and flip the squares before to lookup. + bool symmetricBlackToMove = (entry->key == entry->key2 && pos.side_to_move()); + + // TB files are calculated for white as stronger side. For instance we have + // KRvK, not KvKR. A position where stronger side is white will have its + // material key == entry->key, otherwise we have to switch the color and + // flip the squares before to lookup. + bool blackStronger = (pos.material_key() != entry->key); + + int flipColor = (symmetricBlackToMove || blackStronger) * 8; + int flipSquares = (symmetricBlackToMove || blackStronger) * 070; + int stm = (symmetricBlackToMove || blackStronger) ^ pos.side_to_move(); + + // For pawns, TB files store 4 separate tables according if leading pawn is on + // file a, b, c or d after reordering. The leading pawn is the one with maximum + // MapPawns[] value, that is the one most toward the edges and with lowest rank. + if (entry->hasPawns) { + + // In all the 4 tables, pawns are at the beginning of the piece sequence and + // their color is the reference one. So we just pick the first one. + Piece pc = Piece(item(entry->pawnTable, 0, 0).precomp->pieces[0] ^ flipColor); + + assert(type_of(pc) == PAWN); + + leadPawns = b = pos.pieces(color_of(pc), PAWN); + do + squares[size++] = pop_lsb(&b) ^ flipSquares; + while (b); + + leadPawnsCnt = size; + + std::swap(squares[0], *std::max_element(squares, squares + leadPawnsCnt, pawns_comp)); + + tbFile = file_of(squares[0]); + if (tbFile > FILE_D) + tbFile = file_of(squares[0] ^ 7); // Horizontal flip: SQ_H1 -> SQ_A1 + + d = item(entry->pawnTable , stm, tbFile).precomp; + } else + d = item(entry->pieceTable, stm, tbFile).precomp; + + // DTZ tables are one-sided, i.e. they store positions only for white to + // move or only for black to move, so check for side to move to be stm, + // early exit otherwise. + if (!IsWDL && !check_dtz_stm(entry, stm, tbFile)) + return *result = CHANGE_STM, T(); + + // Now we are ready to get all the position pieces (but the lead pawns) and + // directly map them to the correct color and square. + b = pos.pieces() ^ leadPawns; do { - p[i++] = pop_lsb(&bb) ^ mirror; - } while (bb); - int f = pawn_file((struct TBEntry_pawn *)entry, p); - if ((entry->flags[f] & 1) != bside) { - *success = -1; - return 0; + Square s = pop_lsb(&b); + squares[size] = s ^ flipSquares; + pieces[size++] = Piece(pos.piece_on(s) ^ flipColor); + } while (b); + + assert(size >= 2); + + // Then we reorder the pieces to have the same sequence as the one stored + // in precomp->pieces[i]: the sequence that ensures the best compression. + for (int i = leadPawnsCnt; i < size; ++i) + for (int j = i; j < size; ++j) + if (d->pieces[i] == pieces[j]) + { + std::swap(pieces[i], pieces[j]); + std::swap(squares[i], squares[j]); + break; + } + + // Now we map again the squares so that the square of the lead piece is in + // the triangle A1-D1-D4. + if (file_of(squares[0]) > FILE_D) + for (int i = 0; i < size; ++i) + squares[i] ^= 7; // Horizontal flip: SQ_H1 -> SQ_A1 + + // Encode leading pawns starting with the one with minimum MapPawns[] and + // proceeding in ascending order. + if (entry->hasPawns) { + idx = LeadPawnIdx[leadPawnsCnt][squares[0]]; + + std::sort(squares + 1, squares + leadPawnsCnt, pawns_comp); + + for (int i = 1; i < leadPawnsCnt; ++i) + idx += Binomial[i][MapPawns[squares[i]]]; + + goto encode_remaining; // With pawns we have finished special treatments } - ubyte *pc = entry->file[f].pieces; - for (; i < entry->num;) { - bb = pos.pieces((Color)((pc[i] ^ cmirror) >> 3), - (PieceType)(pc[i] & 0x07)); - do { - p[i++] = pop_lsb(&bb) ^ mirror; - } while (bb); + + // In positions withouth pawns, we further flip the squares to ensure leading + // piece is below RANK_5. + if (rank_of(squares[0]) > RANK_4) + for (int i = 0; i < size; ++i) + squares[i] ^= 070; // Vertical flip: SQ_A8 -> SQ_A1 + + // Look for the first piece of the leading group not on the A1-D4 diagonal + // and ensure it is mapped below the diagonal. + for (int i = 0; i < d->groupLen[0]; ++i) { + if (!off_A1H8(squares[i])) + continue; + + if (off_A1H8(squares[i]) > 0) // A1-H8 diagonal flip: SQ_A3 -> SQ_C3 + for (int j = i; j < size; ++j) + squares[j] = Square(((squares[j] >> 3) | (squares[j] << 3)) & 63); + break; } - idx = encode_pawn((struct TBEntry_pawn *)entry, entry->file[f].norm, p, entry->file[f].factor); - res = decompress_pairs(entry->file[f].precomp, idx); - if (entry->flags[f] & 2) - res = entry->map[entry->map_idx[f][wdl_to_map[wdl + 2]] + res]; + // Encode the leading group. + // + // Suppose we have KRvK. Let's say the pieces are on square numbers wK, wR + // and bK (each 0...63). The simplest way to map this position to an index + // is like this: + // + // index = wK * 64 * 64 + wR * 64 + bK; + // + // But this way the TB is going to have 64*64*64 = 262144 positions, with + // lots of positions being equivalent (because they are mirrors of each + // other) and lots of positions being invalid (two pieces on one square, + // adjacent kings, etc.). + // Usually the first step is to take the wK and bK together. There are just + // 462 ways legal and not-mirrored ways to place the wK and bK on the board. + // Once we have placed the wK and bK, there are 62 squares left for the wR + // Mapping its square from 0..63 to available squares 0..61 can be done like: + // + // wR -= (wR > wK) + (wR > bK); + // + // In words: if wR "comes later" than wK, we deduct 1, and the same if wR + // "comes later" than bK. In case of two same pieces like KRRvK we want to + // place the two Rs "together". If we have 62 squares left, we can place two + // Rs "together" in 62 * 61 / 2 ways (we divide by 2 because rooks can be + // swapped and still get the same position.) + // + // In case we have at least 3 unique pieces (inlcuded kings) we encode them + // together. + if (entry->hasUniquePieces) { + + int adjust1 = squares[1] > squares[0]; + int adjust2 = (squares[2] > squares[0]) + (squares[2] > squares[1]); + + // First piece is below a1-h8 diagonal. MapA1D1D4[] maps the b1-d1-d3 + // triangle to 0...5. There are 63 squares for second piece and and 62 + // (mapped to 0...61) for the third. + if (off_A1H8(squares[0])) + idx = ( MapA1D1D4[squares[0]] * 63 + + (squares[1] - adjust1)) * 62 + + squares[2] - adjust2; + + // First piece is on a1-h8 diagonal, second below: map this occurence to + // 6 to differentiate from the above case, rank_of() maps a1-d4 diagonal + // to 0...3 and finally MapB1H1H7[] maps the b1-h1-h7 triangle to 0..27. + else if (off_A1H8(squares[1])) + idx = ( 6 * 63 + rank_of(squares[0]) * 28 + + MapB1H1H7[squares[1]]) * 62 + + squares[2] - adjust2; + + // First two pieces are on a1-h8 diagonal, third below + else if (off_A1H8(squares[2])) + idx = 6 * 63 * 62 + 4 * 28 * 62 + + rank_of(squares[0]) * 7 * 28 + + (rank_of(squares[1]) - adjust1) * 28 + + MapB1H1H7[squares[2]]; + + // All 3 pieces on the diagonal a1-h8 + else + idx = 6 * 63 * 62 + 4 * 28 * 62 + 4 * 7 * 28 + + rank_of(squares[0]) * 7 * 6 + + (rank_of(squares[1]) - adjust1) * 6 + + (rank_of(squares[2]) - adjust2); + } else + // We don't have at least 3 unique pieces, like in KRRvKBB, just map + // the kings. + idx = MapKK[MapA1D1D4[squares[0]]][squares[1]]; + +encode_remaining: + idx *= d->groupIdx[0]; + Square* groupSq = squares + d->groupLen[0]; + + // Encode remainig pawns then pieces according to square, in ascending order + bool remainingPawns = entry->hasPawns && entry->pawnTable.pawnCount[1]; + + while (d->groupLen[++next]) + { + std::sort(groupSq, groupSq + d->groupLen[next]); + uint64_t n = 0; + + // Map down a square if "comes later" than a square in the previous + // groups (similar to what done earlier for leading group pieces). + for (int i = 0; i < d->groupLen[next]; ++i) + { + auto f = [&](Square s) { return groupSq[i] > s; }; + auto adjust = std::count_if(squares, groupSq, f); + n += Binomial[i + 1][groupSq[i] - adjust - 8 * remainingPawns]; + } - if (!(entry->flags[f] & pa_flags[wdl + 2]) || (wdl & 1)) - res *= 2; - } + remainingPawns = false; + idx += n * d->groupIdx[next]; + groupSq += d->groupLen[next]; + } - return res; + // Now that we have the index, decompress the pair and get the score + return map_score(entry, tbFile, decompress_pairs(d, idx), wdl); } -// Add underpromotion captures to list of captures. -static ExtMove *add_underprom_caps(Position& pos, ExtMove *stack, ExtMove *end) -{ - ExtMove *moves, *extra = end; - - for (moves = stack; moves < end; moves++) { - Move move = moves->move; - if (type_of(move) == PROMOTION && !pos.empty(to_sq(move))) { - (*extra++).move = (Move)(move - (1 << 12)); - (*extra++).move = (Move)(move - (2 << 12)); - (*extra++).move = (Move)(move - (3 << 12)); +// Group together pieces that will be encoded together. The general rule is that +// a group contains pieces of same type and color. The exception is the leading +// group that, in case of positions withouth pawns, can be formed by 3 different +// pieces (default) or by the king pair when there is not a unique piece apart +// from the kings. When there are pawns, pawns are always first in pieces[]. +// +// As example KRKN -> KRK + N, KNNK -> KK + NN, KPPKP -> P + PP + K + K +// +// The actual grouping depends on the TB generator and can be inferred from the +// sequence of pieces in piece[] array. +template +void set_groups(T& e, PairsData* d, int order[], File f) { + + int n = 0, firstLen = e.hasPawns ? 0 : e.hasUniquePieces ? 3 : 2; + d->groupLen[n] = 1; + + // Number of pieces per group is stored in groupLen[], for instance in KRKN + // the encoder will default on '111', so groupLen[] will be (3, 1). + for (int i = 1; i < e.pieceCount; ++i) + if (--firstLen > 0 || d->pieces[i] == d->pieces[i - 1]) + d->groupLen[n]++; + else + d->groupLen[++n] = 1; + + d->groupLen[++n] = 0; // Zero-terminated + + // The sequence in pieces[] defines the groups, but not the order in which + // they are encoded. If the pieces in a group g can be combined on the board + // in N(g) different ways, then the position encoding will be of the form: + // + // g1 * N(g2) * N(g3) + g2 * N(g3) + g3 + // + // This ensures unique encoding for the whole position. The order of the + // groups is a per-table parameter and could not follow the canonical leading + // pawns/pieces -> remainig pawns -> remaining pieces. In particular the + // first group is at order[0] position and the remaining pawns, when present, + // are at order[1] position. + bool pp = e.hasPawns && e.pawnTable.pawnCount[1]; // Pawns on both sides + int next = pp ? 2 : 1; + int freeSquares = 64 - d->groupLen[0] - (pp ? d->groupLen[1] : 0); + uint64_t idx = 1; + + for (int k = 0; next < n || k == order[0] || k == order[1]; ++k) + if (k == order[0]) // Leading pawns or pieces + { + d->groupIdx[0] = idx; + idx *= e.hasPawns ? LeadPawnsSize[d->groupLen[0]][f] + : e.hasUniquePieces ? 31332 : 462; + } + else if (k == order[1]) // Remaining pawns + { + d->groupIdx[1] = idx; + idx *= Binomial[d->groupLen[1]][48 - d->groupLen[0]]; + } + else // Remainig pieces + { + d->groupIdx[next] = idx; + idx *= Binomial[d->groupLen[next]][freeSquares]; + freeSquares -= d->groupLen[next++]; + } + + d->groupIdx[n] = idx; +} + +// In Recursive Pairing each symbol represents a pair of childern symbols. So +// read d->btree[] symbols data and expand each one in his left and right child +// symbol until reaching the leafs that represent the symbol value. +uint8_t set_symlen(PairsData* d, Sym s, std::vector& visited) { + + visited[s] = true; // We can set it now because tree is acyclic + Sym sr = d->btree[s].get(); + + if (sr == 0xFFF) + return 0; + + Sym sl = d->btree[s].get(); + + if (!visited[sl]) + d->symlen[sl] = set_symlen(d, sl, visited); + + if (!visited[sr]) + d->symlen[sr] = set_symlen(d, sr, visited); + + return d->symlen[sl] + d->symlen[sr] + 1; +} + +uint8_t* set_sizes(PairsData* d, uint8_t* data) { + + d->flags = *data++; + + if (d->flags & TBFlag::SingleValue) { + d->blocksNum = d->blockLengthSize = 0; + d->span = d->sparseIndexSize = 0; // Broken MSVC zero-init + d->minSymLen = *data++; // Here we store the single value + return data; + } + + // groupLen[] is a zero-terminated list of group lengths, the last groupIdx[] + // element stores the biggest index that is the tb size. + uint64_t tbSize = d->groupIdx[std::find(d->groupLen, d->groupLen + 7, 0) - d->groupLen]; + + d->sizeofBlock = 1ULL << *data++; + d->span = 1ULL << *data++; + d->sparseIndexSize = (tbSize + d->span - 1) / d->span; // Round up + int padding = number(data++); + d->blocksNum = number(data); data += sizeof(uint32_t); + d->blockLengthSize = d->blocksNum + padding; // Padded to ensure SparseIndex[] + // does not point out of range. + d->maxSymLen = *data++; + d->minSymLen = *data++; + d->lowestSym = (Sym*)data; + d->base64.resize(d->maxSymLen - d->minSymLen + 1); + + // The canonical code is ordered such that longer symbols (in terms of + // the number of bits of their Huffman code) have lower numeric value, + // so that d->lowestSym[i] >= d->lowestSym[i+1] (when read as LittleEndian). + // Starting from this we compute a base64[] table indexed by symbol length + // and containing 64 bit values so that d->base64[i] >= d->base64[i+1]. + // See http://www.eecs.harvard.edu/~michaelm/E210/huffman.pdf + for (int i = d->base64.size() - 2; i >= 0; --i) { + d->base64[i] = (d->base64[i + 1] + number(&d->lowestSym[i]) + - number(&d->lowestSym[i + 1])) / 2; + + assert(d->base64[i] * 2 >= d->base64[i+1]); } - } - return extra; + // Now left-shift by an amount so that d->base64[i] gets shifted 1 bit more + // than d->base64[i+1] and given the above assert condition, we ensure that + // d->base64[i] >= d->base64[i+1]. Moreover for any symbol s64 of length i + // and right-padded to 64 bits holds d->base64[i-1] >= s64 >= d->base64[i]. + for (size_t i = 0; i < d->base64.size(); ++i) + d->base64[i] <<= 64 - i - d->minSymLen; // Right-padding to 64 bits + + data += d->base64.size() * sizeof(Sym); + d->symlen.resize(number(data)); data += sizeof(uint16_t); + d->btree = (LR*)data; + + // The comrpession scheme used is "Recursive Pairing", that replaces the most + // frequent adjacent pair of symbols in the source message by a new symbol, + // reevaluating the frequencies of all of the symbol pairs with respect to + // the extended alphabet, and then repeating the process. + // See http://www.larsson.dogma.net/dcc99.pdf + std::vector visited(d->symlen.size()); + + for (Sym sym = 0; sym < d->symlen.size(); ++sym) + if (!visited[sym]) + d->symlen[sym] = set_symlen(d, sym, visited); + + return data + d->symlen.size() * sizeof(LR) + (d->symlen.size() & 1); } -static int probe_ab(Position& pos, int alpha, int beta, int *success) -{ - int v; - ExtMove stack[64]; - ExtMove *moves, *end; - StateInfo st; - - // Generate (at least) all legal non-ep captures including (under)promotions. - // It is OK to generate more, as long as they are filtered out below. - if (!pos.checkers()) { - end = generate(pos, stack); - // Since underpromotion captures are not included, we need to add them. - end = add_underprom_caps(pos, stack, end); - } else - end = generate(pos, stack); - - for (moves = stack; moves < end; moves++) { - Move capture = moves->move; - if (!pos.capture(capture) || type_of(capture) == ENPASSANT - || !pos.legal(capture)) - continue; - pos.do_move(capture, st, pos.gives_check(capture)); - v = -probe_ab(pos, -beta, -alpha, success); - pos.undo_move(capture); - if (*success == 0) return 0; - if (v > alpha) { - if (v >= beta) { - *success = 2; - return v; - } - alpha = v; +template +uint8_t* set_dtz_map(WDLEntry&, T&, uint8_t*, File) { return nullptr; } + +template +uint8_t* set_dtz_map(DTZEntry&, T& p, uint8_t* data, File maxFile) { + + p.map = data; + + for (File f = FILE_A; f <= maxFile; ++f) { + if (item(p, 0, f).precomp->flags & TBFlag::Mapped) + for (int i = 0; i < 4; ++i) { // Sequence like 3,x,x,x,1,x,0,2,x,x + item(p, 0, f).map_idx[i] = (uint16_t)(data - p.map + 1); + data += *data + 1; + } } - } - v = probe_wdl_table(pos, success); - if (*success == 0) return 0; - if (alpha >= v) { - *success = 1 + (alpha > 0); - return alpha; - } else { - *success = 1; - return v; - } + return data += (uintptr_t)data & 1; // Word alignment } -// Probe the WDL table for a particular position. -// If *success != 0, the probe was successful. -// The return value is from the point of view of the side to move: -// -2 : loss -// -1 : loss, but draw under 50-move rule -// 0 : draw -// 1 : win, but draw under 50-move rule -// 2 : win -int Tablebases::probe_wdl(Position& pos, int *success) -{ - int v; +template +void do_init(Entry& e, T& p, uint8_t* data) { - *success = 1; - v = probe_ab(pos, -2, 2, success); + const bool IsWDL = std::is_same::value; - // If en passant is not possible, we are done. - if (pos.ep_square() == SQ_NONE) - return v; - if (!(*success)) return 0; - - // Now handle en passant. - int v1 = -3; - // Generate (at least) all legal en passant captures. - ExtMove stack[192]; - ExtMove *moves, *end; - StateInfo st; - - if (!pos.checkers()) - end = generate(pos, stack); - else - end = generate(pos, stack); - - for (moves = stack; moves < end; moves++) { - Move capture = moves->move; - if (type_of(capture) != ENPASSANT - || !pos.legal(capture)) - continue; - pos.do_move(capture, st, pos.gives_check(capture)); - int v0 = -probe_ab(pos, -2, 2, success); - pos.undo_move(capture); - if (*success == 0) return 0; - if (v0 > v1) v1 = v0; - } - if (v1 > -3) { - if (v1 >= v) v = v1; - else if (v == 0) { - // Check whether there is at least one legal non-ep move. - for (moves = stack; moves < end; moves++) { - Move capture = moves->move; - if (type_of(capture) == ENPASSANT) continue; - if (pos.legal(capture)) break; - } - if (moves == end && !pos.checkers()) { - end = generate(pos, end); - for (; moves < end; moves++) { - Move move = moves->move; - if (pos.legal(move)) - break; - } - } - // If not, then we are forced to play the losing ep capture. - if (moves == end) - v = v1; + PairsData* d; + + enum { Split = 1, HasPawns = 2 }; + + assert(e.hasPawns == !!(*data & HasPawns)); + assert((e.key != e.key2) == !!(*data & Split)); + + data++; // First byte stores flags + + const int Sides = IsWDL && (e.key != e.key2) ? 2 : 1; + const File MaxFile = e.hasPawns ? FILE_D : FILE_A; + + bool pp = e.hasPawns && e.pawnTable.pawnCount[1]; // Pawns on both sides + + assert(!pp || e.pawnTable.pawnCount[0]); + + for (File f = FILE_A; f <= MaxFile; ++f) { + + for (int i = 0; i < Sides; i++) + item(p, i, f).precomp = new PairsData(); + + int order[][2] = { { *data & 0xF, pp ? *(data + 1) & 0xF : 0xF }, + { *data >> 4, pp ? *(data + 1) >> 4 : 0xF } }; + data += 1 + pp; + + for (int k = 0; k < e.pieceCount; ++k, ++data) + for (int i = 0; i < Sides; i++) + item(p, i, f).precomp->pieces[k] = Piece(i ? *data >> 4 : *data & 0xF); + + for (int i = 0; i < Sides; ++i) + set_groups(e, item(p, i, f).precomp, order[i], f); } - } - return v; + data += (uintptr_t)data & 1; // Word alignment + + for (File f = FILE_A; f <= MaxFile; ++f) + for (int i = 0; i < Sides; i++) + data = set_sizes(item(p, i, f).precomp, data); + + if (!IsWDL) + data = set_dtz_map(e, p, data, MaxFile); + + for (File f = FILE_A; f <= MaxFile; ++f) + for (int i = 0; i < Sides; i++) { + (d = item(p, i, f).precomp)->sparseIndex = (SparseEntry*)data; + data += d->sparseIndexSize * sizeof(SparseEntry); + } + + for (File f = FILE_A; f <= MaxFile; ++f) + for (int i = 0; i < Sides; i++) { + (d = item(p, i, f).precomp)->blockLength = (uint16_t*)data; + data += d->blockLengthSize * sizeof(uint16_t); + } + + for (File f = FILE_A; f <= MaxFile; ++f) + for (int i = 0; i < Sides; i++) { + data = (uint8_t*)(((uintptr_t)data + 0x3F) & ~0x3F); // 64 byte alignment + (d = item(p, i, f).precomp)->data = data; + data += d->blocksNum * d->sizeofBlock; + } } -// This routine treats a position with en passant captures as one without. -static int probe_dtz_no_ep(Position& pos, int *success) -{ - int wdl, dtz; +template +void* init(Entry& e, const Position& pos) { - wdl = probe_ab(pos, -2, 2, success); - if (*success == 0) return 0; + const bool IsWDL = std::is_same::value; - if (wdl == 0) return 0; + static Mutex mutex; - if (*success == 2) - return wdl == 2 ? 1 : 101; + // Avoid a thread reads 'ready' == true while another is still in do_init(), + // this could happen due to compiler reordering. + if (e.ready.load(std::memory_order_acquire)) + return e.baseAddress; - ExtMove stack[192]; - ExtMove *moves, *end = NULL; - StateInfo st; + std::unique_lock lk(mutex); - if (wdl > 0) { - // Generate at least all legal non-capturing pawn moves - // including non-capturing promotions. - if (!pos.checkers()) - end = generate(pos, stack); - else - end = generate(pos, stack); - - for (moves = stack; moves < end; moves++) { - Move move = moves->move; - if (type_of(pos.moved_piece(move)) != PAWN || pos.capture(move) - || !pos.legal(move)) - continue; - pos.do_move(move, st, pos.gives_check(move)); - int v = -Tablebases::probe_wdl(pos, success); - pos.undo_move(move); - if (*success == 0) return 0; - if (v == wdl) - return v == 2 ? 1 : 101; + if (e.ready.load(std::memory_order_relaxed)) // Recheck under lock + return e.baseAddress; + + // Pieces strings in decreasing order for each color, like ("KPP","KR") + std::string fname, w, b; + for (PieceType pt = KING; pt >= PAWN; --pt) { + w += std::string(popcount(pos.pieces(WHITE, pt)), PieceToChar[pt]); + b += std::string(popcount(pos.pieces(BLACK, pt)), PieceToChar[pt]); } - } - dtz = 1 + probe_dtz_table(pos, wdl, success); - if (*success >= 0) { - if (wdl & 1) dtz += 100; - return wdl >= 0 ? dtz : -dtz; - } + const uint8_t TB_MAGIC[][4] = { { 0xD7, 0x66, 0x0C, 0xA5 }, + { 0x71, 0xE8, 0x23, 0x5D } }; + + fname = (e.key == pos.material_key() ? w + 'v' + b : b + 'v' + w) + + (IsWDL ? ".rtbw" : ".rtbz"); + + uint8_t* data = TBFile(fname).map(&e.baseAddress, &e.mapping, TB_MAGIC[IsWDL]); + if (data) + e.hasPawns ? do_init(e, e.pawnTable, data) : do_init(e, e.pieceTable, data); + + e.ready.store(true, std::memory_order_release); + return e.baseAddress; +} - if (wdl > 0) { - int best = 0xffff; - for (moves = stack; moves < end; moves++) { - Move move = moves->move; - if (pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN - || !pos.legal(move)) - continue; - pos.do_move(move, st, pos.gives_check(move)); - int v = -Tablebases::probe_dtz(pos, success); - pos.undo_move(move); - if (*success == 0) return 0; - if (v > 0 && v + 1 < best) - best = v + 1; +template::type> +T probe_table(const Position& pos, ProbeState* result, WDLScore wdl = WDLDraw) { + + if (!(pos.pieces() ^ pos.pieces(KING))) + return T(WDLDraw); // KvK + + E* entry = EntryTable.get(pos.material_key()); + + if (!entry || !init(*entry, pos)) + return *result = FAIL, T(); + + return do_probe_table(pos, entry, wdl, result); +} + +// For a position where the side to move has a winning capture it is not necessary +// to store a winning value so the generator treats such positions as "don't cares" +// and tries to assign to it a value that improves the compression ratio. Similarly, +// if the side to move has a drawing capture, then the position is at least drawn. +// If the position is won, then the TB needs to store a win value. But if the +// position is drawn, the TB may store a loss value if that is better for compression. +// All of this means that during probing, the engine must look at captures and probe +// their results and must probe the position itself. The "best" result of these +// probes is the correct result for the position. +// DTZ table don't store values when a following move is a zeroing winning move +// (winning capture or winning pawn move). Also DTZ store wrong values for positions +// where the best move is an ep-move (even if losing). So in all these cases set +// the state to ZEROING_BEST_MOVE. +template +WDLScore search(Position& pos, ProbeState* result) { + + WDLScore value, bestValue = WDLLoss; + StateInfo st; + + auto moveList = MoveList(pos); + size_t totalCount = moveList.size(), moveCount = 0; + + for (const Move& move : moveList) + { + if ( !pos.capture(move) + && (!CheckZeroingMoves || type_of(pos.moved_piece(move)) != PAWN)) + continue; + + moveCount++; + + pos.do_move(move, st); + value = -search(pos, result); + pos.undo_move(move); + + if (*result == FAIL) + return WDLDraw; + + if (value > bestValue) + { + bestValue = value; + + if (value >= WDLWin) + { + *result = ZEROING_BEST_MOVE; // Winning DTZ-zeroing move + return value; + } + } } - return best; - } else { - int best = -1; - if (!pos.checkers()) - end = generate(pos, stack); + + // In case we have already searched all the legal moves we don't have to probe + // the TB because the stored score could be wrong. For instance TB tables + // do not contain information on position with ep rights, so in this case + // the result of probe_wdl_table is wrong. Also in case of only capture + // moves, for instance here 4K3/4q3/6p1/2k5/6p1/8/8/8 w - - 0 7, we have to + // return with ZEROING_BEST_MOVE set. + bool noMoreMoves = (moveCount && moveCount == totalCount); + + if (noMoreMoves) + value = bestValue; else - end = generate(pos, stack); - for (moves = stack; moves < end; moves++) { - int v; - Move move = moves->move; - if (!pos.legal(move)) - continue; - pos.do_move(move, st, pos.gives_check(move)); - if (st.rule50 == 0) { - if (wdl == -2) v = -1; - else { - v = probe_ab(pos, 1, 2, success); - v = (v == 2) ? 0 : -101; + { + value = probe_table(pos, result); + + if (*result == FAIL) + return WDLDraw; + } + + // DTZ stores a "don't care" value if bestValue is a win + if (bestValue >= value) + return *result = ( bestValue > WDLDraw + || noMoreMoves ? ZEROING_BEST_MOVE : OK), bestValue; + + return *result = OK, value; +} + +} // namespace + +void Tablebases::init(const std::string& paths) { + + EntryTable.clear(); + MaxCardinality = 0; + TBFile::Paths = paths; + + if (paths.empty() || paths == "") + return; + + // MapB1H1H7[] encodes a square below a1-h8 diagonal to 0..27 + int code = 0; + for (Square s = SQ_A1; s <= SQ_H8; ++s) + if (off_A1H8(s) < 0) + MapB1H1H7[s] = code++; + + // MapA1D1D4[] encodes a square in the a1-d1-d4 triangle to 0..9 + std::vector diagonal; + code = 0; + for (Square s = SQ_A1; s <= SQ_D4; ++s) + if (off_A1H8(s) < 0 && file_of(s) <= FILE_D) + MapA1D1D4[s] = code++; + + else if (!off_A1H8(s) && file_of(s) <= FILE_D) + diagonal.push_back(s); + + // Diagonal squares are encoded as last ones + for (auto s : diagonal) + MapA1D1D4[s] = code++; + + // MapKK[] encodes all the 461 possible legal positions of two kings where + // the first is in the a1-d1-d4 triangle. If the first king is on the a1-d4 + // diagonal, the other one shall not to be above the a1-h8 diagonal. + std::vector> bothOnDiagonal; + code = 0; + for (int idx = 0; idx < 10; idx++) + for (Square s1 = SQ_A1; s1 <= SQ_D4; ++s1) + if (MapA1D1D4[s1] == idx && (idx || s1 == SQ_B1)) // SQ_B1 is mapped to 0 + { + for (Square s2 = SQ_A1; s2 <= SQ_H8; ++s2) + if ((PseudoAttacks[KING][s1] | s1) & s2) + continue; // Illegal position + + else if (!off_A1H8(s1) && off_A1H8(s2) > 0) + continue; // First on diagonal, second above + + else if (!off_A1H8(s1) && !off_A1H8(s2)) + bothOnDiagonal.push_back(std::make_pair(idx, s2)); + + else + MapKK[idx][s2] = code++; + } + + // Legal positions with both kings on diagonal are encoded as last ones + for (auto p : bothOnDiagonal) + MapKK[p.first][p.second] = code++; + + // Binomial[] stores the Binomial Coefficents using Pascal rule. There + // are Binomial[k][n] ways to choose k elements from a set of n elements. + Binomial[0][0] = 1; + + for (int n = 1; n < 64; n++) // Squares + for (int k = 0; k < 6 && k <= n; ++k) // Pieces + Binomial[k][n] = (k > 0 ? Binomial[k - 1][n - 1] : 0) + + (k < n ? Binomial[k ][n - 1] : 0); + + // MapPawns[s] encodes squares a2-h7 to 0..47. This is the number of possible + // available squares when the leading one is in 's'. Moreover the pawn with + // highest MapPawns[] is the leading pawn, the one nearest the edge and, + // among pawns with same file, the one with lowest rank. + int availableSquares = 47; // Available squares when lead pawn is in a2 + + // Init the tables for the encoding of leading pawns group: with 6-men TB we + // can have up to 4 leading pawns (KPPPPK). + for (int leadPawnsCnt = 1; leadPawnsCnt <= 4; ++leadPawnsCnt) + for (File f = FILE_A; f <= FILE_D; ++f) + { + // Restart the index at every file because TB table is splitted + // by file, so we can reuse the same index for different files. + int idx = 0; + + // Sum all possible combinations for a given file, starting with + // the leading pawn on rank 2 and increasing the rank. + for (Rank r = RANK_2; r <= RANK_7; ++r) + { + Square sq = make_square(f, r); + + // Compute MapPawns[] at first pass. + // If sq is the leading pawn square, any other pawn cannot be + // below or more toward the edge of sq. There are 47 available + // squares when sq = a2 and reduced by 2 for any rank increase + // due to mirroring: sq == a3 -> no a2, h2, so MapPawns[a3] = 45 + if (leadPawnsCnt == 1) + { + MapPawns[sq] = availableSquares--; + MapPawns[sq ^ 7] = availableSquares--; // Horizontal flip + } + LeadPawnIdx[leadPawnsCnt][sq] = idx; + idx += Binomial[leadPawnsCnt - 1][MapPawns[sq]]; + } + // After a file is traversed, store the cumulated per-file index + LeadPawnsSize[leadPawnsCnt][f] = idx; + } + + for (PieceType p1 = PAWN; p1 < KING; ++p1) { + EntryTable.insert({KING, p1, KING}); + + for (PieceType p2 = PAWN; p2 <= p1; ++p2) { + EntryTable.insert({KING, p1, p2, KING}); + EntryTable.insert({KING, p1, KING, p2}); + + for (PieceType p3 = PAWN; p3 < KING; ++p3) + EntryTable.insert({KING, p1, p2, KING, p3}); + + for (PieceType p3 = PAWN; p3 <= p2; ++p3) { + EntryTable.insert({KING, p1, p2, p3, KING}); + + for (PieceType p4 = PAWN; p4 <= p3; ++p4) + EntryTable.insert({KING, p1, p2, p3, p4, KING}); + + for (PieceType p4 = PAWN; p4 < KING; ++p4) + EntryTable.insert({KING, p1, p2, p3, KING, p4}); + } + + for (PieceType p3 = PAWN; p3 <= p1; ++p3) + for (PieceType p4 = PAWN; p4 <= (p1 == p3 ? p2 : p3); ++p4) + EntryTable.insert({KING, p1, p2, KING, p3, p4}); } - } else { - v = -Tablebases::probe_dtz(pos, success) - 1; - } - pos.undo_move(move); - if (*success == 0) return 0; - if (v < best) - best = v; } - return best; - } + + sync_cout << "info string Found " << EntryTable.size() << " tablebases" << sync_endl; } -static int wdl_to_dtz[] = { - -1, -101, 0, 101, 1 -}; +// Probe the WDL table for a particular position. +// If *result != FAIL, the probe was successful. +// The return value is from the point of view of the side to move: +// -2 : loss +// -1 : loss, but draw under 50-move rule +// 0 : draw +// 1 : win, but draw under 50-move rule +// 2 : win +WDLScore Tablebases::probe_wdl(Position& pos, ProbeState* result) { + + *result = OK; + return search(pos, result); +} // Probe the DTZ table for a particular position. -// If *success != 0, the probe was successful. +// If *result != FAIL, the probe was successful. // The return value is from the point of view of the side to move: // n < -100 : loss, but draw under 50-move rule // -100 <= n < -1 : loss in n ply (assuming 50-move counter == 0) @@ -578,103 +1434,90 @@ static int wdl_to_dtz[] = { // // In short, if a move is available resulting in dtz + 50-move-counter <= 99, // then do not accept moves leading to dtz + 50-move-counter == 100. -// -int Tablebases::probe_dtz(Position& pos, int *success) -{ - *success = 1; - int v = probe_dtz_no_ep(pos, success); +int Tablebases::probe_dtz(Position& pos, ProbeState* result) { - if (pos.ep_square() == SQ_NONE) - return v; - if (*success == 0) return 0; - - // Now handle en passant. - int v1 = -3; - - ExtMove stack[192]; - ExtMove *moves, *end; - StateInfo st; - - if (!pos.checkers()) - end = generate(pos, stack); - else - end = generate(pos, stack); - - for (moves = stack; moves < end; moves++) { - Move capture = moves->move; - if (type_of(capture) != ENPASSANT - || !pos.legal(capture)) - continue; - pos.do_move(capture, st, pos.gives_check(capture)); - int v0 = -probe_ab(pos, -2, 2, success); - pos.undo_move(capture); - if (*success == 0) return 0; - if (v0 > v1) v1 = v0; - } - if (v1 > -3) { - v1 = wdl_to_dtz[v1 + 2]; - if (v < -100) { - if (v1 >= 0) - v = v1; - } else if (v < 0) { - if (v1 >= 0 || v1 < -100) - v = v1; - } else if (v > 100) { - if (v1 > 0) - v = v1; - } else if (v > 0) { - if (v1 == 1) - v = v1; - } else if (v1 >= 0) { - v = v1; - } else { - for (moves = stack; moves < end; moves++) { - Move move = moves->move; - if (type_of(move) == ENPASSANT) continue; - if (pos.legal(move)) break; - } - if (moves == end && !pos.checkers()) { - end = generate(pos, end); - for (; moves < end; moves++) { - Move move = moves->move; - if (pos.legal(move)) - break; - } - } - if (moves == end) - v = v1; + *result = OK; + WDLScore wdl = search(pos, result); + + if (*result == FAIL || wdl == WDLDraw) // DTZ tables don't store draws + return 0; + + // DTZ stores a 'don't care' value in this case, or even a plain wrong + // one as in case the best move is a losing ep, so it cannot be probed. + if (*result == ZEROING_BEST_MOVE) + return dtz_before_zeroing(wdl); + + int dtz = probe_table(pos, result, wdl); + + if (*result == FAIL) + return 0; + + if (*result != CHANGE_STM) + return (dtz + 100 * (wdl == WDLBlessedLoss || wdl == WDLCursedWin)) * sign_of(wdl); + + // DTZ stores results for the other side, so we need to do a 1-ply search and + // find the winning move that minimizes DTZ. + StateInfo st; + int minDTZ = 0xFFFF; + + for (const Move& move : MoveList(pos)) + { + bool zeroing = pos.capture(move) || type_of(pos.moved_piece(move)) == PAWN; + + pos.do_move(move, st); + + // For zeroing moves we want the dtz of the move _before_ doing it, + // otherwise we will get the dtz of the next move sequence. Search the + // position after the move to get the score sign (because even in a + // winning position we could make a losing capture or going for a draw). + dtz = zeroing ? -dtz_before_zeroing(search(pos, result)) + : -probe_dtz(pos, result); + + pos.undo_move(move); + + if (*result == FAIL) + return 0; + + // Convert result from 1-ply search. Zeroing moves are already accounted + // by dtz_before_zeroing() that returns the DTZ of the previous move. + if (!zeroing) + dtz += sign_of(dtz); + + // Skip the draws and if we are winning only pick positive dtz + if (dtz < minDTZ && sign_of(dtz) == sign_of(wdl)) + minDTZ = dtz; } - } - return v; + // Special handle a mate position, when there are no legal moves, in this + // case return value is somewhat arbitrary, so stick to the original TB code + // that returns -1 in this case. + return minDTZ == 0xFFFF ? -1 : minDTZ; } // Check whether there has been at least one repetition of positions // since the last capture or pawn move. static int has_repeated(StateInfo *st) { - while (1) { - int i = 4, e = std::min(st->rule50, st->pliesFromNull); - if (e < i) - return 0; - StateInfo *stp = st->previous->previous; - do { - stp = stp->previous->previous; - if (stp->key == st->key) - return 1; - i += 2; - } while (i <= e); - st = st->previous; - } -} + while (1) { + int i = 4, e = std::min(st->rule50, st->pliesFromNull); -static Value wdl_to_Value[5] = { - -VALUE_MATE + MAX_PLY + 1, - VALUE_DRAW - 2, - VALUE_DRAW, - VALUE_DRAW + 2, - VALUE_MATE - MAX_PLY - 1 -}; + if (e < i) + return 0; + + StateInfo *stp = st->previous->previous; + + do { + stp = stp->previous->previous; + + if (stp->key == st->key) + return 1; + + i += 2; + } while (i <= e); + + st = st->previous; + } +} // Use the DTZ tables to filter out moves that don't preserve the win or draw. // If the position is lost, but DTZ is fairly high, only keep moves that @@ -684,103 +1527,130 @@ static Value wdl_to_Value[5] = { // no moves were filtered out. bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score) { - int success; - - int dtz = probe_dtz(pos, &success); - if (!success) return false; - - StateInfo st; - - // Probe each move. - for (size_t i = 0; i < rootMoves.size(); i++) { - Move move = rootMoves[i].pv[0]; - pos.do_move(move, st, pos.gives_check(move)); - int v = 0; - if (pos.checkers() && dtz > 0) { - ExtMove s[192]; - if (generate(pos, s) == s) - v = 1; - } - if (!v) { - if (st.rule50 != 0) { - v = -Tablebases::probe_dtz(pos, &success); - if (v > 0) v++; - else if (v < 0) v--; - } else { - v = -Tablebases::probe_wdl(pos, &success); - v = wdl_to_dtz[v + 2]; - } - } - pos.undo_move(move); - if (!success) return false; - rootMoves[i].score = (Value)v; - } + assert(rootMoves.size()); - // Obtain 50-move counter for the root position. - // In Stockfish there seems to be no clean way, so we do it like this: - int cnt50 = st.previous->rule50; - - // Use 50-move counter to determine whether the root position is - // won, lost or drawn. - int wdl = 0; - if (dtz > 0) - wdl = (dtz + cnt50 <= 100) ? 2 : 1; - else if (dtz < 0) - wdl = (-dtz + cnt50 <= 100) ? -2 : -1; - - // Determine the score to report to the user. - score = wdl_to_Value[wdl + 2]; - // If the position is winning or losing, but too few moves left, adjust the - // score to show how close it is to winning or losing. - // NOTE: int(PawnValueEg) is used as scaling factor in score_to_uci(). - if (wdl == 1 && dtz <= 100) - score = (Value)(((200 - dtz - cnt50) * int(PawnValueEg)) / 200); - else if (wdl == -1 && dtz >= -100) - score = -(Value)(((200 + dtz - cnt50) * int(PawnValueEg)) / 200); - - // Now be a bit smart about filtering out moves. - size_t j = 0; - if (dtz > 0) { // winning (or 50-move rule draw) - int best = 0xffff; - for (size_t i = 0; i < rootMoves.size(); i++) { - int v = rootMoves[i].score; - if (v > 0 && v < best) - best = v; - } - int max = best; - // If the current phase has not seen repetitions, then try all moves - // that stay safely within the 50-move budget, if there are any. - if (!has_repeated(st.previous) && best + cnt50 <= 99) - max = 99 - cnt50; - for (size_t i = 0; i < rootMoves.size(); i++) { - int v = rootMoves[i].score; - if (v > 0 && v <= max) - rootMoves[j++] = rootMoves[i]; - } - } else if (dtz < 0) { // losing (or 50-move rule draw) - int best = 0; - for (size_t i = 0; i < rootMoves.size(); i++) { - int v = rootMoves[i].score; - if (v < best) - best = v; - } - // Try all moves, unless we approach or have a 50-move rule draw. - if (-best * 2 + cnt50 < 100) - return true; - for (size_t i = 0; i < rootMoves.size(); i++) { - if (rootMoves[i].score == best) - rootMoves[j++] = rootMoves[i]; + ProbeState result; + int dtz = probe_dtz(pos, &result); + + if (result == FAIL) + return false; + + StateInfo st; + + // Probe each move + for (size_t i = 0; i < rootMoves.size(); ++i) { + Move move = rootMoves[i].pv[0]; + pos.do_move(move, st); + int v = 0; + + if (pos.checkers() && dtz > 0) { + ExtMove s[MAX_MOVES]; + + if (generate(pos, s) == s) + v = 1; + } + + if (!v) { + if (st.rule50 != 0) { + v = -probe_dtz(pos, &result); + + if (v > 0) + ++v; + else if (v < 0) + --v; + } else { + v = -probe_wdl(pos, &result); + v = dtz_before_zeroing(WDLScore(v)); + } + } + + pos.undo_move(move); + + if (result == FAIL) + return false; + + rootMoves[i].score = (Value)v; } - } else { // drawing - // Try all moves that preserve the draw. - for (size_t i = 0; i < rootMoves.size(); i++) { - if (rootMoves[i].score == 0) - rootMoves[j++] = rootMoves[i]; + + // Obtain 50-move counter for the root position. + // In Stockfish there seems to be no clean way, so we do it like this: + int cnt50 = st.previous ? st.previous->rule50 : 0; + + // Use 50-move counter to determine whether the root position is + // won, lost or drawn. + WDLScore wdl = WDLDraw; + + if (dtz > 0) + wdl = (dtz + cnt50 <= 100) ? WDLWin : WDLCursedWin; + else if (dtz < 0) + wdl = (-dtz + cnt50 <= 100) ? WDLLoss : WDLBlessedLoss; + + // Determine the score to report to the user. + score = WDL_to_value[wdl + 2]; + + // If the position is winning or losing, but too few moves left, adjust the + // score to show how close it is to winning or losing. + // NOTE: int(PawnValueEg) is used as scaling factor in score_to_uci(). + if (wdl == WDLCursedWin && dtz <= 100) + score = (Value)(((200 - dtz - cnt50) * int(PawnValueEg)) / 200); + else if (wdl == WDLBlessedLoss && dtz >= -100) + score = -(Value)(((200 + dtz - cnt50) * int(PawnValueEg)) / 200); + + // Now be a bit smart about filtering out moves. + size_t j = 0; + + if (dtz > 0) { // winning (or 50-move rule draw) + int best = 0xffff; + + for (size_t i = 0; i < rootMoves.size(); ++i) { + int v = rootMoves[i].score; + + if (v > 0 && v < best) + best = v; + } + + int max = best; + + // If the current phase has not seen repetitions, then try all moves + // that stay safely within the 50-move budget, if there are any. + if (!has_repeated(st.previous) && best + cnt50 <= 99) + max = 99 - cnt50; + + for (size_t i = 0; i < rootMoves.size(); ++i) { + int v = rootMoves[i].score; + + if (v > 0 && v <= max) + rootMoves[j++] = rootMoves[i]; + } + } else if (dtz < 0) { // losing (or 50-move rule draw) + int best = 0; + + for (size_t i = 0; i < rootMoves.size(); ++i) { + int v = rootMoves[i].score; + + if (v < best) + best = v; + } + + // Try all moves, unless we approach or have a 50-move rule draw. + if (-best * 2 + cnt50 < 100) + return true; + + for (size_t i = 0; i < rootMoves.size(); ++i) { + if (rootMoves[i].score == best) + rootMoves[j++] = rootMoves[i]; + } + } else { // drawing + // Try all moves that preserve the draw. + for (size_t i = 0; i < rootMoves.size(); ++i) { + if (rootMoves[i].score == 0) + rootMoves[j++] = rootMoves[i]; + } } - } - rootMoves.resize(j, Search::RootMove(MOVE_NONE)); - return true; + rootMoves.resize(j, Search::RootMove(MOVE_NONE)); + + return true; } // Use the WDL tables to filter out moves that don't preserve the win or draw. @@ -790,35 +1660,43 @@ bool Tablebases::root_probe(Position& pos, Search::RootMoves& rootMoves, Value& // no moves were filtered out. bool Tablebases::root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score) { - int success; - - int wdl = Tablebases::probe_wdl(pos, &success); - if (!success) return false; - score = wdl_to_Value[wdl + 2]; - - StateInfo st; - - int best = -2; - - // Probe each move. - for (size_t i = 0; i < rootMoves.size(); i++) { - Move move = rootMoves[i].pv[0]; - pos.do_move(move, st, pos.gives_check(move)); - int v = -Tablebases::probe_wdl(pos, &success); - pos.undo_move(move); - if (!success) return false; - rootMoves[i].score = (Value)v; - if (v > best) - best = v; - } + ProbeState result; - size_t j = 0; - for (size_t i = 0; i < rootMoves.size(); i++) { - if (rootMoves[i].score == best) - rootMoves[j++] = rootMoves[i]; - } - rootMoves.resize(j, Search::RootMove(MOVE_NONE)); + WDLScore wdl = Tablebases::probe_wdl(pos, &result); - return true; -} + if (result == FAIL) + return false; + + score = WDL_to_value[wdl + 2]; + + StateInfo st; + int best = WDLLoss; + + // Probe each move + for (size_t i = 0; i < rootMoves.size(); ++i) { + Move move = rootMoves[i].pv[0]; + pos.do_move(move, st); + WDLScore v = -Tablebases::probe_wdl(pos, &result); + pos.undo_move(move); + + if (result == FAIL) + return false; + + rootMoves[i].score = (Value)v; + + if (v > best) + best = v; + } + + size_t j = 0; + + for (size_t i = 0; i < rootMoves.size(); ++i) { + if (rootMoves[i].score == best) + rootMoves[j++] = rootMoves[i]; + } + + rootMoves.resize(j, Search::RootMove(MOVE_NONE)); + + return true; +} diff --git a/Engines/Windows/stockfish/src/syzygy/tbprobe.h b/Engines/Windows/stockfish/src/syzygy/tbprobe.h index b23fdf6..287b617 100644 --- a/Engines/Windows/stockfish/src/syzygy/tbprobe.h +++ b/Engines/Windows/stockfish/src/syzygy/tbprobe.h @@ -1,19 +1,79 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (c) 2013 Ronald de Man + Copyright (C) 2016-2018 Marco Costalba, Lucas Braesch + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + #ifndef TBPROBE_H #define TBPROBE_H +#include + #include "../search.h" namespace Tablebases { +enum WDLScore { + WDLLoss = -2, // Loss + WDLBlessedLoss = -1, // Loss, but draw under 50-move rule + WDLDraw = 0, // Draw + WDLCursedWin = 1, // Win, but draw under 50-move rule + WDLWin = 2, // Win + + WDLScoreNone = -1000 +}; + +// Possible states after a probing operation +enum ProbeState { + FAIL = 0, // Probe failed (missing file table) + OK = 1, // Probe succesful + CHANGE_STM = -1, // DTZ should check the other side + ZEROING_BEST_MOVE = 2 // Best move zeroes DTZ (capture or pawn move) +}; + extern int MaxCardinality; -void init(const std::string& path); -int probe_wdl(Position& pos, int *success); -int probe_dtz(Position& pos, int *success); +void init(const std::string& paths); +WDLScore probe_wdl(Position& pos, ProbeState* result); +int probe_dtz(Position& pos, ProbeState* result); bool root_probe(Position& pos, Search::RootMoves& rootMoves, Value& score); bool root_probe_wdl(Position& pos, Search::RootMoves& rootMoves, Value& score); void filter_root_moves(Position& pos, Search::RootMoves& rootMoves); +inline std::ostream& operator<<(std::ostream& os, const WDLScore v) { + + os << (v == WDLLoss ? "Loss" : + v == WDLBlessedLoss ? "Blessed loss" : + v == WDLDraw ? "Draw" : + v == WDLCursedWin ? "Cursed win" : + v == WDLWin ? "Win" : "None"); + + return os; +} + +inline std::ostream& operator<<(std::ostream& os, const ProbeState v) { + + os << (v == FAIL ? "Failed" : + v == OK ? "Success" : + v == CHANGE_STM ? "Probed opponent side" : + v == ZEROING_BEST_MOVE ? "Best move zeroes DTZ" : "None"); + + return os; +} + } #endif diff --git a/Engines/Windows/stockfish/src/thread.cpp b/Engines/Windows/stockfish/src/thread.cpp index 1f1490a..97beb58 100644 --- a/Engines/Windows/stockfish/src/thread.cpp +++ b/Engines/Windows/stockfish/src/thread.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,172 +29,144 @@ ThreadPool Threads; // Global object -/// Thread constructor launches the thread and then waits until it goes to sleep -/// in idle_loop(). -Thread::Thread() { +/// Thread constructor launches the thread and waits until it goes to sleep +/// in idle_loop(). Note that 'searching' and 'exit' should be alredy set. - resetCalls = exit = false; - maxPly = callsCnt = 0; - tbHits = 0; - history.clear(); - counterMoves.clear(); - idx = Threads.size(); // Start from 0 +Thread::Thread(size_t n) : idx(n), stdThread(&Thread::idle_loop, this) { - std::unique_lock lk(mutex); - searching = true; - nativeThread = std::thread(&Thread::idle_loop, this); - sleepCondition.wait(lk, [&]{ return !searching; }); + wait_for_search_finished(); } -/// Thread destructor waits for thread termination before returning +/// Thread destructor wakes up the thread in idle_loop() and waits +/// for its termination. Thread should be already waiting. Thread::~Thread() { - mutex.lock(); + assert(!searching); + exit = true; - sleepCondition.notify_one(); - mutex.unlock(); - nativeThread.join(); + start_searching(); + stdThread.join(); } -/// Thread::wait_for_search_finished() waits on sleep condition -/// until not searching +/// Thread::clear() reset histories, usually before a new game -void Thread::wait_for_search_finished() { +void Thread::clear() { - std::unique_lock lk(mutex); - sleepCondition.wait(lk, [&]{ return !searching; }); -} + counterMoves.fill(MOVE_NONE); + mainHistory.fill(0); + captureHistory.fill(0); + for (auto& to : contHistory) + for (auto& h : to) + h.fill(0); -/// Thread::wait() waits on sleep condition until condition is true + contHistory[NO_PIECE][0].fill(Search::CounterMovePruneThreshold - 1); +} -void Thread::wait(std::atomic_bool& condition) { +/// Thread::start_searching() wakes up the thread that will start the search - std::unique_lock lk(mutex); - sleepCondition.wait(lk, [&]{ return bool(condition); }); +void Thread::start_searching() { + + std::lock_guard lk(mutex); + searching = true; + cv.notify_one(); // Wake up the thread in idle_loop() } -/// Thread::start_searching() wakes up the thread that will start the search +/// Thread::wait_for_search_finished() blocks on the condition variable +/// until the thread has finished searching. -void Thread::start_searching(bool resume) { +void Thread::wait_for_search_finished() { std::unique_lock lk(mutex); - - if (!resume) - searching = true; - - sleepCondition.notify_one(); + cv.wait(lk, [&]{ return !searching; }); } -/// Thread::idle_loop() is where the thread is parked when it has no work to do +/// Thread::idle_loop() is where the thread is parked, blocked on the +/// condition variable, when it has no work to do. void Thread::idle_loop() { - while (!exit) + // If OS already scheduled us on a different group than 0 then don't overwrite + // the choice, eventually we are one of many one-threaded processes running on + // some Windows NUMA hardware, for instance in fishtest. To make it simple, + // just check if running threads are below a threshold, in this case all this + // NUMA machinery is not needed. + if (Options["Threads"] >= 8) + WinProcGroup::bindThisThread(idx); + + while (true) { std::unique_lock lk(mutex); - searching = false; + cv.notify_one(); // Wake up anyone waiting for search finished + cv.wait(lk, [&]{ return searching; }); - while (!searching && !exit) - { - sleepCondition.notify_one(); // Wake up any waiting thread - sleepCondition.wait(lk); - } + if (exit) + return; lk.unlock(); - if (!exit) - search(); + search(); } } +/// ThreadPool::set() creates/destroys threads to match the requested number. +/// Created and launced threads wil go immediately to sleep in idle_loop. +/// Upon resizing, threads are recreated to allow for binding if necessary. -/// ThreadPool::init() creates and launches requested threads that will go -/// immediately to sleep. We cannot use a constructor because Threads is a -/// static object and we need a fully initialized engine at this point due to -/// allocation of Endgames in the Thread constructor. - -void ThreadPool::init() { - - push_back(new MainThread); - read_uci_options(); -} - - -/// ThreadPool::exit() terminates threads before the program exits. Cannot be -/// done in destructor because threads must be terminated before deleting any -/// static objects while still in main(). - -void ThreadPool::exit() { - - while (size()) - delete back(), pop_back(); -} - - -/// ThreadPool::read_uci_options() updates internal threads parameters from the -/// corresponding UCI options and creates/destroys threads to match requested -/// number. Thread objects are dynamically allocated. +void ThreadPool::set(size_t requested) { -void ThreadPool::read_uci_options() { + if (size() > 0) { // destroy any existing thread(s) + main()->wait_for_search_finished(); - size_t requested = Options["Threads"]; - - assert(requested > 0); + while (size() > 0) + delete back(), pop_back(); + } - while (size() < requested) - push_back(new Thread); + if (requested > 0) { // create new thread(s) + push_back(new MainThread(0)); - while (size() > requested) - delete back(), pop_back(); + while (size() < requested) + push_back(new Thread(size())); + clear(); + } } +/// ThreadPool::clear() sets threadPool data to initial values. -/// ThreadPool::nodes_searched() returns the number of nodes searched - -uint64_t ThreadPool::nodes_searched() const { +void ThreadPool::clear() { - uint64_t nodes = 0; for (Thread* th : *this) - nodes += th->rootPos.nodes_searched(); - return nodes; -} - - -/// ThreadPool::tb_hits() returns the number of TB hits + th->clear(); -uint64_t ThreadPool::tb_hits() const { - - uint64_t hits = 0; - for (Thread* th : *this) - hits += th->tbHits; - return hits; + main()->callsCnt = 0; + main()->previousScore = VALUE_INFINITE; + main()->previousTimeReduction = 1; } - -/// ThreadPool::start_thinking() wakes up the main thread sleeping in idle_loop() -/// and starts a new search, then returns immediately. +/// ThreadPool::start_thinking() wakes up main thread waiting in idle_loop() and +/// returns immediately. Main thread will wake up other threads and start the search. void ThreadPool::start_thinking(Position& pos, StateListPtr& states, - const Search::LimitsType& limits) { + const Search::LimitsType& limits, bool ponderMode) { main()->wait_for_search_finished(); - Search::Signals.stopOnPonderhit = Search::Signals.stop = false; + stopOnPonderhit = stop = false; + ponder = ponderMode; Search::Limits = limits; Search::RootMoves rootMoves; for (const auto& m : MoveList(pos)) if ( limits.searchmoves.empty() || std::count(limits.searchmoves.begin(), limits.searchmoves.end(), m)) - rootMoves.push_back(Search::RootMove(m)); + rootMoves.emplace_back(m); if (!rootMoves.empty()) Tablebases::filter_root_moves(pos, rootMoves); @@ -206,18 +178,22 @@ void ThreadPool::start_thinking(Position& pos, StateListPtr& states, if (states.get()) setupStates = std::move(states); // Ownership transfer, states is now empty + // We use Position::set() to set root position across threads. But there are + // some StateInfo fields (previous, pliesFromNull, capturedPiece) that cannot + // be deduced from a fen string, so set() clears them and to not lose the info + // we need to backup and later restore setupStates->back(). Note that setupStates + // is shared by threads but is accessed in read-only mode. StateInfo tmp = setupStates->back(); - for (Thread* th : Threads) + for (Thread* th : *this) { - th->maxPly = 0; - th->tbHits = 0; - th->rootDepth = DEPTH_ZERO; + th->nodes = th->tbHits = th->nmp_ply = th->nmp_odd = 0; + th->rootDepth = th->completedDepth = DEPTH_ZERO; th->rootMoves = rootMoves; th->rootPos.set(pos.fen(), pos.is_chess960(), &setupStates->back(), th); } - setupStates->back() = tmp; // Restore st->previous, cleared by Position::set() + setupStates->back() = tmp; main()->start_searching(); } diff --git a/Engines/Windows/stockfish/src/thread.h b/Engines/Windows/stockfish/src/thread.h index d1165bb..1397449 100644 --- a/Engines/Windows/stockfish/src/thread.h +++ b/Engines/Windows/stockfish/src/thread.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,7 +22,6 @@ #define THREAD_H_INCLUDED #include -#include #include #include #include @@ -36,74 +35,87 @@ #include "thread_win32.h" -/// Thread struct keeps together all the thread-related stuff. We also use -/// per-thread pawn and material hash tables so that once we get a pointer to an -/// entry its life time is unlimited and we don't have to care about someone -/// changing the entry under our feet. +/// Thread class keeps together all the thread-related stuff. We use +/// per-thread pawn and material hash tables so that once we get a +/// pointer to an entry its life time is unlimited and we don't have +/// to care about someone changing the entry under our feet. class Thread { - std::thread nativeThread; Mutex mutex; - ConditionVariable sleepCondition; - bool exit, searching; + ConditionVariable cv; + size_t idx; + bool exit = false, searching = true; // Set before starting std::thread + std::thread stdThread; public: - Thread(); + explicit Thread(size_t); virtual ~Thread(); virtual void search(); + void clear(); void idle_loop(); - void start_searching(bool resume = false); + void start_searching(); void wait_for_search_finished(); - void wait(std::atomic_bool& b); Pawns::Table pawnsTable; Material::Table materialTable; Endgames endgames; - size_t idx, PVIdx; - int maxPly, callsCnt; - uint64_t tbHits; + size_t PVIdx; + int selDepth, nmp_ply, nmp_odd; + std::atomic nodes, tbHits; Position rootPos; Search::RootMoves rootMoves; - Depth rootDepth; - Depth completedDepth; - std::atomic_bool resetCalls; - HistoryStats history; - MoveStats counterMoves; - FromToStats fromTo; - CounterMoveHistoryStats counterMoveHistory; + Depth rootDepth, completedDepth; + CounterMoveHistory counterMoves; + ButterflyHistory mainHistory; + CapturePieceToHistory captureHistory; + ContinuationHistory contHistory; }; -/// MainThread is a derived class with a specific overload for the main thread +/// MainThread is a derived class specific for main thread struct MainThread : public Thread { - virtual void search(); - bool easyMovePlayed, failedLow; - double bestMoveChanges; + using Thread::Thread; + + void search() override; + void check_time(); + + bool failedLow; + double bestMoveChanges, previousTimeReduction; Value previousScore; + int callsCnt; }; /// ThreadPool struct handles all the threads-related stuff like init, starting, /// parking and, most importantly, launching a thread. All the access to threads -/// data is done through this class. +/// is done through this class. struct ThreadPool : public std::vector { - void init(); // No constructor and destructor, threads rely on globals that should - void exit(); // be initialized and valid during the whole thread lifetime. + void start_thinking(Position&, StateListPtr&, const Search::LimitsType&, bool = false); + void clear(); + void set(size_t); - MainThread* main() { return static_cast(at(0)); } - void start_thinking(Position&, StateListPtr&, const Search::LimitsType&); - void read_uci_options(); - uint64_t nodes_searched() const; - uint64_t tb_hits() const; + MainThread* main() const { return static_cast(front()); } + uint64_t nodes_searched() const { return accumulate(&Thread::nodes); } + uint64_t tb_hits() const { return accumulate(&Thread::tbHits); } + + std::atomic_bool stop, ponder, stopOnPonderhit; private: StateListPtr setupStates; + + uint64_t accumulate(std::atomic Thread::* member) const { + + uint64_t sum = 0; + for (Thread* th : *this) + sum += (th->*member).load(std::memory_order_relaxed); + return sum; + } }; extern ThreadPool Threads; diff --git a/Engines/Windows/stockfish/src/thread_win32.h b/Engines/Windows/stockfish/src/thread_win32.h index 47516c6..5da186a 100644 --- a/Engines/Windows/stockfish/src/thread_win32.h +++ b/Engines/Windows/stockfish/src/thread_win32.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/stockfish/src/timeman.cpp b/Engines/Windows/stockfish/src/timeman.cpp index 6d3b731..035fe33 100644 --- a/Engines/Windows/stockfish/src/timeman.cpp +++ b/Engines/Windows/stockfish/src/timeman.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/stockfish/src/timeman.h b/Engines/Windows/stockfish/src/timeman.h index 9930a4b..f4e3a95 100644 --- a/Engines/Windows/stockfish/src/timeman.h +++ b/Engines/Windows/stockfish/src/timeman.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/Engines/Windows/stockfish/src/tt.cpp b/Engines/Windows/stockfish/src/tt.cpp index f5b72ba..25f1cd0 100644 --- a/Engines/Windows/stockfish/src/tt.cpp +++ b/Engines/Windows/stockfish/src/tt.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,7 +33,7 @@ TranspositionTable TT; // Our global transposition table void TranspositionTable::resize(size_t mbSize) { - size_t newClusterCount = size_t(1) << msb((mbSize * 1024 * 1024) / sizeof(Cluster)); + size_t newClusterCount = mbSize * 1024 * 1024 / sizeof(Cluster); if (newClusterCount == clusterCount) return; @@ -41,7 +41,7 @@ void TranspositionTable::resize(size_t mbSize) { clusterCount = newClusterCount; free(mem); - mem = calloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1, 1); + mem = malloc(clusterCount * sizeof(Cluster) + CacheLineSize - 1); if (!mem) { @@ -51,6 +51,7 @@ void TranspositionTable::resize(size_t mbSize) { } table = (Cluster*)((uintptr_t(mem) + CacheLineSize - 1) & ~(CacheLineSize - 1)); + clear(); } diff --git a/Engines/Windows/stockfish/src/tt.h b/Engines/Windows/stockfish/src/tt.h index 677f38e..ca7dfbf 100644 --- a/Engines/Windows/stockfish/src/tt.h +++ b/Engines/Windows/stockfish/src/tt.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -104,9 +104,9 @@ class TranspositionTable { void resize(size_t mbSize); void clear(); - // The lowest order bits of the key are used to get the index of the cluster + // The 32 lowest order bits of the key are used to get the index of the cluster TTEntry* first_entry(const Key key) const { - return &table[(size_t)key & (clusterCount - 1)].entry[0]; + return &table[(uint32_t(key) * uint64_t(clusterCount)) >> 32].entry[0]; } private: diff --git a/Engines/Windows/stockfish/src/types.h b/Engines/Windows/stockfish/src/types.h index 519f6af..009a933 100644 --- a/Engines/Windows/stockfish/src/types.h +++ b/Engines/Windows/stockfish/src/types.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -76,7 +76,7 @@ # include // Header for _pext_u64() intrinsic # define pext(b, m) _pext_u64(b, m) #else -# define pext(b, m) (0) +# define pext(b, m) 0 #endif #ifdef USE_POPCNT @@ -128,7 +128,7 @@ enum MoveType { }; enum Color { - WHITE, BLACK, NO_COLOR, COLOR_NB = 2 + WHITE, BLACK, COLOR_NB = 2 }; enum CastlingSide { @@ -146,7 +146,7 @@ enum CastlingRight { }; template struct MakeCastling { - static const CastlingRight + static constexpr CastlingRight right = C == WHITE ? S == QUEEN_SIDE ? WHITE_OOO : WHITE_OO : S == QUEEN_SIDE ? BLACK_OOO : BLACK_OO; }; @@ -183,11 +183,11 @@ enum Value : int { VALUE_MATE_IN_MAX_PLY = VALUE_MATE - 2 * MAX_PLY, VALUE_MATED_IN_MAX_PLY = -VALUE_MATE + 2 * MAX_PLY, - PawnValueMg = 188, PawnValueEg = 248, - KnightValueMg = 753, KnightValueEg = 832, - BishopValueMg = 826, BishopValueEg = 897, - RookValueMg = 1285, RookValueEg = 1371, - QueenValueMg = 2513, QueenValueEg = 2650, + PawnValueMg = 171, PawnValueEg = 240, + KnightValueMg = 764, KnightValueEg = 848, + BishopValueMg = 826, BishopValueEg = 891, + RookValueMg = 1282, RookValueEg = 1373, + QueenValueMg = 2526, QueenValueEg = 2646, MidgameLimit = 15258, EndgameLimit = 3915 }; @@ -195,6 +195,7 @@ enum Value : int { enum PieceType { NO_PIECE_TYPE, PAWN, KNIGHT, BISHOP, ROOK, QUEEN, KING, ALL_PIECES = 0, + QUEEN_DIAGONAL = 7, PIECE_TYPE_NB = 8 }; @@ -205,11 +206,9 @@ enum Piece { PIECE_NB = 16 }; -const Piece Pieces[] = { W_PAWN, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, - B_PAWN, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING }; extern Value PieceValue[PHASE_NB][PIECE_NB]; -enum Depth { +enum Depth : int { ONE_PLY = 1, @@ -224,7 +223,7 @@ enum Depth { static_assert(!(ONE_PLY & (ONE_PLY - 1)), "ONE_PLY is not a power of 2"); -enum Square { +enum Square : int { SQ_A1, SQ_B1, SQ_C1, SQ_D1, SQ_E1, SQ_F1, SQ_G1, SQ_H1, SQ_A2, SQ_B2, SQ_C2, SQ_D2, SQ_E2, SQ_F2, SQ_G2, SQ_H2, SQ_A3, SQ_B3, SQ_C3, SQ_D3, SQ_E3, SQ_F3, SQ_G3, SQ_H3, @@ -235,12 +234,14 @@ enum Square { SQ_A8, SQ_B8, SQ_C8, SQ_D8, SQ_E8, SQ_F8, SQ_G8, SQ_H8, SQ_NONE, - SQUARE_NB = 64, + SQUARE_NB = 64 +}; +enum Direction : int { NORTH = 8, EAST = 1, - SOUTH = -8, - WEST = -1, + SOUTH = -NORTH, + WEST = -EAST, NORTH_EAST = NORTH + EAST, SOUTH_EAST = SOUTH + EAST, @@ -263,7 +264,7 @@ enum Rank : int { /// care to avoid left-shifting a signed int to avoid undefined behavior. enum Score : int { SCORE_ZERO }; -inline Score make_score(int mg, int eg) { +constexpr Score make_score(int mg, int eg) { return Score((int)((unsigned int)eg << 16) + mg); } @@ -271,97 +272,123 @@ inline Score make_score(int mg, int eg) { /// according to the standard a simple cast to short is implementation defined /// and so is a right shift of a signed integer. inline Value eg_value(Score s) { - union { uint16_t u; int16_t s; } eg = { uint16_t(unsigned(s + 0x8000) >> 16) }; return Value(eg.s); } inline Value mg_value(Score s) { - union { uint16_t u; int16_t s; } mg = { uint16_t(unsigned(s)) }; return Value(mg.s); } -#define ENABLE_BASE_OPERATORS_ON(T) \ -inline T operator+(T d1, T d2) { return T(int(d1) + int(d2)); } \ -inline T operator-(T d1, T d2) { return T(int(d1) - int(d2)); } \ -inline T operator*(int i, T d) { return T(i * int(d)); } \ -inline T operator*(T d, int i) { return T(int(d) * i); } \ -inline T operator-(T d) { return T(-int(d)); } \ -inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; } \ -inline T& operator-=(T& d1, T d2) { return d1 = d1 - d2; } \ -inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } - -#define ENABLE_FULL_OPERATORS_ON(T) \ -ENABLE_BASE_OPERATORS_ON(T) \ -inline T& operator++(T& d) { return d = T(int(d) + 1); } \ -inline T& operator--(T& d) { return d = T(int(d) - 1); } \ -inline T operator/(T d, int i) { return T(int(d) / i); } \ -inline int operator/(T d1, T d2) { return int(d1) / int(d2); } \ +#define ENABLE_BASE_OPERATORS_ON(T) \ +constexpr T operator+(T d1, T d2) { return T(int(d1) + int(d2)); } \ +constexpr T operator-(T d1, T d2) { return T(int(d1) - int(d2)); } \ +constexpr T operator-(T d) { return T(-int(d)); } \ +inline T& operator+=(T& d1, T d2) { return d1 = d1 + d2; } \ +inline T& operator-=(T& d1, T d2) { return d1 = d1 - d2; } + +#define ENABLE_INCR_OPERATORS_ON(T) \ +inline T& operator++(T& d) { return d = T(int(d) + 1); } \ +inline T& operator--(T& d) { return d = T(int(d) - 1); } + +#define ENABLE_FULL_OPERATORS_ON(T) \ +ENABLE_BASE_OPERATORS_ON(T) \ +ENABLE_INCR_OPERATORS_ON(T) \ +constexpr T operator*(int i, T d) { return T(i * int(d)); } \ +constexpr T operator*(T d, int i) { return T(int(d) * i); } \ +constexpr T operator/(T d, int i) { return T(int(d) / i); } \ +constexpr int operator/(T d1, T d2) { return int(d1) / int(d2); } \ +inline T& operator*=(T& d, int i) { return d = T(int(d) * i); } \ inline T& operator/=(T& d, int i) { return d = T(int(d) / i); } ENABLE_FULL_OPERATORS_ON(Value) -ENABLE_FULL_OPERATORS_ON(PieceType) -ENABLE_FULL_OPERATORS_ON(Piece) -ENABLE_FULL_OPERATORS_ON(Color) ENABLE_FULL_OPERATORS_ON(Depth) -ENABLE_FULL_OPERATORS_ON(Square) -ENABLE_FULL_OPERATORS_ON(File) -ENABLE_FULL_OPERATORS_ON(Rank) +ENABLE_FULL_OPERATORS_ON(Direction) + +ENABLE_INCR_OPERATORS_ON(PieceType) +ENABLE_INCR_OPERATORS_ON(Piece) +ENABLE_INCR_OPERATORS_ON(Color) +ENABLE_INCR_OPERATORS_ON(Square) +ENABLE_INCR_OPERATORS_ON(File) +ENABLE_INCR_OPERATORS_ON(Rank) ENABLE_BASE_OPERATORS_ON(Score) #undef ENABLE_FULL_OPERATORS_ON +#undef ENABLE_INCR_OPERATORS_ON #undef ENABLE_BASE_OPERATORS_ON /// Additional operators to add integers to a Value -inline Value operator+(Value v, int i) { return Value(int(v) + i); } -inline Value operator-(Value v, int i) { return Value(int(v) - i); } +constexpr Value operator+(Value v, int i) { return Value(int(v) + i); } +constexpr Value operator-(Value v, int i) { return Value(int(v) - i); } inline Value& operator+=(Value& v, int i) { return v = v + i; } inline Value& operator-=(Value& v, int i) { return v = v - i; } +/// Additional operators to add a Direction to a Square +inline Square operator+(Square s, Direction d) { return Square(int(s) + int(d)); } +inline Square operator-(Square s, Direction d) { return Square(int(s) - int(d)); } +inline Square& operator+=(Square &s, Direction d) { return s = s + d; } +inline Square& operator-=(Square &s, Direction d) { return s = s - d; } + /// Only declared but not defined. We don't want to multiply two scores due to /// a very high risk of overflow. So user should explicitly convert to integer. -inline Score operator*(Score s1, Score s2); +Score operator*(Score, Score) = delete; /// Division of a Score must be handled separately for each term inline Score operator/(Score s, int i) { return make_score(mg_value(s) / i, eg_value(s) / i); } -inline Color operator~(Color c) { +/// Multiplication of a Score by an integer. We check for overflow in debug mode. +inline Score operator*(Score s, int i) { + + Score result = Score(int(s) * i); + + assert(eg_value(result) == (i * eg_value(s))); + assert(mg_value(result) == (i * mg_value(s))); + assert((i == 0) || (result / i) == s ); + + return result; +} + +constexpr Color operator~(Color c) { return Color(c ^ BLACK); // Toggle color } -inline Square operator~(Square s) { +constexpr Square operator~(Square s) { return Square(s ^ SQ_A8); // Vertical flip SQ_A1 -> SQ_A8 } -inline Piece operator~(Piece pc) { +constexpr File operator~(File f) { + return File(f ^ FILE_H); // Horizontal flip FILE_A -> FILE_H +} + +constexpr Piece operator~(Piece pc) { return Piece(pc ^ 8); // Swap color of piece B_KNIGHT -> W_KNIGHT } -inline CastlingRight operator|(Color c, CastlingSide s) { +constexpr CastlingRight operator|(Color c, CastlingSide s) { return CastlingRight(WHITE_OO << ((s == QUEEN_SIDE) + 2 * c)); } -inline Value mate_in(int ply) { +constexpr Value mate_in(int ply) { return VALUE_MATE - ply; } -inline Value mated_in(int ply) { +constexpr Value mated_in(int ply) { return -VALUE_MATE + ply; } -inline Square make_square(File f, Rank r) { +constexpr Square make_square(File f, Rank r) { return Square((r << 3) + f); } -inline Piece make_piece(Color c, PieceType pt) { +constexpr Piece make_piece(Color c, PieceType pt) { return Piece((c << 3) + pt); } -inline PieceType type_of(Piece pc) { +constexpr PieceType type_of(Piece pc) { return PieceType(pc & 7); } @@ -370,27 +397,27 @@ inline Color color_of(Piece pc) { return Color(pc >> 3); } -inline bool is_ok(Square s) { +constexpr bool is_ok(Square s) { return s >= SQ_A1 && s <= SQ_H8; } -inline File file_of(Square s) { +constexpr File file_of(Square s) { return File(s & 7); } -inline Rank rank_of(Square s) { +constexpr Rank rank_of(Square s) { return Rank(s >> 3); } -inline Square relative_square(Color c, Square s) { +constexpr Square relative_square(Color c, Square s) { return Square(s ^ (c * 56)); } -inline Rank relative_rank(Color c, Rank r) { +constexpr Rank relative_rank(Color c, Rank r) { return Rank(r ^ (c * 7)); } -inline Rank relative_rank(Color c, Square s) { +constexpr Rank relative_rank(Color c, Square s) { return relative_rank(c, rank_of(s)); } @@ -399,23 +426,27 @@ inline bool opposite_colors(Square s1, Square s2) { return ((s >> 3) ^ s) & 1; } -inline Square pawn_push(Color c) { +constexpr Direction pawn_push(Color c) { return c == WHITE ? NORTH : SOUTH; } -inline Square from_sq(Move m) { +constexpr Square from_sq(Move m) { return Square((m >> 6) & 0x3F); } -inline Square to_sq(Move m) { +constexpr Square to_sq(Move m) { return Square(m & 0x3F); } -inline MoveType type_of(Move m) { +constexpr int from_to(Move m) { + return m & 0xFFF; +} + +constexpr MoveType type_of(Move m) { return MoveType(m & (3 << 14)); } -inline PieceType promotion_type(Move m) { +constexpr PieceType promotion_type(Move m) { return PieceType(((m >> 12) & 3) + KNIGHT); } @@ -424,11 +455,11 @@ inline Move make_move(Square from, Square to) { } template -inline Move make(Square from, Square to, PieceType pt = KNIGHT) { +constexpr Move make(Square from, Square to, PieceType pt = KNIGHT) { return Move(T + ((pt - KNIGHT) << 12) + (from << 6) + to); } -inline bool is_ok(Move m) { +constexpr bool is_ok(Move m) { return from_sq(m) != to_sq(m); // Catch MOVE_NULL and MOVE_NONE } diff --git a/Engines/Windows/stockfish/src/uci.cpp b/Engines/Windows/stockfish/src/uci.cpp index b195b87..adba98d 100644 --- a/Engines/Windows/stockfish/src/uci.cpp +++ b/Engines/Windows/stockfish/src/uci.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ along with this program. If not, see . */ +#include #include #include #include @@ -27,30 +28,27 @@ #include "position.h" #include "search.h" #include "thread.h" +#include "tt.h" #include "timeman.h" #include "uci.h" +#include "syzygy/tbprobe.h" using namespace std; -extern void benchmark(const Position& pos, istream& is); +extern vector setup_bench(const Position&, istream&); namespace { // FEN string of the initial position, normal chess const char* StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; - // A list to keep track of the position states along the setup moves (from the - // start position to the position just before the search starts). Needed by - // 'draw by repetition' detection. - StateListPtr States(new std::deque(1)); - // position() is called when engine receives the "position" UCI command. // The function sets up the position described in the given FEN string ("fen") // or the starting position ("startpos") and then makes the moves given in the // following move list ("moves"). - void position(Position& pos, istringstream& is) { + void position(Position& pos, istringstream& is, StateListPtr& states) { Move m; string token, fen; @@ -68,14 +66,14 @@ namespace { else return; - States = StateListPtr(new std::deque(1)); - pos.set(fen, Options["UCI_Chess960"], &States->back(), Threads.main()); + states = StateListPtr(new std::deque(1)); // Drop old and create a new one + pos.set(fen, Options["UCI_Chess960"], &states->back(), Threads.main()); // Parse move list (if any) while (is >> token && (m = UCI::to_move(pos, token)) != MOVE_NONE) { - States->push_back(StateInfo()); - pos.do_move(m, States->back(), pos.gives_check(m)); + states->emplace_back(); + pos.do_move(m, states->back()); } } @@ -108,10 +106,11 @@ namespace { // the thinking time and other parameters from the input string, then starts // the search. - void go(Position& pos, istringstream& is) { + void go(Position& pos, istringstream& is, StateListPtr& states) { Search::LimitsType limits; string token; + bool ponderMode = false; limits.startTime = now(); // As early as possible! @@ -129,10 +128,53 @@ namespace { else if (token == "nodes") is >> limits.nodes; else if (token == "movetime") is >> limits.movetime; else if (token == "mate") is >> limits.mate; + else if (token == "perft") is >> limits.perft; else if (token == "infinite") limits.infinite = 1; - else if (token == "ponder") limits.ponder = 1; + else if (token == "ponder") ponderMode = true; + + Threads.start_thinking(pos, states, limits, ponderMode); + } + + + // bench() is called when engine receives the "bench" command. Firstly + // a list of UCI commands is setup according to bench parameters, then + // it is run one by one printing a summary at the end. + + void bench(Position& pos, istream& args, StateListPtr& states) { + + string token; + uint64_t num, nodes = 0, cnt = 1; + + vector list = setup_bench(pos, args); + num = count_if(list.begin(), list.end(), [](string s) { return s.find("go ") == 0; }); + + TimePoint elapsed = now(); - Threads.start_thinking(pos, States, limits); + for (const auto& cmd : list) + { + istringstream is(cmd); + is >> skipws >> token; + + if (token == "go") + { + cerr << "\nPosition: " << cnt++ << '/' << num << endl; + go(pos, is, states); + Threads.main()->wait_for_search_finished(); + nodes += Threads.nodes_searched(); + } + else if (token == "setoption") setoption(is); + else if (token == "position") position(pos, is, states); + else if (token == "ucinewgame") Search::clear(); + } + + elapsed = now() - elapsed + 1; // Ensure positivity to avoid a 'divide by zero' + + dbg_print(); // Just before exiting + + cerr << "\n===========================" + << "\nTotal time (ms) : " << elapsed + << "\nNodes searched : " << nodes + << "\nNodes/second : " << 1000 * nodes / elapsed << endl; } } // namespace @@ -148,8 +190,10 @@ void UCI::loop(int argc, char* argv[]) { Position pos; string token, cmd; + StateListPtr states(new std::deque(1)); + auto uiThread = std::make_shared(0); - pos.set(StartFEN, false, &States->back(), Threads.main()); + pos.set(StartFEN, false, &states->back(), uiThread.get()); for (int i = 1; i < argc; ++i) cmd += std::string(argv[i]) + " "; @@ -160,61 +204,42 @@ void UCI::loop(int argc, char* argv[]) { istringstream is(cmd); - token.clear(); // getline() could return empty or blank line + token.clear(); // Avoid a stale if getline() returns empty or blank line is >> skipws >> token; - // The GUI sends 'ponderhit' to tell us to ponder on the same move the - // opponent has played. In case Signals.stopOnPonderhit is set we are - // waiting for 'ponderhit' to stop the search (for instance because we - // already ran out of time), otherwise we should continue searching but - // switching from pondering to normal search. + // The GUI sends 'ponderhit' to tell us the user has played the expected move. + // So 'ponderhit' will be sent if we were told to ponder on the same move the + // user has played. We should continue searching but switch from pondering to + // normal search. In case Threads.stopOnPonderhit is set we are waiting for + // 'ponderhit' to stop the search, for instance if max search depth is reached. if ( token == "quit" || token == "stop" - || (token == "ponderhit" && Search::Signals.stopOnPonderhit)) - { - Search::Signals.stop = true; - Threads.main()->start_searching(true); // Could be sleeping - } + || (token == "ponderhit" && Threads.stopOnPonderhit)) + Threads.stop = true; + else if (token == "ponderhit") - Search::Limits.ponder = 0; // Switch to normal search + Threads.ponder = false; // Switch to normal search else if (token == "uci") sync_cout << "id name " << engine_info(true) << "\n" << Options << "\nuciok" << sync_endl; - else if (token == "ucinewgame") - { - Search::clear(); - Time.availableNodes = 0; - } - else if (token == "isready") sync_cout << "readyok" << sync_endl; - else if (token == "go") go(pos, is); - else if (token == "position") position(pos, is); else if (token == "setoption") setoption(is); + else if (token == "go") go(pos, is, states); + else if (token == "position") position(pos, is, states); + else if (token == "ucinewgame") Search::clear(); + else if (token == "isready") sync_cout << "readyok" << sync_endl; - // Additional custom non-UCI commands, useful for debugging - else if (token == "flip") pos.flip(); - else if (token == "bench") benchmark(pos, is); - else if (token == "d") sync_cout << pos << sync_endl; - else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; - else if (token == "perft") - { - int depth; - stringstream ss; - - is >> depth; - ss << Options["Hash"] << " " - << Options["Threads"] << " " << depth << " current perft"; - - benchmark(pos, ss); - } + // Additional custom non-UCI commands, mainly for debugging + else if (token == "flip") pos.flip(); + else if (token == "bench") bench(pos, is, states); + else if (token == "d") sync_cout << pos << sync_endl; + else if (token == "eval") sync_cout << Eval::trace(pos) << sync_endl; else sync_cout << "Unknown command: " << cmd << sync_endl; - } while (token != "quit" && argc == 1); // Passed args have one-shot behaviour - - Threads.main()->wait_for_search_finished(); + } while (token != "quit" && argc == 1); // Command line args are one-shot } @@ -227,6 +252,8 @@ void UCI::loop(int argc, char* argv[]) { string UCI::value(Value v) { + assert(-VALUE_INFINITE < v && v < VALUE_INFINITE); + stringstream ss; if (abs(v) < VALUE_MATE - MAX_PLY) diff --git a/Engines/Windows/stockfish/src/uci.h b/Engines/Windows/stockfish/src/uci.h index 4697877..0b3550b 100644 --- a/Engines/Windows/stockfish/src/uci.h +++ b/Engines/Windows/stockfish/src/uci.h @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -49,7 +49,7 @@ class Option { Option(OnChange = nullptr); Option(bool v, OnChange = nullptr); Option(const char* v, OnChange = nullptr); - Option(int v, int min, int max, OnChange = nullptr); + Option(int v, int minv, int maxv, OnChange = nullptr); Option& operator=(const std::string&); void operator<<(const Option&); diff --git a/Engines/Windows/stockfish/src/ucioption.cpp b/Engines/Windows/stockfish/src/ucioption.cpp index ab931bb..87ebaa8 100644 --- a/Engines/Windows/stockfish/src/ucioption.cpp +++ b/Engines/Windows/stockfish/src/ucioption.cpp @@ -2,7 +2,7 @@ Stockfish, a UCI chess playing engine derived from Glaurung 2.1 Copyright (C) 2004-2008 Tord Romstad (Glaurung author) Copyright (C) 2008-2015 Marco Costalba, Joona Kiiski, Tord Romstad - Copyright (C) 2015-2016 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad + Copyright (C) 2015-2018 Marco Costalba, Joona Kiiski, Gary Linscott, Tord Romstad Stockfish is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,7 +39,7 @@ namespace UCI { void on_clear_hash(const Option&) { Search::clear(); } void on_hash_size(const Option& o) { TT.resize(o); } void on_logger(const Option& o) { start_logger(o); } -void on_threads(const Option&) { Threads.read_uci_options(); } +void on_threads(const Option& o) { Threads.set(o); } void on_tb_path(const Option& o) { Tablebases::init(o); } @@ -55,11 +55,12 @@ bool CaseInsensitiveLess::operator() (const string& s1, const string& s2) const void init(OptionsMap& o) { - const int MaxHashMB = Is64Bit ? 1024 * 1024 : 2048; + // at most 2^32 clusters. + const int MaxHashMB = Is64Bit ? 131072 : 2048; o["Debug Log File"] << Option("", on_logger); - o["Contempt"] << Option(0, -100, 100); - o["Threads"] << Option(1, 1, 128, on_threads); + o["Contempt"] << Option(20, -100, 100); + o["Threads"] << Option(1, 1, 512, on_threads); o["Hash"] << Option(16, 1, MaxHashMB, on_hash_size); o["Clear Hash"] << Option(on_clear_hash); o["Ponder"] << Option(false); diff --git a/GenIconos/Formatos.tema b/GenIconos/Formatos.tema index 121b1d4..f07f675 100644 --- a/GenIconos/Formatos.tema +++ b/GenIconos/Formatos.tema @@ -17,7 +17,7 @@ Utilidades gnome 64px-Gnome-applications-utilities.png Terminar woocons application-exit.png NuevaPartida nuvola run.png -Opciones woocons cog.png +Opciones woocons Cog.png Entrenamiento nuvola edu_miscellaneous.png Aplazar nuvola history_clear.png Capturas nuvola kmines.png @@ -97,7 +97,7 @@ Pegar16 silk paste_plain.png RivalesMP nuvola konquest.png -Camara woocons camera.png +Camara woocons Camera.png Usuarios nuvola kuser.png @@ -265,7 +265,7 @@ BookGuide silk book_open.png Mas qt4 addtab(3).png MasR qt4 addtab(3)R.png -MasDoc woocons file_Add.png +MasDoc woocons File_Add.png NuevaDB gnome 64px-Gnome-text-x-preview_qt.png Potencia silk lightning.png @@ -452,10 +452,11 @@ WorldMap nuvola homepage.png Africa lucas BlankMap-Africa.png Maps qt4 icon-m-ovi-service-maps.png -Sol gnome 64px-Gnome-weather-clear.png -SolNubes gnome 64px-Gnome-weather-few-clouds.png -Nubes gnome 64px-Gnome-weather-overcast.png -Tormenta gnome 64px-Gnome-weather-storm.png +Sol windows8 Summer_32px.png +SolNubes windows8 Partly_Cloudy_Day_32px.png +SolNubesLluvia windows8 Rain_Cloud_32px.png +Lluvia windows8 Rain_32px.png +Invierno windows8 Snowflake_32px.png Words gnome 64px-Im-nov.png AdaptVoice gnome 64px-Gnome-preferences-desktop.png @@ -528,3 +529,10 @@ Study windows8 Under_Computer_32px.png Lichess otros lichess.png Miniatura windows8 Microscope_26px.png + +Locomotora windows8 Steam_Engine_26px.png +Positions nuvola runMono.png + +TrainSequential windows8 Running_32px.png +TrainStatic windows8 Gymnastics_32px.png +TrainPositions windows8 Pullups_32px.png diff --git a/GenIconos/gnome/64px-Gnome-weather-clear.png b/GenIconos/gnome/64px-Gnome-weather-clear.png deleted file mode 100644 index 8a444ca..0000000 Binary files a/GenIconos/gnome/64px-Gnome-weather-clear.png and /dev/null differ diff --git a/GenIconos/gnome/64px-Gnome-weather-few-clouds.png b/GenIconos/gnome/64px-Gnome-weather-few-clouds.png deleted file mode 100644 index 25f996e..0000000 Binary files a/GenIconos/gnome/64px-Gnome-weather-few-clouds.png and /dev/null differ diff --git a/GenIconos/gnome/64px-Gnome-weather-overcast.png b/GenIconos/gnome/64px-Gnome-weather-overcast.png deleted file mode 100644 index 2f05ebb..0000000 Binary files a/GenIconos/gnome/64px-Gnome-weather-overcast.png and /dev/null differ diff --git a/GenIconos/gnome/64px-Gnome-weather-storm.png b/GenIconos/gnome/64px-Gnome-weather-storm.png deleted file mode 100644 index 3f9d0ed..0000000 Binary files a/GenIconos/gnome/64px-Gnome-weather-storm.png and /dev/null differ diff --git a/GenIconos/limpia.py b/GenIconos/limpia.py new file mode 100644 index 0000000..6d07ab5 --- /dev/null +++ b/GenIconos/limpia.py @@ -0,0 +1,55 @@ +# -*- coding: latin-1 -*- +import os +# import shutil + + + +def leeFormato(): + with open("Formatos.tema") as f: + li = [] + for linea in f: + linea = linea.strip() + if linea and not linea.strip().startswith("#"): + nombre, folder, png = linea.split(" ") + folder = folder.replace("/", "\\") + li.append(os.path.join(".", folder, png)) + return li + +def leeCarpeta(li, carpeta): + for x in os.listdir(carpeta): + x = os.path.join(carpeta, x) + if os.path.isdir(x): + leeCarpeta(li, x) + elif x.endswith(".png"): + li.append(x) + + +liFormato = leeFormato() + +liPNG = [] +leeCarpeta(liPNG, ".") + + +for x in liFormato: + if x not in liPNG: + print "Falta el fichero", x + +for x in liPNG: + if x not in liFormato: + print "Borrado el fichero", x + os.remove(x) + + + + + +# miraCarpeta( "." ) + +# for x in linom : + # if x not in li : + # print "Si esta en formato y no en codigo", x +# for x in li : + # if x not in linom : + # print "No esta en formato y si en codigo", x + + diff --git a/GenIconos/nuvola/runMono.png b/GenIconos/nuvola/runMono.png new file mode 100644 index 0000000..37e886d Binary files /dev/null and b/GenIconos/nuvola/runMono.png differ diff --git a/GenIconos/otros/led_circle_orange.png b/GenIconos/otros/led_circle_orange.png deleted file mode 100644 index cd2354d..0000000 Binary files a/GenIconos/otros/led_circle_orange.png and /dev/null differ diff --git a/GenIconos/silk/arrow_joinR.png b/GenIconos/silk/arrow_joinR.png deleted file mode 100644 index c0259c8..0000000 Binary files a/GenIconos/silk/arrow_joinR.png and /dev/null differ diff --git a/GenIconos/vehicles/Train.png b/GenIconos/vehicles/Train.png deleted file mode 100644 index 3939228..0000000 Binary files a/GenIconos/vehicles/Train.png and /dev/null differ diff --git a/GenIconos/vehicles/Train1.png b/GenIconos/vehicles/Train1.png deleted file mode 100644 index 26ca80e..0000000 Binary files a/GenIconos/vehicles/Train1.png and /dev/null differ diff --git a/GenIconos/windows8/Active_Directory_32px.png b/GenIconos/windows8/Active_Directory_32px.png deleted file mode 100644 index 63191ef..0000000 Binary files a/GenIconos/windows8/Active_Directory_32px.png and /dev/null differ diff --git a/GenIconos/windows8/Ascending_Sorting_32px.png b/GenIconos/windows8/Ascending_Sorting_32px.png deleted file mode 100644 index 63dc1a4..0000000 Binary files a/GenIconos/windows8/Ascending_Sorting_32px.png and /dev/null differ diff --git a/GenIconos/windows8/Books_32px.png b/GenIconos/windows8/Books_32px.png deleted file mode 100644 index 2504675..0000000 Binary files a/GenIconos/windows8/Books_32px.png and /dev/null differ diff --git a/GenIconos/windows8/Code_Fork_32px.png b/GenIconos/windows8/Code_Fork_32px.png deleted file mode 100644 index c2e925b..0000000 Binary files a/GenIconos/windows8/Code_Fork_32px.png and /dev/null differ diff --git a/GenIconos/windows8/Coral_32px.png b/GenIconos/windows8/Coral_32px.png deleted file mode 100644 index c0e138f..0000000 Binary files a/GenIconos/windows8/Coral_32px.png and /dev/null differ diff --git a/GenIconos/windows8/Factory_32px.png b/GenIconos/windows8/Factory_32px.png deleted file mode 100644 index a590aa9..0000000 Binary files a/GenIconos/windows8/Factory_32px.png and /dev/null differ diff --git a/GenIconos/windows8/Google_Sketchup_32px.png b/GenIconos/windows8/Google_Sketchup_32px.png deleted file mode 100644 index bafdf98..0000000 Binary files a/GenIconos/windows8/Google_Sketchup_32px.png and /dev/null differ diff --git a/GenIconos/windows8/Gymnastics_32px.png b/GenIconos/windows8/Gymnastics_32px.png new file mode 100644 index 0000000..15b88ff Binary files /dev/null and b/GenIconos/windows8/Gymnastics_32px.png differ diff --git a/GenIconos/windows8/Partly_Cloudy_Day_32px.png b/GenIconos/windows8/Partly_Cloudy_Day_32px.png new file mode 100644 index 0000000..c16e720 Binary files /dev/null and b/GenIconos/windows8/Partly_Cloudy_Day_32px.png differ diff --git a/GenIconos/windows8/Pullups_32px.png b/GenIconos/windows8/Pullups_32px.png new file mode 100644 index 0000000..093f5d8 Binary files /dev/null and b/GenIconos/windows8/Pullups_32px.png differ diff --git a/GenIconos/windows8/Rain_32px.png b/GenIconos/windows8/Rain_32px.png new file mode 100644 index 0000000..f05f628 Binary files /dev/null and b/GenIconos/windows8/Rain_32px.png differ diff --git a/GenIconos/windows8/Rain_Cloud_32px.png b/GenIconos/windows8/Rain_Cloud_32px.png new file mode 100644 index 0000000..9abd24e Binary files /dev/null and b/GenIconos/windows8/Rain_Cloud_32px.png differ diff --git a/GenIconos/windows8/Running_32px.png b/GenIconos/windows8/Running_32px.png new file mode 100644 index 0000000..ec0df8b Binary files /dev/null and b/GenIconos/windows8/Running_32px.png differ diff --git a/GenIconos/windows8/Share_18px.png b/GenIconos/windows8/Share_18px.png deleted file mode 100644 index ca7633d..0000000 Binary files a/GenIconos/windows8/Share_18px.png and /dev/null differ diff --git a/GenIconos/windows8/Snowflake_32px.png b/GenIconos/windows8/Snowflake_32px.png new file mode 100644 index 0000000..69f0e4a Binary files /dev/null and b/GenIconos/windows8/Snowflake_32px.png differ diff --git a/GenIconos/windows8/Steam_Engine_26px.png b/GenIconos/windows8/Steam_Engine_26px.png new file mode 100644 index 0000000..286f33d Binary files /dev/null and b/GenIconos/windows8/Steam_Engine_26px.png differ diff --git a/GenIconos/windows8/Summer_32px.png b/GenIconos/windows8/Summer_32px.png new file mode 100644 index 0000000..9590a0f Binary files /dev/null and b/GenIconos/windows8/Summer_32px.png differ diff --git a/GenIconos/windows8/University_32px.png b/GenIconos/windows8/University_32px.png deleted file mode 100644 index 4628af8..0000000 Binary files a/GenIconos/windows8/University_32px.png and /dev/null differ diff --git a/GenIconos/windows8/dashboard-32.png b/GenIconos/windows8/dashboard-32.png deleted file mode 100644 index c47aac6..0000000 Binary files a/GenIconos/windows8/dashboard-32.png and /dev/null differ diff --git a/GenIconos/windows8/define_location-26.png b/GenIconos/windows8/define_location-26.png deleted file mode 100644 index 711ee66..0000000 Binary files a/GenIconos/windows8/define_location-26.png and /dev/null differ diff --git a/GenIconos/windows8/documents-32.png b/GenIconos/windows8/documents-32.png deleted file mode 100644 index 730cfb5..0000000 Binary files a/GenIconos/windows8/documents-32.png and /dev/null differ diff --git a/GenIconos/windows8/engine-26.png b/GenIconos/windows8/engine-26.png deleted file mode 100644 index 396a3e4..0000000 Binary files a/GenIconos/windows8/engine-26.png and /dev/null differ diff --git a/GenIconos/windows8/factory-32.png b/GenIconos/windows8/factory-32.png deleted file mode 100644 index 8199f2e..0000000 Binary files a/GenIconos/windows8/factory-32.png and /dev/null differ diff --git a/GenIconos/windows8/folder-32.png b/GenIconos/windows8/folder-32.png deleted file mode 100644 index 8ed4cca..0000000 Binary files a/GenIconos/windows8/folder-32.png and /dev/null differ diff --git a/GenIconos/windows8/greentech-32.png b/GenIconos/windows8/greentech-32.png deleted file mode 100644 index 60e3951..0000000 Binary files a/GenIconos/windows8/greentech-32.png and /dev/null differ diff --git a/GenIconos/windows8/online-26.png b/GenIconos/windows8/online-26.png deleted file mode 100644 index 98b5f6b..0000000 Binary files a/GenIconos/windows8/online-26.png and /dev/null differ diff --git a/GenIconos/windows8/research-26.png b/GenIconos/windows8/research-26.png deleted file mode 100644 index cd13392..0000000 Binary files a/GenIconos/windows8/research-26.png and /dev/null differ diff --git a/GenIconos/windows8/research-32.png b/GenIconos/windows8/research-32.png deleted file mode 100644 index 4ef164b..0000000 Binary files a/GenIconos/windows8/research-32.png and /dev/null differ diff --git a/GenIconos/windows8/settings-26.png b/GenIconos/windows8/settings-26.png deleted file mode 100644 index 5f086a3..0000000 Binary files a/GenIconos/windows8/settings-26.png and /dev/null differ diff --git a/GenIconos/windows8/speedometer-26.png b/GenIconos/windows8/speedometer-26.png deleted file mode 100644 index c865254..0000000 Binary files a/GenIconos/windows8/speedometer-26.png and /dev/null differ diff --git a/IntFiles/Figs/bb.png b/IntFiles/Figs/bb.png index 7a3e0e0..4207e62 100644 Binary files a/IntFiles/Figs/bb.png and b/IntFiles/Figs/bb.png differ diff --git a/IntFiles/Figs/bk.png b/IntFiles/Figs/bk.png index c61353b..5cabf24 100644 Binary files a/IntFiles/Figs/bk.png and b/IntFiles/Figs/bk.png differ diff --git a/IntFiles/Figs/bn.png b/IntFiles/Figs/bn.png index bb0b80e..2825893 100644 Binary files a/IntFiles/Figs/bn.png and b/IntFiles/Figs/bn.png differ diff --git a/IntFiles/Figs/bp.png b/IntFiles/Figs/bp.png index 2d7a07d..a6b22d8 100644 Binary files a/IntFiles/Figs/bp.png and b/IntFiles/Figs/bp.png differ diff --git a/IntFiles/Figs/bq.png b/IntFiles/Figs/bq.png index 699faf1..cfdfaab 100644 Binary files a/IntFiles/Figs/bq.png and b/IntFiles/Figs/bq.png differ diff --git a/IntFiles/Figs/br.png b/IntFiles/Figs/br.png index 3cf9eca..5b87efd 100644 Binary files a/IntFiles/Figs/br.png and b/IntFiles/Figs/br.png differ diff --git a/IntFiles/Figs/wb.png b/IntFiles/Figs/wb.png index ff1c569..5c5afd0 100644 Binary files a/IntFiles/Figs/wb.png and b/IntFiles/Figs/wb.png differ diff --git a/IntFiles/Figs/wk.png b/IntFiles/Figs/wk.png index 8004479..7431cf6 100644 Binary files a/IntFiles/Figs/wk.png and b/IntFiles/Figs/wk.png differ diff --git a/IntFiles/Figs/wn.png b/IntFiles/Figs/wn.png index 22bfa7b..7b6b519 100644 Binary files a/IntFiles/Figs/wn.png and b/IntFiles/Figs/wn.png differ diff --git a/IntFiles/Figs/wp.png b/IntFiles/Figs/wp.png index a944c4a..cbdd9e5 100644 Binary files a/IntFiles/Figs/wp.png and b/IntFiles/Figs/wp.png differ diff --git a/IntFiles/Figs/wq.png b/IntFiles/Figs/wq.png index 02b3778..87f4ac7 100644 Binary files a/IntFiles/Figs/wq.png and b/IntFiles/Figs/wq.png differ diff --git a/IntFiles/Figs/wr.png b/IntFiles/Figs/wr.png index 16dc87d..5315f14 100644 Binary files a/IntFiles/Figs/wr.png and b/IntFiles/Figs/wr.png differ diff --git a/IntFiles/Iconos.bin b/IntFiles/Iconos.bin index a364ba5..7500ee5 100644 Binary files a/IntFiles/Iconos.bin and b/IntFiles/Iconos.bin differ diff --git a/IntFiles/base.board b/IntFiles/base.board index 1f719c9..20d33f1 100644 --- a/IntFiles/base.board +++ b/IntFiles/base.board @@ -1,92 +1,7515 @@ -ANjTCt8K3NDI5NDM3NTQxMyM0MjkyNDY0MzA2IzQyODc5MjUzNjUjS0dsRGIyUmxMbEZVTGxSaFls +ANjTCt8K3NDI5NDA0NjE5MyM0MjkzNTg1NjMyIzQyODgyMzQ3ODgjS0dsRGIyUmxMbEZVTGxSaFls UnBjRzl6Q2tac1pXTm9ZUXB3TVFvb1pIQXlDbE1uYjNCaFkybGtZV1FuQ25BekNrWXhDbk5USjJa dnxjbTFoSndwd05BcFRKek1uQ25OVEoyTnZiRzl5YVc1MFpYSnBiM0l5Sndwd05RcEpMVEVLYzFN -bllXNWphRzlEWVhOcGJHeGhKd3B3fE5ncEpNellLYzFNbllURm9PQ2NLY0RjS1V5ZGtNbVEySndw -d09BcHpVeWRoYm1Ob2J5Y0tjRGtLU1RRS2MxTW5ZMjlzYjNJbkNuQXh8TUFwTU5ESTRPREkyTWpn -ek9Vd0tjMU1uZG5WbGJHOG5DbkF4TVFwSk5RcHpVeWR3YjNOcFkybHZiaWNLY0RFeUNpaHBRMjlr -WlM1UnxWQzVVWVdKVWFYQnZjd3BRYjNOcFkybHZiZ3B3TVRNS0tHUndNVFFLVXlkaGJIUnZKd3B3 -TVRVS1NURTJDbk5USjI5eVpHVnVKd3B3fE1UWUtTVGtLYzJjNUNra3hNamdLYzFNbllXNW5kV3h2 -Sndwd01UY0tTVEFLYzFNbmVTY0tTVEUyQ25OVEozZ25Da2t3Q25OaWMxTW58WkdWemRHbHVieWNL -Y0RFNENsTW5iU2NLYzFNbmNtVmtiMjVrWlc5ekp3cHdNVGtLU1RBeENuTlRKMkZzZEc5allXSmxl -bUVuQ25BeXxNQXBKTWpBS2MxTW5iR2xXWVhKekp3cHdNakVLS0d4d01qSUtLRk1uY0c5emFXTnBi -MjRuQ2xNbmJ5Y0tkSEF5TXdwaEtGTW5ZVEZvfE9DY0tVeWRqSndwMGNESTBDbUVvVXlkbmNtOXpi -M0luQ2xNbmJpY0tkSEF5TlFwaEtGTW5ZV3gwYjJOaFltVjZZU2NLVXlkdUp3cDB8Y0RJMkNtRW9V -eWQwYVhCdkp3cFRKMjRuQ25Sd01qY0tZU2hUSjJSbGMzUnBibThuQ2xNbmRDY0tkSEF5T0FwaEtG -TW5ZVzVqYUc5RHxZWE5wYkd4aEp3cFRKMjRuQ25Sd01qa0tZU2hUSjJOdmJHOXlKd3BUSjI0bkNu -UndNekFLWVNoVEoyTnZiRzl5YVc1MFpYSnBiM0lufENsTW5iaWNLZEhBek1RcGhLRk1uWTI5c2Iz -SnBiblJsY21sdmNqSW5DbE1uYmljS2RIQXpNZ3BoS0ZNbmIzQmhZMmxrWVdRbkNsTW58YmljS2RI -QXpNd3BoS0ZNbmNtVmtiMjVrWlc5ekp3cFRKMnduQ25Sd016UUtZU2hUSjJadmNtMWhKd3BUSjNR -bkNuUndNelVLWVNoVHxKMkZ1WTJodkp3cFRKMjRuQ25Sd016WUtZU2hUSjNaMVpXeHZKd3BUSjI0 -bkNuUndNemNLWVNoVEoyUmxjMk4xWld4bmRXVW5DbE1ufGJpY0tkSEF6T0FwaGMxTW5ZMjlzYjNK -cGJuUmxjbWx2Y2ljS2NETTVDa3cwTWpnNE1qWXlPRE01VEFwelV5ZGtaWE5qZFdWc1ozVmx8Sndw -d05EQUtTVFlLYzFNbmMybE5iM1pwWW14bEp3cHdOREVLU1RBeENuTlRKMmR5YjNOdmNpY0tjRFF5 -Q2trekNuTlRKM1JwY0c4bnxDbkEwTXdwSk1RcHpZaTQ9fCM0Mjg2MjEyNjkxIzQyODc5MjUzNjUj -RmFsc2UjS0dsRGIyUmxMbEZVTGxSaFlsUnBjRzl6Q2tac1pXTm9ZUXB3TVFvb1pIQXlDbE1uYjNC -aFkybGtZV1FuQ25BekNrWXhDbk5USjJadnxjbTFoSndwd05BcFRKMkVuQ25OVEoyTnZiRzl5YVc1 -MFpYSnBiM0l5Sndwd05RcEpMVEVLYzFNbllXNWphRzlEWVhOcGJHeGhKd3B3fE5ncEpNellLYzFN -bmRHbHdieWNLY0RjS1NUSUtjMU1uWVc1amFHOG5DbkE0Q2trMENuTlRKMk52Ykc5eUp3cHdPUXBN -TkRJNE9ESTJ8TWpnek9Vd0tjMU1uWkdWelkzVmxiR2QxWlNjS2NERXdDa2syQ25OVEoyRXhhRGdu -Q25BeE1RcFRKMlF5WkRZbkNuQXhNZ3B6VXlkMnxkV1ZzYnljS2NERXpDa2sxQ25OVEozQnZjMmxq -YVc5dUp3cHdNVFFLS0dsRGIyUmxMbEZVTGxSaFlsUnBjRzl6Q2xCdmMybGphVzl1fENuQXhOUW9v -WkhBeE5ncFRKMkZzZEc4bkNuQXhOd3BKTVRZS2MxTW5iM0prWlc0bkNuQXhPQXBKT1Fwelp6Z0tT -VEV5T0FwelV5ZGh8Ym1kMWJHOG5DbkF4T1FwSk1BcHpVeWQ1SndwSk1UWUtjMU1uZUNjS1NUQUtj -Mkp6VXlka1pYTjBhVzV2Sndwd01qQUtVeWR0SndwenxVeWR5WldSdmJtUmxiM01uQ25BeU1RcEpN -REVLYzFNbllXeDBiMk5oWW1WNllTY0tjREl5Q2treU1BcHpVeWRqYjJ4dmNtbHVkR1Z5fGFXOXlK -d3B3TWpNS1REUXlPRGd5TmpJNE16bE1Dbk5USjJ4cFZtRnljeWNLY0RJMENpaHNjREkxQ2loVEoz -QnZjMmxqYVc5dUp3cFR8SjI4bkNuUndNallLWVNoVEoyRXhhRGduQ2xNbll5Y0tkSEF5TndwaEtG -TW5aM0p2YzI5eUp3cFRKMjRuQ25Sd01qZ0tZU2hUSjJGc3xkRzlqWVdKbGVtRW5DbE1uYmljS2RI -QXlPUXBoS0ZNbmRHbHdieWNLVXlkdUp3cDBjRE13Q21Fb1V5ZGtaWE4wYVc1dkp3cFRKM1FufENu -UndNekVLWVNoVEoyRnVZMmh2UTJGemFXeHNZU2NLVXlkdUp3cDBjRE15Q21Fb1V5ZGpiMnh2Y2lj -S1V5ZHVKd3AwY0RNekNtRW98VXlkamIyeHZjbWx1ZEdWeWFXOXlKd3BUSjI0bkNuUndNelFLWVNo -VEoyTnZiRzl5YVc1MFpYSnBiM0l5SndwVEoyNG5DblJ3TXpVS3xZU2hUSjI5d1lXTnBaR0ZrSndw -VEoyNG5DblJ3TXpZS1lTaFRKM0psWkc5dVpHVnZjeWNLVXlkc0p3cDBjRE0zQ21Fb1V5ZG1iM0p0 -fFlTY0tVeWQwSndwMGNETTRDbUVvVXlkaGJtTm9ieWNLVXlkdUp3cDBjRE01Q21Fb1V5ZDJkV1Zz -YnljS1V5ZHVKd3AwY0RRd0NtRW98VXlka1pYTmpkV1ZzWjNWbEp3cFRKMjRuQ25Sd05ERUtZWE5U -SjNOcFRXOTJhV0pzWlNjS2NEUXlDa2t3TVFwelV5ZG5jbTl6YjNJbnxDbkEwTXdwSk13cHpZaTQ9 -fCMjIzAjMCM0Mjk0NzY5NjM1IyNLR2xEYjJSbExsRlVMbFJoWWxScGNHOXpDa1pzWldOb1lRcHdN -UW9vWkhBeUNsTW5iM0JoWTJsa1lXUW5DbkF6Q2tZeENuTlRKMlp2fGNtMWhKd3B3TkFwVEp6TW5D -bk5USjJOdmJHOXlhVzUwWlhKcGIzSXlKd3B3TlFwSkxURUtjMU1uWVc1amFHOURZWE5wYkd4aEp3 -cHd8TmdwSk16WUtjMU1uWVc1amFHOG5DbkEzQ2trMUNuTlRKM1JwY0c4bkNuQTRDa2t4Q25OVEoy -TnZiRzl5Sndwd09RcE1OREk0TXpjMnxNRGMyTjB3S2MxTW5aR1Z6WTNWbGJHZDFaU2NLY0RFd0Nr -azJDbk5USjJFeGFEZ25DbkF4TVFwVEoyRXhhRGduQ25BeE1ncHpVeWQyfGRXVnNieWNLY0RFekNr -azBDbk5USjJOdmJHOXlhVzUwWlhKcGIzSW5DbkF4TkFwTU5ESTVORGt5TXpVeU1Fd0tjMU1uWkdW -emRHbHV8YnljS2NERTFDbE1uYlNjS2MxTW5jbVZrYjI1a1pXOXpKd3B3TVRZS1NUQXdDbk5USjJG -c2RHOWpZV0psZW1FbkNuQXhOd3BKTWpJS3xjMU1uY0c5emFXTnBiMjRuQ25BeE9Bb29hVU52WkdV -dVVWUXVWR0ZpVkdsd2IzTUtVRzl6YVdOcGIyNEtjREU1Q2loa2NESXdDbE1ufFlXeDBieWNLY0RJ -eENra3hOZ3B6VXlkdmNtUmxiaWNLY0RJeUNrazRDbk5uTndwSk1USTRDbk5USjJGdVozVnNieWNL -Y0RJekNra3d8Q25OVEoza25Da2t4TmdwelV5ZDRKd3BKTUFwelluTlRKMnhwVm1GeWN5Y0tjREkw -Q2loc2NESTFDaWhUSjNCdmMybGphVzl1SndwVHxKMjhuQ25Sd01qWUtZU2hUSjJFeGFEZ25DbE1u -WXljS2RIQXlOd3BoS0ZNblozSnZjMjl5SndwVEoyNG5DblJ3TWpnS1lTaFRKMkZzfGRHOWpZV0ps -ZW1FbkNsTW5iaWNLZEhBeU9RcGhLRk1uZEdsd2J5Y0tVeWR1SndwMGNETXdDbUVvVXlka1pYTjBh -VzV2SndwVEozUW58Q25Sd016RUtZU2hUSjJGdVkyaHZRMkZ6YVd4c1lTY0tVeWR1SndwMGNETXlD -bUVvVXlkamIyeHZjaWNLVXlkdUp3cDBjRE16Q21Fb3xVeWRqYjJ4dmNtbHVkR1Z5YVc5eUp3cFRK -MjRuQ25Sd016UUtZU2hUSjJOdmJHOXlhVzUwWlhKcGIzSXlKd3BUSjI0bkNuUndNelVLfFlTaFRK -Mjl3WVdOcFpHRmtKd3BUSjI0bkNuUndNellLWVNoVEozSmxaRzl1WkdWdmN5Y0tVeWRzSndwMGNE -TTNDbUVvVXlkbWIzSnR8WVNjS1V5ZDBKd3AwY0RNNENtRW9VeWRoYm1Ob2J5Y0tVeWR1SndwMGNE -TTVDbUVvVXlkMmRXVnNieWNLVXlkdUp3cDBjRFF3Q21Fb3xVeWRrWlhOamRXVnNaM1ZsSndwVEoy -NG5DblJ3TkRFS1lTaFRKM0J1WnljS1V5ZGpKd3AwY0RReUNtRnpVeWR6YVUxdmRtbGliR1VufENu -QTBNd3BKTURBS2MxTW5aM0p2YzI5eUp3cHdORFFLU1RFS2MxTW5jRzVuSndwd05EVUtVeWNuQ25O -aUxnPT18I0tHbERiMlJsTGxGVUxsUmhZbFJwY0c5ekNrWnNaV05vWVFwd01Rb29aSEF5Q2xNbmIz -QmhZMmxrWVdRbkNuQXpDa1l4Q25OVEoyWnZ8Y20xaEp3cHdOQXBUSnpNbkNuTlRKMk52Ykc5eWFX -NTBaWEpwYjNJeUp3cHdOUXBKTFRFS2MxTW5ZVzVqYUc5RFlYTnBiR3hoSndwd3xOZ3BKTXpZS2Mx -TW5ZVzVqYUc4bkNuQTNDa2sxQ25OVEozUnBjRzhuQ25BNENra3hDbk5USjJOdmJHOXlKd3B3T1Fw -TU5ESTRNVGMwfE9UYzJNRXdLYzFNblpHVnpZM1ZsYkdkMVpTY0tjREV3Q2trMkNuTlRKMkV4YURn -bkNuQXhNUXBUSjJFeGFEZ25DbkF4TWdwelV5ZDJ8ZFdWc2J5Y0tjREV6Q2trMENuTlRKMk52Ykc5 -eWFXNTBaWEpwYjNJbkNuQXhOQXBNTkRJNE9UTTVOalE0TUV3S2MxTW5aR1Z6ZEdsdXxieWNLY0RF -MUNsTW5iU2NLYzFNbmNtVmtiMjVrWlc5ekp3cHdNVFlLU1RBd0NuTlRKMkZzZEc5allXSmxlbUVu -Q25BeE53cEpNaklLfGMxTW5jRzl6YVdOcGIyNG5DbkF4T0Fvb2FVTnZaR1V1VVZRdVZHRmlWR2x3 -YjNNS1VHOXphV05wYjI0S2NERTVDaWhrY0RJd0NsTW58WVd4MGJ5Y0tjREl4Q2treE5ncHpVeWR2 -Y21SbGJpY0tjREl5Q2trNENuTm5Od3BKTVRJNENuTlRKMkZ1WjNWc2J5Y0tjREl6Q2trd3xDbk5U -SjNrbkNra3hOZ3B6VXlkNEp3cEpNQXB6WW5OVEoyeHBWbUZ5Y3ljS2NESTBDaWhzY0RJMUNpaFRK -M0J2YzJsamFXOXVKd3BUfEoyOG5DblJ3TWpZS1lTaFRKMkV4YURnbkNsTW5ZeWNLZEhBeU53cGhL -Rk1uWjNKdmMyOXlKd3BUSjI0bkNuUndNamdLWVNoVEoyRnN8ZEc5allXSmxlbUVuQ2xNbmJpY0tk -SEF5T1FwaEtGTW5kR2x3YnljS1V5ZHVKd3AwY0RNd0NtRW9VeWRrWlhOMGFXNXZKd3BUSjNRbnxD -blJ3TXpFS1lTaFRKMkZ1WTJodlEyRnphV3hzWVNjS1V5ZHVKd3AwY0RNeUNtRW9VeWRqYjJ4dmNp -Y0tVeWR1SndwMGNETXpDbUVvfFV5ZGpiMnh2Y21sdWRHVnlhVzl5SndwVEoyNG5DblJ3TXpRS1lT -aFRKMk52Ykc5eWFXNTBaWEpwYjNJeUp3cFRKMjRuQ25Sd016VUt8WVNoVEoyOXdZV05wWkdGa0p3 -cFRKMjRuQ25Sd016WUtZU2hUSjNKbFpHOXVaR1Z2Y3ljS1V5ZHNKd3AwY0RNM0NtRW9VeWRtYjNK -dHxZU2NLVXlkMEp3cDBjRE00Q21Fb1V5ZGhibU5vYnljS1V5ZHVKd3AwY0RNNUNtRW9VeWQyZFdW -c2J5Y0tVeWR1SndwMGNEUXdDbUVvfFV5ZGtaWE5qZFdWc1ozVmxKd3BUSjI0bkNuUndOREVLWVNo -VEozQnVaeWNLVXlkakp3cDBjRFF5Q21GelV5ZHphVTF2ZG1saWJHVW58Q25BME13cEpNREFLYzFN -blozSnZjMjl5Sndwd05EUUtTVEVLYzFNbmNHNW5Kd3B3TkRVS1V5Y25Dbk5pTGc9PXwjIzAjwrdD -YnVybmV0dCNBcmlhbCNTIzYwIzU2IzIjMTEwIzEwMA== +bllXNWphRzlEWVhOcGJHeGhKd3B3fE5ncEpNellLYzFNbmRHbHdieWNLY0RjS1NURUtjMU1uWVc1 +amFHOG5DbkE0Q2trMENuTlRKMk52Ykc5eUp3cHdPUXBNTkRJNE5EVTN8TmpFeE5rd0tjMU1uWVRG +b09DY0tjREV3Q2xNbmFEVmlOeWNLY0RFeENuTlRKM1oxWld4dkp3cHdNVElLU1RVS2MxTW5jRzl6 +YVdOcHxiMjRuQ25BeE13b29hVU52WkdVdVVWUXVWR0ZpVkdsd2IzTUtVRzl6YVdOcGIyNEtjREUw +Q2loa2NERTFDbE1uWVd4MGJ5Y0tjREUyfENra3hOZ3B6VXlkdmNtUmxiaWNLY0RFM0NrazVDbk5u +T0FwSk1USTRDbk5USjJGdVozVnNieWNLY0RFNENra3dDbk5USjNrbkNra3h8TmdwelV5ZDRKd3BK +TUFwelluTlRKMlJsYzNScGJtOG5DbkF4T1FwVEoyMG5Dbk5USjNKbFpHOXVaR1Z2Y3ljS2NESXdD +a2t3TVFwenxVeWRoYkhSdlkyRmlaWHBoSndwd01qRUtTVEl3Q25OVEoyeHBWbUZ5Y3ljS2NESXlD +aWhzY0RJekNpaFRKM0J2YzJsamFXOXVKd3BUfEoyOG5DblJ3TWpRS1lTaFRKMkV4YURnbkNsTW5Z +eWNLZEhBeU5RcGhLRk1uWjNKdmMyOXlKd3BUSjI0bkNuUndNallLWVNoVEoyRnN8ZEc5allXSmxl +bUVuQ2xNbmJpY0tkSEF5TndwaEtGTW5kR2x3YnljS1V5ZHVKd3AwY0RJNENtRW9VeWRrWlhOMGFX +NXZKd3BUSjNRbnxDblJ3TWprS1lTaFRKMkZ1WTJodlEyRnphV3hzWVNjS1V5ZHVKd3AwY0RNd0Nt +RW9VeWRqYjJ4dmNpY0tVeWR1SndwMGNETXhDbUVvfFV5ZGpiMnh2Y21sdWRHVnlhVzl5SndwVEoy +NG5DblJ3TXpJS1lTaFRKMk52Ykc5eWFXNTBaWEpwYjNJeUp3cFRKMjRuQ25Sd016TUt8WVNoVEoy +OXdZV05wWkdGa0p3cFRKMjRuQ25Sd016UUtZU2hUSjNKbFpHOXVaR1Z2Y3ljS1V5ZHNKd3AwY0RN +MUNtRW9VeWRtYjNKdHxZU2NLVXlkMEp3cDBjRE0yQ21Fb1V5ZGhibU5vYnljS1V5ZHVKd3AwY0RN +M0NtRW9VeWQyZFdWc2J5Y0tVeWR1SndwMGNETTRDbUVvfFV5ZGtaWE5qZFdWc1ozVmxKd3BUSjI0 +bkNuUndNemtLWVhOVEoyTnZiRzl5YVc1MFpYSnBiM0luQ25BME1BcE1OREk0TkRVM05qRXh8Tmt3 +S2MxTW5aR1Z6WTNWbGJHZDFaU2NLY0RReENrazJDbk5USjNOcFRXOTJhV0pzWlNjS2NEUXlDa2t3 +TVFwelV5ZG5jbTl6YjNJbnxDbkEwTXdwSk13cHpVeWR3Ym1jbkNuQTBOQXBUSjF4NE9EbFFUa2Rj +Y2x4dVhIZ3hZVnh1WEhnd01GeDRNREJjZURBd1hISkpTRVJTfFhIZ3dNRng0TURCY2VEQXdJRng0 +TURCY2VEQXdYSGd3TUNCY2VEQTRYSGd3Tmx4NE1EQmNlREF3WEhnd01ITjZlbHg0WmpSY2VEQXd8 +WEhnd01GeDRNREJjZURBMGMwSkpWRng0TURoY2VEQTRYSGd3T0Z4NE1EaDhYSGd3T0dSY2VEZzRY +SGd3TUZ4NE1EQmNlREF3WEhSd3xTRmx6WEhnd01GeDRNREJjZURCbFhIaGpORng0TURCY2VEQXdY +SGd3WlZ4NFl6UmNlREF4WEhnNU5TdGNlREJsWEhneFlseDRNREJjfGVEQXdYSGd3TWx4NE1URkpS +RUZVV0Z4NE9EVmNlR1ZrWEhnNU5FOUxWRkZjZURGalhIZzRObHg0T1daY2VHUm1YSGhpWkRkY2VH +Wmh8WEhoaE4xeDRaRFJjZURBM1hIZzVNRnhjWEhoa05qSmNlRGc0UGtKY2VERm1JbHg0WVRod1hI +aGhNRzAxWEhnNFpVTXVJbHg0WlRSQ3xYSGc1TUVwY2VHTXpKRng0T1RKY2VEaGlYSGc0T0doY2VH +TmpYSGhoWlZ4NFpUWkVYSGc0TlVoY2VEa3dVQzFhWEhoaU5YRmNlREV6fFhIZ3dOVkZRZDF4NE1H +TkJLV0pjZUdFMFhIaGlNVVpjZUdVM1hIaGtaVng0WWpkY2VEZzFSVng0T1RralhIaGpaVng0T0Rn +NUxWeDR8WlRaWlhIZzVaVng0WmpNN1hIaGxabHg0Wm1Jc1hIaGpaVGxjZUdRd1hIaGhORWxjZURr +elhIZ3dObU5jZUdJMVhIZ3dZMXg0WmpWY3xlR1kwWEhobU5IaGNlRGcxWWlrck9GeDRPR05jZUdZ +eFhIaGlZVng0WlRKY2VHVTRYSGhsTWtnbU0xeDRZbVp0WEhnd01seDRNV1JjfGVHRTVYSGhrTkRG +Y2VEa3pNMXg0WmpWY2VHVTNYSGc1WVZ4NFpEQmNlREUwSWxoY2VEaGxYSGc1Tmx4NE1XVmNlR1Jq +WEhneE9WeDR8TVdOY1hGeDRaamhjZUdFM1hIZ3dNbHg0WVRkY2VHTm1YSGc1WTI5Y2VEaGlYSGhp +WTNoY2VHRTJYSGhrWVZ4NE9XVmNlR0V3WEhnNHxZelJjZURnNVhIZ3dPRng0WWpSY2VHSm1YSGhs +TlZGY2VHTmxYSGhtTjF4NFltWnZYSGhpT1Z4NE1EQkFYSGhsTWx4Y1hIaGxZVGhjfGVEaGxYSGd4 +TTJ3d1hIaGlOaUJjZUdVNVBseDRPR1ZjZURneVhIaG1ObHg0WkRaY2VHUTJaMXg0WW1WY2VHVm1W +MXg0WWpaTVhIZ3d8TUZ4NFlUQmNlR0V6WEhoaU0xeDRZV0pjZUdSaVhIaGlNRng0WkdWYVpseDRN +RFZ6WEhnNE5seDRZelpqWEhobE1pQmNlRGszWEhoanxaRng0WW1WWVdWeDRZV0VpWEhnNU1FaGNl +R0UyWEhobE4wQmNlR0prWEhnNE1uZHhiRng0WlRGY2VHRmxYSGhrWkc1NFhIaGlaRng0fFlXWmNl +R0ZtWEhoaU9GeDRPV1Z3UjF4NFlqSnJYSGhrT0Z4NFkyTk9YSGhrTkNNdVhIaG1NVng0WkdWY2VE +aGpYSGhrTVVoT01IeGN8ZUdJMVhIaG1abHg0WkRWcVhIZzRNVng0WTJWY2VHWTBYSGd4WVROSlJU +TkNYSGhpTUZ4NE1UQmNlREV4WEc1Q1ExeDRNRFZFWEhneHxPRng0WlROY2VHTmtPVTQwWEhnd1pW +eDRNV05jZUdGa1IxeDRaVEozT0M5Y2VEZ3hJRng0T1RZN1hIZzVObHg0TVdKY2VHVTRYSGhtfFls +QlZZRng0WW1KY2VERXdYSGc1TkZ4NE9XUkdYSGc1TlZ4NE1ETmNlREU0WEhobFkyeGNlR0U0WEhn +d01FQkRYSGd3TlNSY2VERTF8WEhoaVpGeDRaV0ZjZURGaWZESmNlREE0WEhoaU1WeDRPVFZjZURC +aVhIZzRPRng0WlRKY2VHUXdYSGhqWXlsSVhIZ3hObnBjZURreHxYSGhqWkVkdVhIaG1ZMXg0TVRC +Y2VHVXpYSGhqT0NaclhIaGhOMXg0T0RGY2VHTXdYSGhoWkZ4NFlqaDNiMXg0TUdOY2VHWTJYSGcz +fFpseDRaalJjZURFd1hIZzVOMXg0WmpWY2VHSXpYSGhqT0Z4NE9HSmNlRGxrWEhoaU1GeDRaV05G +WEhnNE5WeDRPVEZNWEhoaE5seDR8WWpSY2VHTmxhVXNrWEhoa00wRmNlR1prWEhobE5YcGNlREJp +WEhnd05GeDRNVFk3WEhoaE16ZGNlREEzWEhoaFpWeDRZbU5aWEhneHxOVmhQVENKY2VEazFYSGhp +WlZ4NE9EQmNlR0k0VkZOY2VHRTFOR3N3WEhneE5seDRZbUpjZURBMFhIaGlPVXhtWEhnNVlWeDRa +alUrfFhIaGhNbHg0T1dGY2VHTmlPMXg0WkROY2VHRTNYSGc0TUZ4NFpHTmNlREEyWEhoaFpGOWNl +RGd3ZTF4NFlqRmNlREE0WEhneE5tZGN8ZURCaVhIaGpabHg0WmpOY2VHWTVmRng0WWpSUmJrMWNl +REF5WENkY2VHTm1kbHg0TVdaY2VHWXdYSGhrWXloY2VHRmpYSGhrWVZ4NHxPRGxjZURFMlRUWWhY +SGd4TkZ4NE9UUmNlR1kyWEhoaU5UeGNlR05sWEhobVlseDRabVZTTFZ4NE9UbGNlR0ptWEhoaE9I +cGNkRng0fFptWmNlR00yWEhobU5WeDRPVFpjZURCbVhIaGhNbHg0WkRWY2VEQm1SbHg0WkRJa1ps +eDRZVE43WEhoallWeDRaR1ltWEhnNE5seDR8T0RaY2VEZzJYSGhpWlZ4NFpEWlRXbHg0WWpkQWFX +WmNlR1UyWEhobE9WeDRaR1ZjZUdJMlhIaG1OaXh3WEhoak9GeDRaREJjZURrenxYSGhqWVZ4NFpE +SmNlRGhsVzF4NFlqZGNlR0ZtWEhobU5YNWNlR1JsYkdsY2VEa3pKazFjZUdabEszNWNlREF3WEho +aVpWeDRaalJjfGVHVmpYSGd3T0Z4NE1XUTJYSGhsTldGY2VEQXdYSGd3TUZ4NE1EQmNlREF3U1VW +T1JGeDRZV1ZDWUZ4NE9ESW5DbkEwTlFwellpND18IzQyODYyMTI2OTEjNDI4NDU3NjExNiNGYWxz +ZSNLR2xEYjJSbExsRlVMbFJoWWxScGNHOXpDa1pzWldOb1lRcHdNUW9vWkhBeUNsTW5iM0JoWTJs +a1lXUW5DbkF6Q2tZeENuTlRKMlp2fGNtMWhKd3B3TkFwVEoyRW5Dbk5USjJOdmJHOXlhVzUwWlhK +cGIzSXlKd3B3TlFwSkxURUtjMU1uWVc1amFHOURZWE5wYkd4aEp3cHd8TmdwSk16WUtjMU1uZEds +d2J5Y0tjRGNLU1RJS2MxTW5ZVzVqYUc4bkNuQTRDa2swQ25OVEoyTnZiRzl5Sndwd09RcE1OREk0 +T0RJMnxNamd6T1V3S2MxTW5ZVEZvT0NjS2NERXdDbE1uWkRKa05pY0tjREV4Q25OVEozWjFaV3h2 +Sndwd01USUtTVFVLYzFNbmNHOXphV05wfGIyNG5DbkF4TXdvb2FVTnZaR1V1VVZRdVZHRmlWR2x3 +YjNNS1VHOXphV05wYjI0S2NERTBDaWhrY0RFMUNsTW5ZV3gwYnljS2NERTJ8Q2treE5ncHpVeWR2 +Y21SbGJpY0tjREUzQ2trNUNuTm5PQXBKTVRJNENuTlRKMkZ1WjNWc2J5Y0tjREU0Q2trd0NuTlRK +M2tuQ2treHxOZ3B6VXlkNEp3cEpNQXB6WW5OVEoyUmxjM1JwYm04bkNuQXhPUXBUSjIwbkNuTlRK +M0psWkc5dVpHVnZjeWNLY0RJd0Nra3dNUXB6fFV5ZGhiSFJ2WTJGaVpYcGhKd3B3TWpFS1NUSXdD +bk5USjJ4cFZtRnljeWNLY0RJeUNpaHNjREl6Q2loVEozQnZjMmxqYVc5dUp3cFR8SjI4bkNuUndN +alFLWVNoVEoyRXhhRGduQ2xNbll5Y0tkSEF5TlFwaEtGTW5aM0p2YzI5eUp3cFRKMjRuQ25Sd01q +WUtZU2hUSjJGc3xkRzlqWVdKbGVtRW5DbE1uYmljS2RIQXlOd3BoS0ZNbmRHbHdieWNLVXlkdUp3 +cDBjREk0Q21Fb1V5ZGtaWE4wYVc1dkp3cFRKM1FufENuUndNamtLWVNoVEoyRnVZMmh2UTJGemFX +eHNZU2NLVXlkdUp3cDBjRE13Q21Fb1V5ZGpiMnh2Y2ljS1V5ZHVKd3AwY0RNeENtRW98VXlkamIy +eHZjbWx1ZEdWeWFXOXlKd3BUSjI0bkNuUndNeklLWVNoVEoyTnZiRzl5YVc1MFpYSnBiM0l5Sndw +VEoyNG5DblJ3TXpNS3xZU2hUSjI5d1lXTnBaR0ZrSndwVEoyNG5DblJ3TXpRS1lTaFRKM0psWkc5 +dVpHVnZjeWNLVXlkc0p3cDBjRE0xQ21Fb1V5ZG1iM0p0fFlTY0tVeWQwSndwMGNETTJDbUVvVXlk +aGJtTm9ieWNLVXlkdUp3cDBjRE0zQ21Fb1V5ZDJkV1ZzYnljS1V5ZHVKd3AwY0RNNENtRW98VXlk +a1pYTmpkV1ZzWjNWbEp3cFRKMjRuQ25Sd016a0tZWE5USjJOdmJHOXlhVzUwWlhKcGIzSW5DbkEw +TUFwTU5ESTRPREkyTWpnenxPVXdLYzFNblpHVnpZM1ZsYkdkMVpTY0tjRFF4Q2trMkNuTlRKM05w +VFc5MmFXSnNaU2NLY0RReUNra3dNUXB6VXlkbmNtOXpiM0lufENuQTBNd3BKTXdwellpND18IyMj +MTUjMzUjNDI5NDc2OTYzNSNpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBNkFBQUFPWUNBWUFBQURM +N0o1eEFBQUFBWE5TUjBJQXJzNGM2UUFBQUFSblFVMUJBQUN4and2OFlRVUFBQUFKY0VoWmN3QUFE +c01BQUE3REFjZHZxR1FBQVArbFNVUkJWSGhlN04ySnIzVmJWU1h3L1M5YmxWaFdGS0owQ1VRUmJD +cEtpWUJBdlFjODRCVVBlQTJoc3d0MnFQSFpvWVhZbDJLREZpTGNDemsxZjNPZmNibVMrZ1BxM0l5 +VmJQWTVlNjAxNTVoampyRyszUDNkOTNIODZzcy9kL21qMy9yazVTKy85S3VYdi83eWIxNys5Wi8r +L1BMN3YvYjg1VXUvL2RMbEZ6L3hNNWV2L2YzL3VseSs4NjNMTjcvKzFYMzI1VmMvYy9uVDMvdjA1 +Vi8rOFN0Ny9jTmYvK0h1OWV6UC8raVhMLy84ZDErNlhDNTNHK1B6SC8vcHk5LzgyVzlkL3U3UGYz +dmovUHUvL3YydStiZXYvZTk5SnY1Zi8rbHZ6UHJMeHZpTFAvNlZ5Ky85Nm9jdmYvSzdyK3o5Uzcv +ejhzemM3M3J6di9MU2Y5ODh2L1RKbjcxODdtTS9zVmd2My83bTVSLy81bzh1Zi9iN245dWNmL1Vu +djM3NTVQdmZjUG5qTDc2NGNXRVdSMjIvL1VzZnVQenRWNzY0ejc5ei80M0xWLy95OXk2Lzg4dlBi +VXdjZk9VUFByL3g5cHB4OSsvL2ZQbmk1NSs1ZlBFWG45MjRYL25EWDF5TXJ2dHYvc3ZsMjkvNlA1 +ZHYvZHMvYkg2WTViSG1uLzcyajdmZXIvN1ZxMXVuK0wvMXVmK3hHTVgrOUVkKy9QTDVGMzd5OHJX +dmZubjV0T2MzUC92K3pRR1g1NjkrNFNON3lmL3lCOSt5OGZHTkwzRjgvOTFmZVc3M1cvLzNmL0c3 +VzQ4YTRQbk8zZGN2My9yR1B5MCs2OFRhTllNSkIycVM4eHYvOG5mTDR4Lys1Z3VYTDd6eTh5Y1Bn +OGQzNjN4V2cyR2RHT3FIdzlvLytJMlBYYjcrejMrMU5lUEwybC83MUxzZjF1aS9ITmx2RGgvV3FW +YzhReTQ5ODUybURQVmFKN1phY0dQT09uVjVKcithZkJjajMzMm1tNjkvN1crV0svdmdVU010NGNj +Nm1raS96T05PTFB2Z3hoc040WUdHeEZXSC9ydi8rcWZmczJ1c3BlMW5mL28vWFQ3Njh6KzBmZEFi +ZS9WTHpiRGh6SGRhc1Y2K0w3ejh6dFVIM2RLMmZmS1lyei9yVHp6V24vVm4vVmwvMXAvMVovMVpm +L3I4RlB4NXZQS2hIOTNrUUZ1czJRcHkyZmkxZi9pekJjeDRBaVBjV2dVekxWRUlhTTdsT2ZBTUpy +YTdadG1QVUo4UkEvQXJ6LzNZQXJGZVBzUWpVSEdFcVJoakQ0Y3A2RE1mZmR0aVZQUUszT0V4aDRG +NDlsZ3ZqcmkvLyt2L2MvY2lIWUdLVlk5OEJpeWFiNCs4eUZ1RHptSGtVREFJZ2xBMVVVNXJ0bG5X +TWZHc0ZTZWlrVU44bkxqRXg1VVlHcUIyNDFNZmZ1dmlOeWNXakE0Y1BCcDZnRGY3bU92ajczM2Qx +a1d3Y3BoTG85UE05TVk2OFFod3pUNzljeENxZ2ZqRklFaEMrSTNQdkcveE01aDREaXYxaVlsL3ZP +SWQ1NFlEMWxwMW1WT3plZnFBV1Q2NUhjeXBQLzExbUJGOURtMTZVRWY2QVpkYXhmWFprTXQzNHBk +YkxVeGluWndPQkgvQVJJdjR4V2MwYXQ1bitPMVR2eHBoak1IZ3h3Y3padzJjNXNYVU54ZmV4WExK +clZhWThBcVAvZVpvRkNiMTY0WDRZdkpZUEtKdWMvSllSL042WWErN05iaURwLzZzUCt2UCtyUCty +RC9yei9wVG5QcXovbndxL2p5SXhRWS9yY2VreEEwUVlBS2tHQnVBa0V4d2F4RnNyL1dhakhqaUJV +eHNhOFZ4SjJnQ1FLZ2lDRWQ4UklxdElSdC9TUFkyaUVBTmpVSHNTOCsrNllGc2Ixa1FTNGo3MC8w +OGs4TTZQMm5MYjhDbVBqRVpWZUdHZXV3alJwZTVOQzdEWitTdmNDZGVUQWgvM2c0UnVIM2VUcWxm +UFFTR0YzdnNKY3BmZnZFZHk2OERoVGswd3lFbE40enlpR3VvRng2WXhYMytYYTlaOGVGY1kvZWdt +TmpxRlRQQzlNejhlUkRjTGEvcWxCOEgxc0tGQXdiVVowTERhUTVFZU1TRlIyNTdZaGdpakRtSlRp +N3JQVC9YM0M4R0J5RWVmTWF4SHVFSlp6U2hyd3h0WGh4RHpmTGhKQWF4Unh3eDFLRWUrNnlUVS8v +aGhKRlo0TEZlVE0rc1YxdjBxVS9xcERGYUUxZk40b2hyRFJ4cWtadEJ2VWx6MlJQOXc2WVdkYWxG +aisyVE83M0JrenV6NmF1OWNPdVRHdDNWQ2F0ZXcwSGZES292OXFpaC9xdy82OC82MDczK3JEL3J6 +L3F6L3F3L1BYc0svdHhmd1ZVazhYbkFZSklveGlaZ0FRSk1jNjFCaEhVdUFCRGdtYytBQVMrZU54 +c015VXptUEJjYktEOVp1eENJRElERkJrd3N1QlRJN0RDWUkyemlFY05mZGNORUZIQUVGM0c4OEFz +L3NpUVlTRmN3OHN4cmhvRjhPTXdSR2FKaEZ1OGM1eDJwRGhMeFlWUXJqQkVkYnZDQ1pDWVEzd1du +WHdFUVd3d2NSOFRxRUJPZUhJYldpR09JVHhUd3VQc3JiMWk5S2RFVGUzQURFMTQ5d3dGajR0UEZl +TERacCtueXd5NUg4SjVHdnF6aC9GVzdYc0VDYzk1OHFGbWR4dFkxMzJQUzVHWU9ocEhUZDIrNzBn +L3I0TUNYK3MySnJSL3luRDBjZzQ1QllGSWI0ZW83QStGQ2JXcG5PSDNiZms1Y2Rjb25sM25QWVlz +cDZVME1PWHlIWFgydUhEYjJNSmM5T0pVTEZybXNnVlZ2NU9NSjNPVmd0c1ljTGVKWlBHdkU4Qmsy +SEd6TXdZRmZuS3ZiYzRlT3VBNE4ybm54bVRmdTJ5SThpR2R0L1ZsLzBrajlXWC9Xbi9Wbi9WbC8x +cC8xNTFQeDUwR1lDclJRMElnMkF2UGRteDEzWkhtZTRBaFF0RUFCdndhWTlkWjVNNkdKTDMvZ3pi +dE9RV21LTjBEQXlMbmtUcUdJMHpqeEVLTkFROE9RSjJiRTdLLzRJekFFZUM1WERJb01Rd3h2cU15 +cEt3WWxHaisxTTBDYXJGN1l6M0czRjVFU3IvclVZQjB1SWhiTkVzZWhnVmhZNExWVy9ZUWhMMU1t +TjR3T0xjK1pSMzd6WWhqaXl5bTM2NFgzL1BEV1N1Qk1xelozT1Z3NHpZSHFUbXd3TUE1ZUNNQWEy +R0N3UnEzbklYYTNhK0RRVjNqRG1UY2ZNS3JSMEN0NzFLdlh1Tmo1RVNzdW1Fb01iOFAwMkR3c3VK +YkhaM0hWcng3WTVUTkhXL3FxWGdaaGRuUHcwb1FZY0toSmpLMTVjTUJxTHczcHNkcmtoUlV1bjZO +TEdoTFRYVHdhWHZOTmZKeUw2ZTY3d2FDdzBxSjQ4SnFqZXdlT25Jd0ZELytvMlRvWUdaSWUrRXZm +UE1NZHJ1VVJDd1k2d3BrY2ZxV0hKOFJVRzczWG4vV25OZlZuL1ZsLzFwLzFaLzFaZjlhZnNENEZm +eDZDSTlsR0JMb0lSUkRpalJnVlk4NTNHeVZGbUhVU0ErNjVOV3ZjK2F4NGpaQVlVWjRqU21FYVlW +NGNZb3BKQ0MrQXhUYmtScngxQ0NCUUlrQzhnUXo0N1NjcWY5VXJudUZPZklneVI2VGI3R21lL1RC +b21QMjRJTElNZU9IQWo5Z1JsQnF0VTc5TDdab0RyenU4YXZDV0MyNDgrcXQ1UWpMRS9NZzdmM0F4 +YWFKNUdIRkdyRGlCeFJ3K1B2RysxMi9ONlkzWW50c2ZVY0tsRHk3N2ljc1Y4Nmt4TmRnanZzK0d2 +VXlNSy8wS0x1dkVXMDdtNE1LOXRXcUVsWWpnd0NjQmV2c0ZtOFBGR210aHlYNjh5S0YzY0xsZ042 +Y3YrSE5sai9oNlRqUGU0bmdlN2RBaFBMakRpL3p1ZXFIUGNDU0dDeDcxNXc4WmNjVmpCcy9zZzVj +bVlJSEpHbS95YUFRR01lV2xIUWVJbkxRaGhubDd4WEZZT1dUZ3hMcys4TkFlU3BNYnIycjN6Qlh0 +ZmZiNXR6KzhzYlVXRi9Wbi9XbHYvVmwvMXAvMVovMVpmOWFmOVNjY2lYSEwvdHhmd1pVQWNZQVJP +eUEyZWU0blgwSkZUQm9vcWJVU0s4cDNCR2dzTW9pV29JakpjMjl6eEVPWStJZ2hVcUlRM3pQRmlR +V2N2SDVTWHFKbXlMTkdtcGpXRVkxMUN0MllRd2JNOEhuelFkQStHL1pZQ3hzU3JHVUNOU0diRUsy +VkU5azdDR3FHSnFpQndPV1NBOEVhNmcyQlJxZ1ZKa0lWaDBqZ0pSeC9kWTFrVFg1c1VERzlOYk5H +UFBHSmxnZ053b0RiWG5rZE9IajNIQS8ybTFjVDBSR0h2RDdEU0VCTTQvZkF4VkNqdFF4cERXSGpI +MTQxNnRIV1A3R3RzVjV2WU5NM3NYREdURGpUTTNqMEdnNDhuQWE5N0Y2OUU0Y3VZTFlYcHpTMDh5 +K2Z2ME12QnU0WUd6YzRsRlBmQ1ZkOC9mSmRqcGhLajlXZ2J2bnpCNGpheFpQWHhXVHk0QkEvTGhy +REJhN3BUczA0a2VmeG9STE5lOHNIbDJmdTRzdW43MnJXQzMyRXg1eDF0Q0VYVGhQUGV2SHhtVDdz +NytsUFhqMVNrMXkwRW9QU2VmMVpmOWFmOVdmOVdYL1duL1ZuL1ZsL1BpVi9IaDVvaGlJVUtiRGlG +RXM0SjRFam1nbG1vK1pJZ2xEQkVlQ1NVRUIzelVBYzROYUpMNDVHcjdnbVBtSUlCSGg1UTZJWXdE +azRpTUZRbkY5cFVJU21LVkRzeGZiSW9QQnB2cmN2NGhoeU1KNlk5cDh4ei8rQUdha2hJd0k5aDE5 +UHVIODRhR0FSVDJOOTMwTm9US0NCbW9RUGV6UG5tYld3ZU9ZL0d2Y3JDYWRZejkrNzM3YytWeUV4 +cjMvWnpONjhtVkRmWXArMURqam13Sk5jWW5zTFF1eWU0NDNZY29qSVQ5REU0VzBOem9sRGZQM0p3 +V1F0L2p5VEIwL2hsbGprOS8zRWZiL3h4VWpQOFFZTFhQZ3d4SVhObk5vZFZtcVNSeHg2b3lzR1VV +dDZUQWRpZVdZZGtkTU9jK0k1QnJYUFFTdS8yT21GZmlZZTNGdmJEQ1pXdS9nMG5QcG9Cajh3bXBm +RDRZQUxPZVMzeDYrT1JLZGkwZ3g5N09FOGVlMmhiODlkdUtCemVSbFBQRHB5bWRldmNDaW51TGh5 +WURyWS9EY0VlbUdONS9Wbi9lbVpQUFZuL1ZsLzFwLzFaLzFaZjlhZnhxMzdjLzhHbEFBa1dRS20w +V3VpQWU2dUVPUUNRMHdJVTVEbktRb3g3aG9ZZy9wcG1rQWwwWGdpOGxOM2hNd1FMZ0ExQWtCcm1F +NXhCS0poQnNFeEtLS3NSWlQvN3lDNWtJRVV6Mkw4NTk3eEE0dlJFTU16YStYSWdXTm9oR2ZJc0Vi +akNQRWM5eHRYVEtLekgyNTFhYXdHNG1SRk1xUmI0NDJRdko3aEFxZmUvUGlQckhIc3VUMUV4SlRX +eVAreGQ3OTIxK0RRSURhYzJXK2Z0MG5xMW1BOWNWamd3bWYxNE0zQkNaZVkrdUl5ajBlR3NVYnZJ +aEtIMVNuaTgzZms0UkJURExYZ1d5eHg5WlBKQ0c1N1BTTFBnV0FQNGVQRGdOc3pOZXA5ekJhRE9x +amtGbGNjUWpWd2pWdTE0VWxmNVlEYm5CeWUwd2FzZEVQMGV1ZTVkV3JERTZHcjNUQVBUNHd1dnA2 +S2FjNW5kWWxuUDh3K0cycnc2elgyZXdaZmRMV211eHJVZ1dHdlhzQ3AxaGpVdkxxdE5TK25tdUZs +Y3Bob2kwNzFHeiswS0kvbjlXZjlXWC9Xbi9EVW4vVm4vVmwvMXAvMXA5cU5XL2ZuL2plZ211aE9z +RFlxQWxDQ0RGaEJGYTI1TmlvWVNNMEVIQkRGQW9CY3p3aFd3M3kyZGcwNjM4VTNwMUhFR05PSm5R +UEFQK09zUUVOeDFucWJZNjFjaWtDQ2dXejRQU2VhWjM3cSs1WXdBNzZkR3pINEROK2FjRWkwVHpQ +TnVSUHlEb0s4dmdVd0J3ZmhxZ01IdUZHTGVRMHg1N0FoYXV1UnJnYUhuN2NoYW1WQXRSbkU2VitF +aWtDWTJIZDdEZnpoU1MzcTlOWkFUTDJ4aG5ERXg5a2FidUxrc3p2Qk1nVmhxUWsyZS9DRlkzSGgw +a2ZET2h6cGk3aSs0MXRkVE1LY01La2J4M2dVSzJKM2dGcmpTajZZNUtJcFhPR1VCcndoU2ovd3Vn +ZkE5RU1NZUdEREsveHFwajhZUElOQmpiQkYzUENrRi9DbzB4MTJBd1pyNkJjdmFyUEdlbi9JbUlQ +Rlo4OGRRRnV6dlZPREF6THplcEs0Y29ZVE5hc0RObVpVai9WaWlRR245YmlGeDFweGFBY09HUERv +NEhFWWl5Y3VmbkJWZjlhZk9OS1grclArRkwvK3JEL3J6L29UdnVqQlovanF6L3JUK2x2dzU4TVBv +SDQ2alFnc1JwWTVCQnVDQzVTbUFDS3B0eXArdWtXczVBeEUzTXhObUlTd1RaZ0dhUUtCZSs3M3Zn +bFAwOFFWSXptQkozWUZHdkl3a0FMOGxPMHloMUJEa1hBb2lJays5TFAvNWNHODR0a0h0K2FFWUNT +cWxlblY1cTd4bVNNMnZ6K09MUFdLUzNqaWFGZ0VwT25JMUR3Y25vZkszWkx2RFJuY1l2czFCYzB5 +Qno5RHlvc1BmdzN1WHdMRG1TR21QWGpEcVhsaUlCd0daQkI0bUZjc3hzOWFmY0RYaW05d2ltT3Qy +TmJLU2F5d2JWK21Ucnc0VEFoTS85VHBWeHZVcXY4cjVybUxyMGRpNGc2WHZzTm11T05RejVQTFBq +eUdFM05pMjR1M3hUQkR6ZkxaZDRyOFhNK2c2c08zV1BoVnQ4TlZ2aGpBSVU1RGFuc2Nsdzd3SUo5 +bjdqZ1VWNjF5V2krL21uQndIcVQzKzV3M3JNL2hlT3JxZm12Q3MzajV3d0oydUdEZnZLTnJPZWpl +Mm9kRGEzb0RpenI5QVlWRFBkQlh2cUFqdVQyclArdlArclArckQvcnovcXovcXcvNjgvSGNXL2Ru +d2Z4TW93NzBKSVFqVVlUdldmSVFRQkRLTWdjc2hBamFJUkx5RUFBSTRGRXJoVEFwQkVOQTNveklx +NUdBaTQra3EzeGhrZ2VnMEQ4U2dJVEtJN2c3RDBQZy9OMzJTTmNBdm53ei8zWFhXZUlKVDlzNnBJ +bmcrREZSSlo1dUw0N3pyY1dERzRmbzZveGRjcHBIbjZOZ0FsMkRRc20zNjJWWDcwNDBUd2k4RHZ5 +OHNKajNtR1ZldkVLaTE2SXpUemgzeHBtODF4c01ZaFNUUEUxRzgvMjRrTWNhNjJ4Mzd6RFRUNzFH +ekdvZFM3ZnM0OEo5Qk1tNXBBREYzcUcyeHlpY2xvbjUrS2FmT1p3dEdNT0FpWmdVSmU2elZ1SEx6 +cFRsKzlFVHJoTW9RNXJmZlpjTCtRUWl5NnNweTM0dHM3aHd4MVBUR1JlWEh2MFJrNWFadzd4ZkJl +Zm50VWozOG5MM1hMRkczcXJicjJIZ3gvd0FxT1lNREgxN3IwZVNERzB1dURFalhrOW9FMzgyU05H +ZkVXNytaVUljZkZkZjlhZjlXZjlXWC9Xbi9Wbi9WbC8xcC91ZUhvSy9qd0FSNWlHK0lsY3NTRmVZ +b1d2UVVkd0NqSEhHQWhTSklIYnJ5bldDd3k4NHJ5RlVLRC8zeUpnaklpQmNiMDlRWWFjQ25GSE1M +RXhLT0h1bU9iNlNUMG1jWENZWDlNUGtjUXJMdUxOUGYrdTF6emtRNHJhTk13YUI4ZzU3amNYSENG +ZkxZUW1wa0ZjY25xT1BEVWh6V0VnSjc3Y1BWTS9ib2hCL1pwa2o3MWkrSjE0ZjkwdHB1YnQvNG54 +N0hHNHFlWGxEN3g1aFdEQTdBRGF1Qk5QWFJxcHFlSFpjL1dFczRnRG4zcWpmL2FyVzMzVzZFMU1T +QWlFWVlpbFQ0a05yN2M0NmxTSDNzc2psanpXRWI0NGVxVS9CSWczKzlSTUcvVENCTGplT01NVlRu +Q2V3eUtDZDNlZzZadTk2bFdMbWwxNG8wVzVZWTBHWVNCNHRjT252OWFyRlI3cjdLR0xjS0RYMXV0 +ZnpNZEU5bGx6SHU3blcwNXY1OXc5VnpOTXNNRG5VaVBPUEZjVDdQYjZEby92TWFqOThPRFNYYTEw +YnkwdStJZ082QlZtMk9yUCtsT3MrclArTk9yUCtyUCtyRC9yei9vVG5sdjM1L0ZnbUNrQW1SWXIy +blBQQkJBY0tBazFYZ0xGdVNKQTZ3RzFCcEhNeFlCRWkxRGd4VUdPb3IyaDhOWkVET3VCQmRwbmQz +c0l4RkNnUE43V01EMWM3c1JpMkNPL2l5aTlJVUtvZ2ZROVFJWmtoYXN0d3hxQ2dZa0oxRUZNcDBu +bk5uZzM1OVNCTE0xWGo1aWFnUHdjUmhHSHVqMHpQQmNUNmQ1d3FUbjErcXRvTVdFMjcxY1cxR2pn +VjMwRTZ3N0RObjFFSmI4Y2pPMU8yQzUxRWJyWThHbXd0ZnExQit2TTQxT3RoUExaNTkrKytBemN3 +Q1kvUERqUkkyc1ppeEQxTHdlWld2WExlbGdaMURvWExkbFBOOHhMVC9qTVBIUGkyajc5czg1OERF +cUR0R1l0QThLcVR6RGs0TWwrZklRRHZNTGxVZ09zaGowMFlRL000UVF1ZTNDQ0J6bnhCUE5pR3I3 +Rmd0Y2Vtc1NCdTVoNjdHSjR2ZkRjWHZ3YjhPa0JzOHREUy9waURSeHFoMXNzZHpoZzlLL0Y2VGVl +WFdxcFArdlArclArckQvcnovcXovcXcvNjgrbjRzOER5VVRBWElJbnNNVisyaGZNWUJMUEFVS1lJ +anhqRmlMVVpBVXAzaHNGaldKQTYxLzl3dmtmUndPcFdIc0l3cHNSNEpFcE5nSVViUzFNTVJNTTVo +a01YdkhFVjRBUjRXcU1QZjRqYlhtTnhMS1BVV042UTYzSThDeHhEZUpZbzQ3dzdNRURjc1hXYkNS +Ym8vNDBWcFBOTVowM0lURXdYUDdENVBDaEdlWVpGT2RxWUZCdmJKSWZ2L2duUG0rVzFHM2d4elA3 +ckpWTFBMV3FIUjQ5aklqVmhqY2lWNlBQOWhFSFRLZUk3M2V0UE9LcTAxM05zSXVuYjJLb0gwOXE5 +VnpmWWRveEp2TXNoNGZZZU5IZkdCU2ZCSy8vdUxlWElUM1hRL1c0NDAxT2UrRnh4Ymo0Z2kvNzFh +VWY1bk1ZT2R4UDdaei9vYjM2SERqMm1NY1YvVEtQbmxucjgycDc2bFNIdlo3RGFyMmE0UkJELytq +VW9ZRWpCNUs0OW04OXc2a0RBTFkxNk5SajJHK2RPYnFFUVM3UEhFNHVmYUU1Y2ZmQXJ6L3J6L3F6 +L3F3LzY4LzZjOWZWbi9Wbi9mazAvSGtBN3EvT1RRb09pQ1lRaFlXU0lwZnBFTE1GeldiQ0FCRGhH +cVh4eUVPUzlScU1LS1lsRk0rTm1CbFl2M1pBakVoQk1xQStpOE5Vc0JoSUVaL0lGZTN6dm4zYUpw +eUhCMXhpdyt0WElwSlBrUnBremwvN0l6dEQzSWpZYzJ0MmpLRE9jYjhjRUJ4UncyVXQ3SExpeTZW +R1dOY0VVOXMyZUVSbkRrYjdjYXdtTllydlA5b1dqeWo5dWdLVGlvMDdEYlJISDd3eEVOZmJIM2h4 +NzdzNFRPRE5oTjZwVjQvZ2dnZi9lbWlkU3k2NDNYR3Q3eWRIOTF1N1hqQVgvdUZRdDdzK0U2UDZ4 +SFY1RG1QNkNCdERlZHNCQzc3VkxhOTF5K2ZWcFBpR1RSNTc5dytBbVdOTWUzUDR5K2taM3NXaE5m +WHBMMjdnVWJ2K2lzSE1jSWxMSDdpU3ozUGZDVjg4dGN2RE9QTGc4M3NOS2c5TXZzT0tiN2prVWc4 +czhPbXZPVHhidS9NVEo3M0hGZjU4aHNWbisrRVZ6MXFZN0Z2dERINGFwMjNjZWw1LzFwLzFaLzFa +ZjlhZmFxcy82OC82cy81OEt2N2NIMENCTUlsZ1lIMUdRQm9mQWhYS0NNRDRMaWxSRXg0d2lqWG5K +MmhyTmNwK0JVVDhHdVNTM0ZzUkFsM2dWL0pDQkRNQmJTQ2JpT3dqV3JFZkRvOFJoNEpodHNaZWI1 +ODAzL0RkbkovaVBTZkNESEZYVElOYkRRNlVjOXhmNzNkTEdJNllRZTFwRmhOcXNFdGVkY2pqRU1z +Ym9oaEs3WDRsQU0rTGEvaVVTMXgxK0JmQ0dGZ2NnMGpVeWt6K2YzUFVTelRlaU1pQlUzRjhoc1cr +Q0IwdUdEUVpwdDB6elliRDNZRXFMaXo0eGp0dVlQRTJSUC9GZzB1OTNtNFFvK2NSczNwZ0VGLyt2 +REZ4aHh1WGpBV2pXdWlFYUpsWWZ0Z2U1bWUvT2NLRjJ6T2M2cXM3RSs1YnFGbmpPMDdWUVord1dV +OC82bVpjZmRKajYvQmhqN1hxWVFqNDVUSFBPTmFyUlg1MytSZ3dmVkNyTmRFbm50VnBEYjlzSHdh +UE5idnVlaUNKSTQ4WXNPV1FnOVdob1g1MWllR1pHUHptTFNLc3VCYTcvcXcvNjgvNnMvNnNQK3ZQ +K3JQK3JEK2ZrajhQUHprVG9RVUsxblRFQytBWjR4R3N2eDZPT1FWV3JLU0Nhb2prMWpLRXBJU3NP +SVJvQ2hFb1NreGlOS2NBY1JRR2pNK0VpQVNDRWRPUUEwYm1GSWVwQ0h5Yk95Tk5VNVNZM2hCNXEy +REE1Sm1tRVliNk1tSUVkU0VsNWwxQkdVTzA1OHkxQnB3Y1JHQ2ZHdUhWRUhpSVcyeDFhUWpPNEZL +YmVURWl6QmpVZC9qOHVvTFByNDRSR1Z2RDFNa29qSVFIdktqSk13ZWVPUGdnQkJ3VGtIN1lLeCsr +OUVYdCtBdDJuL0htcm8vaXFBMGU5Uk1wQTZ0REwyRWxLSjlqVUovVmJiMWVNUlBPWU1SWER1QVYv +bkNrSmhkODRzcEY3SHNnRDVjR0k4RkJCM1FvcjczcXNjNWV1WERoV2cwT0JwY1k2bEEzUGxmZ2c5 +T3dSNjNlZ0ZrckhnenVjdHBuYlhoMDExc0RGdjdRRi9uVUF3dDhhbVp3V1BRQk43eWg1L2JqR045 +NlpoMStQSVBCR3BqVmx6ZU50S08zL2hEUk4veFpYMy9Xbi9Wbi9WbC8xcC8xWi8xWmY5YWZUOG1m +Qi9PNFRFcHN3bUtCaVU2emtJVjh3Z0RJR3ZPQW1FOEJRQ0tJRWNWRWxIbGlJelF4RUNBTzRiZ0R0 +eVFQR0xFVnFXQ0M5ZGxRS01GRjdBaFhoSDJHUW1IZXZTTWEvNmU0aURUZ1pFRGtpNG5rREd2RWxS +ZHU5WDd2OEJPN1h4T0FONGNEZ3IzdFlBcWZZMjY4NE1JekF6L3kyVU1nTG9MUUxESDlXb0s0REdw +TnNGbXpoOHJ3Z0RkMU9TRFZRTVJ5K0t4bitIUlpnM2VDaEZrdFlvWlRhMTNxeEo5NXo5TnYzS2do +WE9VUWdCVlBlaHVlNEZTamVFUU9HOEc2SjYvMWVvOGpiM2ZFd1ptNERLcG5jTU5yaUFQWEdtQU9C +SnFqRGQ5aEZKdXB4SFg0dzBtdkRrUGFzU2VIdmh4cU1HZ0VYbjhvV01jWWVGMnpERGI2TUw4OHpD +RzJ4cGs5NmxHL25xbEo3VGxBMU9IQWtSK2U4THd4Wnk5TTY0ZkJJRWNPVC9OcWtDTzFlOXRrM241 +OWNDQ29RMjVyNnMvNnMvNnNQK3ZQK3JQK3JEL3J6L3BURGpVWXQrN1BRNkVldUFzdU1BS1FHNE1a +bXU4NVFDNW1RUXBDQUFkR2tmWnJpT2RNYWo5Q1hJQUFiOTJhYkVTaFlDUzVnQVNNQ0poQkRnTm9Q +emxyYWdRRW16aUlFcGVKQ1plZ1AvRGYvdk91ODVaR1BMbVI3OWNleE01QUdER3JTenc1Q2VsaFRC +M2lxRVY4QkJJeDBUQWZQSDdDSjRqbFlIaFJGN0hCaFh3aWhrbDhOZUJIODVuenVYZjh3R0t6MzN5 +d2FZeW1teU5tM0JneERzR0lLeWVqNklQR3d3U2JXTjdTNEhjUDIrSEp2TS82WWUrS2Nlb1gwM080 +WGJTUVEvczhBTyszTDBUTTBMZ1NUeTc3Y0k4RCt0RGI1TVo3Uk90Z3hnZVR3YVFQL3krRDZwODhq +TW1nYWxNN0hZamh1Yy9xVm9mdlRBS0QvUENyQjNkcUZBTk9lZVNWaXptczB3YzU0WXRKOGUyN2VI +b2tQMzE0N3JQWTZwTExwUjU0Y0NXR1BDNW05TjJsVGprOTgza3hUSTQ5Q0NhUHV2UUdIblhoaHNa +aTBQcXovcXcvNjA4WFBQVm4vVmwvMXAvMVovMzVGUHg1RUJYd1FIdGdzK1loZ0JBMHdaQkE4NEcw +UjdNSUE3ZzF6VHpURUdzUW95aWlWb0RZaElWUUZ3QkVMdzl3OXJtc0ZkOTZacEhEOE5mN3hHMk5Q +QXIzTElLT1FUV0Z3QmhVc1VoV3FCck1JVnY4RFBWNlE2Uld3b0xuUDR5cGd5QzlKVkRqSGlZalR2 +bEN2Q1lScEh6Qmh4ZEdKdzRjeXFGZWI3WHdHNE4rN04ydlhmUGlVR1BVNzFDQVdmM2llaTR1bzZn +WE43akZEUjdOeVpPbXk2OFd2K0tCYy9XcW5UQ1c4K0hDUHJuVUFYZjRVVnZpRW5rTTZqbU81TWV6 +NzR3aFhnNEdnMmpWaXE4SWxCNXk2RG13MU9yUVlRNmlYNE1PenppRFJYNmN5aHZoaWlPMmc4Qm5X +T0czenFGQTVDNjg2Q244NmIvbll0QTRmdWhQSFdLclFVOXhwQ1k5aHN0emNWZmYweit4WUtVUGZL +dERYT3ZFd3JjYytOSTdPTlZ0cmI2WTA5YzlURVkzWXNIaXUxalJrOTdtUUlCci9UYjlyRC9yei9x +ei9xdy82OC82cy82c1ArdlA5UC9XL1hrUWpZY0VnU3hBODVPNkJxOW9KemlEQW0vZVpra1JCRnpF +SzdEUHdHc0VjUU10bmdzWW9NUmxKTTAyRDJCK1FpY01EZlpXU0RNTStQeVQxa1FpdmlhS2dXUURM +dkVSN2M2Z1BtdXFacm9JVjQzd1o2aGgzOERNTS9FUTlIaG9zSHl3eWFFWm1vVmNYTW12R1V5bUh2 +RWlKcHhwaExqbXhWR2J2UVNKTjl5YmMzZlo2NisvWWNjTmJQNXEzVjU3TkJ3V1BkRzhqVGNDSUNi +ZjhhSVA2bVZRZkprbldMRVp5aDNQN2tTQkwvRnhzMEtkNTU2Sm9ZK0c1M0tLQjllSzhicFhqZkty +VjMrM3JqSERjbkhWQ29PdVNhY0djYU0xRjhQWVN3ZTRaVHlHdDE1TitCSURGczl4SUlabjRua2U4 +OUljZ3pqWTRNeWNQVERCVDJ0cXdSVURxaTA4SmJkNmNzR0tMM1ZhSjVaY2U2aGNEU3B2REErdmVm +VW5CKzNRa2pYaXFGVSt6MkcwM2h3TjBiNjcrbHoxWi8wcGZ2MVpmOWFmOWFlNDlXZjk2WFA5V1gv +ZXVqOFBUZFpNUHdrRER6Q2hBaHRoSVpCd2tJa0FJRndLc004ZUNaR2lDS1FRcGdRK0k1NUlyQmZE +OXpSU0FmWjZTMEdrOWx0cnZ6bkRXeEtHWlhoa3JabG5qUVlhaXR1M1dVTzgrNGQrNXZzM0pzeWFG +U3lJSk1BTXorUVVCeW5pbitQOFY4SVFCeXVpNGNPSjNHSVFINEU1cE5UaHdvbUR3RFBEZC9YSTd6 +bEJuNWp2OTVuLzgxNE5JUUkxRWg1aGlpMkg1bnZqNHJrQnI4OXFKQUR4MW1qVE4zRjloZ2xPZFRF +a1FZbGp2YytNaFNjNTdJSERQakZoMVV0NHJNR2Y0Yk84dE9DT2IvSHM5WGtGT0xqMU4zeDdMaTY5 +MElETElhOS9zSzJPcGtmaFNnM3F3Yk8rTHcrekg4YUlubmIwQWdaelRPSzVHTzR4cVB4cWw1TjJy +YVZsOWVGSEhlSXdJd3g2YXArNCtHRWFKbUs0TmVobzdYc05xaGFZY2MxRCtGUXpyTXdvcG5ocnlG +bm5tYnJzOWQzbHUxeXUrTTBmUk83aTRMaityRC90RXhQVytyUCtyRC9yei9xei9xdy82ODliOStj +QkFCRWdEdmlRYWdQeEVKYmdtZ1cwZVdDc1FSandRUHRPNENrQVdRRDZMcUg0aXBMRFo0YVFHN2th +YXk5d2lEUHYwTERXSUg3L1hMUG5qQzJ1ZVUwd0ZDT0hBMEp6bm4vWGF4YW4ySnF1QnVMMnhzWG5E +TS9FZ1ZIY2Jib3hqVGZVWVQyQlcwTTh2c05sTDJGcXNsK2ZVSXRuNWpYVzhCay9UQzQyTERqRXAx +cjhNOTA0MW1Cdmdzd2I0c1FzZUxUZklDYjFXVWU0MXNDbXdmYTRjR0dOZWhnTEJya0p4YnltdzZT +L2UrRE5aMyt0ajB1aWtrTnZjS3cyaDRtNlU3czloS2pmOXJqYmIxanZiVjRPRU92RlV5L0Q2WWNl +NFFwT05lUVFYczNNUG44NDBJTWhoalhFVER2VzJLY3UvZkRNL2hnVXArWnhBcTgzVmdZTVl0TXQv +dkVncmp4NmFTM1R3cWFHNURIbk1CSFhYbjNBQytPTC9mZ1BHL0hXb0pPTFYyRElBUmtPNEJSVFBm +YlNqMXdPQmR6cnRUK0k1TlF2KytyUCtyUCtyRC9GcnovcnovcXovcXcvNjgrbjRzK0RhRTBBc1Fr +R21Ba05STFk3SW1JdURkRndheVRLMnlSQUNFb2hDaWNTeFFDcWNJMGxKUHVBQVFveDhoRjBUQ2My +TWovMTRiY3VIb05nUHZMT0g5eGN4R2tlS2VJUWtHSzltU0EyemZQUFZJdXRHVGxBckhVUXdaTWh0 +eHJOK1ZlN3hGUm5CcEx3b2k0aUZOdGF1RFJVWHRnUkd0TVFsV1lnSGxlNFlTVHI4V3lQSEJyclVH +Rk05YWlSY0F3eDhLVUdjL0lhOEdxNjV4R3BQWERLNDRKQlhQRzJIOE1uN0dMQ0F6OGM1dkFKbTNu +OCt5Nm1Bd05QNGNLY1BHTEpHL0VtUDU3MUFSY09ISnliVjRPMSs2Ym0rallIZm5xQU9lYnpIM0F6 +Qmx6MnhLQjR0Z1puYS9LNVlJVVRsdnloSVM3RDRBVW1YS3YveEhYK0NnczltdlBNdlBwb1VJN3dv +b2RpTXBJOWN2R0h1UHNIeDlRRnZ6b2RPTzdpMElpNDY1K3JHWE40NGdzWFlxb3hCb1ZabmZJNVpQ +UU9ML0xSYVhwV2Y5YWY1dXZQK3JQK3JEL3J6L3F6L3F3L240by9Ed0tOd0d5V1FPQVV5WERJUlhK +QTJDaW9JZ0MyWGlNaUV1c2lFdk9FcitrSWNXazRnY29wSHVEZUdzQWhyNStTSHh0VURQOVJNN0VT +bG5qK1N2OXM3dmxtQWhGd2VDdng0ak52M0dZZ0pSZ1FKajU4R2VLTHBVYjVIUkFycHV1SW9EUVBl +WENyWGJNSWlYQnc1ZzBXY1ptSEFUZjJtaE5iclhDSUQ1Ty9OdmRNcmVGZWZOd2I3c2xoamlEdHda +MzY0YkUzQjRKTGZXTDRURER5NGNzNmZZcVI0REdYK0w1SG9Ic1lEWDk2aFNjaU5Ed25HSHZzdFFZ +V3VmQXN2clVFeDZCcXc0MDdqSXpPUURoUkMxeWV1L1RPZ0kySjROeG4xM3JsRmN0K2NXRE5Pamx4 +eFNqbWNRKzNtbUthalQwNjBWKzU4YTl1cHRNbitNT2hQV3FRWDAzMGdYOTFKNithK0lHeHJKRlRY +UHZsWGx4alVIcjAyWDc0eFhTSnFTNWN1QnhPNm9qZitJYTIwOXY2cy82c1ArdlArclArckQvcnov +cXovbFRUVS9Ibi9nMG9VZ1hmelZOTVJFQThmaEluV2lRZ1F6S0pBVVlBWVdnWWc3cTdyUEhHSm05 +dDNJbUhZTDBaaUdFVkVTTWh4MC9Hbm4zeS9XL1lYMGxJODhUNHhQdGV2NlM2bU5jYkhZM1FTT3Mw +SFhuaWVPdGl6bk9GZTVibjhtVWdBUTVya0lJY2I3SE9jZi9RZExYQ3I4ays0MFNUY1lJekJsV2ov +SHNBRENZTnhJbjlhcExIdjJ4R0FCcG9uWmd3NGR2ZFdnTXVvb0JyYTU3OVl1WkE5TjI4QnVKVFAv +U0JVTlowZzA5ODY4enBKYXo2NWJONS9NQXJKeFBBWUsrN2ZUa3MxSUU3ZGVjUXNGZlA1TUFuelJB +WmZBNGhCeFVOaUtlVzVYSjRwU1A1SFdZUjRQWjRCQStiZXNWanhEeWpHWEdKMlhOODR3QWV3eG84 +NHhVbnNNdHJqYjI0bG9PMjlHQTFjbjNUSTViTGZweEVvM0xaWTA3UHhaUkhMM0I4SHVMM1c3ZnZq +R1cvMnRSbEhnWVk1WEVRMEJLTWVHVlFuMCtEWHBaTHVNVGhPVGx6Y05TZjlXZjlXWC9Xbi9Wbi9W +bC8xcC8xcDd4UHhaOEhZZnFQaFRWR3N6MWtNcUQ4N2pmUWlFRjRSS0VJU1h3RzJuTk5qaWdBMGxS +N0NZQ2hYcDJmbElsRUlRZ0NSbTRDaXBHSTNEUC9SN3NFNjduQzVCRkRMQ1Q3NTUydGdkbEFpSHdh +YjUrM1NTRWVCckZoMHlSN2lNbEFGakhCc0wrRFA3Z3k1eWQzZFdxQSt1QjNDR2lDZmZLcFdVTVky +NXc4dUNNS3VDTjJkM21JVjAzNEVROEhIMy92NnphdlExSWVBMGNSbXJzbUc1b0tENlBJU3lENm9F +NjQ1SEhoU1lQdFY1dDgxbmxHTlBpU1N3MXl4UnhFSXJaMUVZKzFubG1QUTN2Z2tWdGVheG5JMENl +Lzl4K0RpaWUvd2FEaFJKL2hkK0dEMkhHOS9NMThEakU4dXdnNG5LWW5lTFp2LytBWUhQTEpENnY2 +OGVhNUE4YmVIS0xxd0lzN0xxSmp0ZGtuWG5SblRvL2s4M2w5TVhvL01jL2hNTS8wWFd3OGlnY3ZM +RG04eE9RcnVscUREaWI3eE4rNjVqbHVjQ2FQUHpEcENUN2MxWi8xWi8xWmY5SmovVmwvd2xkLzFw +LzFaLzNwK2EzNzgvQzJoaWtRcjVGRXA5a0NFM2RFS3poQ2JRSkNVeVVPRU1RZzJGNEFGZWF2ZE8z +eGF4RE1ZNS85eURQMzJldi8xeEdnM2pwWUJ3K1FES2daaG4xKzdjQWFCTUhyKzlta2FkTWNJUEQ3 +N3U1Z0VSTSttSmdWSm5PYWtRRWJzY0h2QUZDTHBwL1haZm1JYUdCV2srYXVHU2FYbU1FTk13NnNY +Wk4vKzV1N1JueDUxZStuZjNQd2lzT2s2aUJ3alpOL2hUeVhQZUpwbkQ3QVpKODFoRUFrOHFzQmZ1 +dDlOazgwdm91UFg0TEFwYys0TU9SWFQvb3BCME81NENZZ2c4QnpTT0ZPTGx6b2Z3NWxudzJ4SEpE +Mk16QU05aG5pQkwrOGNvcjNJUFpaYnk1dm43d1JzMFlPQnNPUk5XcWtIVG93SENUVzRjSjhOS2dQ +MXVJU2R6alJnejM4WnE5NWwzMzJ3eWsvellqcHdoVStySWZEZXZqVUFhTzkrdXVOanIzeTZxT2E5 +QWhQK0lKRFBkYnZvVEI4NFplbjNLM0hzWXRHYUVrc2UrdlAraE8vOVdmOWFWLzlXWC9Xbi9Wbi9W +bC9XbnZyL3R4L0JaY3BrTGxnSjRFaUFRWU1BRUVVajNoTk5vOWM2d0d6TG1Ba0JSNFptcTVwQ1BW +VE5IQ0t0bDQrZWNWRUlETjRnMk9kOVg1TkFSbmUxTmhIeUdMQjQwM0xDKy81NGMxcEtOQWVKTnBq +bmlBSUU4RUVZUzFqUnpDR2VCR3h0MDRhc0dQTVpZZ0xuM3F0Z3cxMnhrRW0wU0RTVC9UZVhPRUNW +b0xDZ3hvWlRHNWNXQ00yZnNUMTNTRmxMY09velR5UldDK3ZmSExoZ1dnaVBtOHk4SzhQQkFhVDJz +T3R5MXVuOU5WNlF0RkhzYkltQndQK1lCS2ZpT1hhZzJxRUphYTluc2NBNGpHOS9UbEl4WkZUTGZv +c25sckVjVERwQ2Y0ZCtwNkxxZS9teFJEWHZzMDlQR3h0RTkvM1BmUUd0N3o0UExWeEdqdUhrRmo2 +RFNNdHdxdEgrbUdQdk12ZjRNS3B1LzF5VzIvZlBoc0Q2WWw2K0VPZlltS2FnWWZ4dlQyemgvbkYx +UU1heEJrZEpKODYxQ2dYVEdMUlZtTEFicTJMQi9naWgzMzlXWC9Xbi9Wbi9WbC8xcC8xWi8xWmY4 +TDRWUHg1RUlJSllKRGpqbUFFU0d4ZWdZSWhBa0JKRUN3SkFXbzA4R21XU3hLQ2k2bThPUUFlT0RF +UjVDNldJYjUxRVppM0xyQm9pa2I2SFhsN3JZZlhXeUI0REdRU21IVUlVYVRDNFZHb3Bva0ZFenda +UHN1SFRMOXpmeHBoeHRXZ2pHUy9HczFwbFB6Mk9JQUlYME9aTnlKV3U5eUU1Sy9FWVUxTk1PSlNn +MkZQWHJHWVhMTU5mYkJXSCt5Vlg0UEZsVU10dUpkRHZXSVJvUDA0eFNPc2ZsWERYcnpMUlNqeUU1 +QjhEaHp4MUxRSDhzVEFGWjVQRHU3WFZPSjVCcGRjMXUrQlBRS3luNkN0SlhKdk44STM3dXlSMDFz +VitPVVJEMDh3TVZBT0pmRndTZ3YyNkY5MHBoZWU1OUFWeHdoWDR2aURSRDl3QXBmNDRvbFB6N2l4 +eng0MXFJV3A4U2kzSHNPdE5qbXQwVU82c2NjYThlVXgzUEhzSHhYd1hEdzh3VzZ2Zko2ZC9KeUhp +Znl3NU9CdzhNQ0lFNzNTRXpubHdxY1k5V2Y5cVgvMVovMVpmOWFmNHRXZjlXZjlXWC9ldWo4UEV5 +N0FMQ1JhalRicHAyT2dFY3NFQ3BmTVpva1VaaStDTkFPaDlpRVNpUTlHSHdFeWxOaHlLSXJ3eEhF +bkFrQVJFU0g3bFFHbTFoamsrRjF5YXhYdC8wK0orT1F3Tk1MYm54WHFyQkdmSU9TelJrejNKWEp5 +WnNEbXVkcjkxYkM4aEphaFZ2VWdGdUhteFZJUEhMRGh4SE9jcVZlRDVOWjRCOXptWjlJUnY3Y25X +K3MwSm9ZaGFPc1lCbjZEb2F6SHYyYUphNTk4QkJ2UnV1UlZzd05OTCtTQ0RaY1JqbmsxUERhb05m +Ymd3eDVjRUFpOCtwaERTdTl6TU9HREh1eHh5S2dCVnJIeEJyTzNjKzU0RU1jZXVNVnhpUU9uNS9E +UWtMMk1CNnVhMXFDakI3RUpXUzVtc0paK3JFc2Y4WXovR0YxZWZYZUpUN2NHRFRtNDZSWmVzZlRT +M2FXUGVCSVhGcytzMVJ0OUVVZnQrRjRPOFRPWEdIU0tON1hKQTY4RFF2L1dNNU12UmhUZkdqblU1 +QkpiRDh6Slo1LzkrS3MvNjgvNnMvNnNQK3ZQK3JQK3RLYityRCtmaWo4UER3RFhVTVVLU0JCQUFT +U3B0elFDYWlJaXpCRTF3Z1ZDbUQzRWpId0p4ZHRmRFppaWlZd0F4VWErTnlyMklGT0J4R2ZZYncv +eitRL0htUm9tOFY1NjlrMUxBaEdaOXlzTDlob0V5MkRtM09VekVJSlF4WWRJM3pOZ2dCY3hNZGlT +ZngyZXk2blJZaE8wZzBnOEpDSWRQMnJOY3pIVXFDaytFNnMxK0NKZTVsVXYzZ2pBWVNNdXpIZ1ZV +ejdZUE5NZm4zR3RIcHlaSjNBaWdoRnZNT2lYR0xESWkwdWZyU0YyKzhTSFFTLzBCRGI3MWFsWGhD +MkhHZ3dDOGh4K1BPUEpaejJoQnpIaFlqSzVIVjd3SnA4NzA4bnBVclBEd1hNY2lHTjRZNVNEUkYz +RXZ5SWRMTmJFb0Q3TG56NHlnMXJwVSsvYzFTT1cyazVzcDBZY2tMaFlBMTZOTFo0MWVvVVROWmlI +Unh3OTBIdmYzZFdHRzdYb2lXZlcyQzgyVTRucGpuOVk5U29HRlY5TmN1QkFYWGlDQlY3ODZZdjlq +RngvMXAvMjE1LzFaLzFaZjlhZjlhZDhCajdxei9yVHVGVi9Ic3hDYkJwcEFDbW9oSWhVUEVFaFdR +QkJOUVo1RWlvQ1daSUN5QWdBTWdvd0d1NU5naUxNQTdOdmlDYXVISVNoVUlmQUNtNEk4R2JEb1VH +NFFDcUNRWmtKVGdiMXIxSEJic2d0cHR6ZUxDazJ6NUhrZWN3TGUwWk1GZHp3bmxqT1lUNU5FUWN1 +K0JsQlBxTEVWWXh2SGY0TUhQaXNUaHpCWlA4ZVJpTytjS2taaktzSFlxeklydWFHRFFhNHhDTXEr +Y1h6T1VhemhwR3RFeGMyK1hJd2VhNEhwMWpQZi8xTXpRNUE5ZGtqeG5JNGU5UkVVRWJFYTc5Y09R +U0l6ajRjZXdPRUN6SGhFQk5HdldjTS9ZdWdjYVJXZTVuMXhIUlp3Y3FqZGxvejVCUkRmT3R3NEJM +ak5PajVmL0FMcitmZWZOR1EzUHVIeS9RYVQ5YlJrYmR4YWhCdkQ0VHBEVXoyTUxSOGUwZ01GbjhR +V2V0WEJuQkM3NnVWeWExLytxZ3VHblE0d21Pdm1oaFliL1VBSno3cjYyS2Y3K21GMnVTM0YvZm0r +RVE4YTJDdVArdFBlOFJZRG1kUC9WbC8xcC8xWi8xWmY5YWY5ZWN0Ky9OUWlKOUtpZDhEd0RVQXlV +VHB1WUw4Sks1Z3hWdmpzeUlVeWNpU3BVRklKanhOM3pjUHM4WUZNSUc1eTBFODVyZkJReTdRWW9o +SDlHdW9hU1JDL0VmYmllZlhGWmdVSVlSRUxQYkE2bmZPTmRvZXo1ZTR5WTBrYXhDZFlUL1NOUUpl +TmNPUm9UNzcxSVluRFliSEhqamtRQ1RqMlVzb1lzVk0xdURKR3JseFE2aEVwVTdDc0k5UkNBRW5N +YWc5K0xmUFduc0laM2tla2VpQnRmYkFCRDgrOWNkbkhHdTIrbUVTaTNCY2NCS1dBMUJkZUhDWHkv +T0l6RkFMUXpFaTduQzVCaDBzMW5sT3FIanpYVzc4ZTY1MlBTQkNkY2Zzc0ZtNzh4Tm5EK2ZoUzF5 +R2pxbnM5Vm50OUtjZmpJRURlL0h2dVgyZTA0SmM1dFNyN2pQK2VkRFFrL3J3cnE5TXNRZkkxSnBl +NFZZZXo4VDFxeTg0NUEzODRNcFFqNXJUVi8xUUUyN3hJZTZhYnVMaEFqN1lZSGZoVlgxcWc1TzJZ +YVlETWRVdFp2MVpmOWFmOVdmOVdYL1duL1ZuL1ZsL21uc3Evanc4OERiR1d3cWdpWVBRTlh1YlA2 +Q0JNYWRCcmdBUnhGMEJMdnVRanhUL3YwTVJtT2Z1eE1pZ0N2TFpKYzhXT3cxUmxJTEU4SzluYWJa +aU5JSTVZUFRUczM4aGpJSHROL3gxYnNRb3J5WWd3M1BFSThCbnRmcWNRV3oycWRNY3ZJak1RQ3po +RXBVNVBPRkQzajBFcGtINHloc2VKRE1UMFRoMDdBMC9UTzBnRkYvekdRbC84T1UvMUdaS3RZWnIy +RnhpYXJyMXVMZkhHalVtSjB3UnBWak1sNzNxZ3lVaVZnT09jUWpYNHBzKzRNTitZbFNEdHo0RWhF +L1k4S0VlQndMeGlHM3QyYit6RHpCWWd4YzFXQWM3ckdMcHB6elc3TG9SS1I3Rm9STTVjTGM0SjA4 +TWhCZjdJL0p3WmExMW5qdXcxTGdIeVJoSm5kYUp4WFIwNTdrNDhPa2g0NHF4OWMrY211Z1hMbm53 +RlBQQ3EyLzJyRTRtUDY3cFZPM21ySU0zQnRXdnh4eW9VKy93QzRmUDR0SzlIdEdSbnRnVHpkYWY5 +V2Y5V1gvU1VQMVpmOWFmOVNjdVBLOC82ODliOXVmaDdRVHhBKytoRFpxdHFjQzZOQ0NnRmM5c0F2 +b3BWM0RFSWdMeGtpQ0RRTVR4WGROOWRyZWVNSWdISVlwQUVERW95cndtTXlqVHBCRXcrZzZYWDFm +d21YQVlXOUVLUXlTeUVJVU1qWU10dFRHM2VqS0lXeTBhb1VIaVBUYW8zQm9FdDcwd3FOODZKTXFo +V2Q0OHlPUHdrWk5Ra0U4QTZsYW56ekJybE11ekZkSndoZzg0SWliMXdHU3ZYQXdrcHFiaUIxYTln +SFY3TkwxUUN5N2RZZHRHenpyZk5Yb1BsT0VSWnZIdzc4Mld0VERnWEsvRXNsNXZqWWNjY3hHZmVh +S3p4bWM5eTFxWXhZUVpGM2hUb3pjeThPTktiK0JLbk95bk94ekk0U0RTVjg4WWFlTVBkbmRhdEVj +L3JiRVc1M0RpemlXbjJQYXIxUjY1VTE5MHJIYWZ0eC9EQVcxNUxxZjF1S1luY3pHb05mUXRuN3Iw +MUZzay9kOERabmloWndlTmVueTNYdTljc1B0dWpWNm9DVS8wSUU4T0VmWGhzUDZzUDJHb1ArdlAr +clArckQvcnovcXovbndxL2p3RTl4K3ZDZ1pzaUpmQVFrVVlpaUE2YXhDc0VNODBUbk1SenlCRXBU +Zy9PZHRMMUJ0djR0cGpuYmM0NWpSVU00RTJ4QWVXMEdCeVJiVGVvdmkxQkNRd3FEWFdHdGFFSUlS +cURqSzI4R3ZSQ0laVE16UFVKNDQ3d1RMaEd2UTc1NjhwaEdUeHJMTUdmcUluZVBYand5RW50aHBq +MEpoSlErMG5EakhFMXhodmxmQ0ZHMitlOE9Vek1jbExYUGJLNzdsOStCRVRaK2JsbHc4bVFzUzFP +VDJBTlljT2ZzVWdhT0l3NTRMSFd1dmdWNU40MXU5Ym1SMzNLeFExbThNcERITGFDNnZEVzQvMFFj +L3dIVDNvOFhMS1RDTm9PT1ExdjN1SHZ4WDBjQ2MyZkM0Y3JrRm4zbmVjd1NTUDczTEM1ZzZQOWJo +eng3ZjRqSmJEelp3ZXFKRStHVU5ORGtQNTFXS1A3K0xEU2hjT1QrdG9sOWJweWR6MmZuSTdDSEhu +T1I3VkpINE9BSndzbjdPZUp0VHBVSWxSY2FOdU9QR2xoMkpaaDd2NnMvNnNQK3RQY2V2UCtoTU9l +YzN2M3ZwejY2NC82OC82OC9iOGVSRDA4Kzk2elU0Z1FITENFRmh6emZ2cFhGTGdnVUt5QklwVUJH +TUFEOUFXTUFBQUp6cHZXS3dEZ0VtQUZEUGlSQnFnR3FnSVJER0tpL0NKQThITStkdzdmbUJKRU5N +OG5JWkdham9jNm9nSllTSVdXT0MwQjc0TXpaSVBObkdKOWpUbjNjNGpHd2NJZEY4VFRVeUNzQWRl +elhYSWVWTkFHQzR4ektrUGovQm90Qnhxd1lkL0JjMTNuT0JEN01VOWUyTUllWEVxSjJFU1dRNGFj +V05temNTQit2QnAvVFo2YW5acE5QRVordWJ0blJoaXExOHUrSEVqTGxQbzB6bnV0KzlxY1lkTlBO +LzFRWDc5Z3kreDFlVXpIT0l4bHd0WHVFdXRNTHVJVkYyZU1aV1llUExaSlQ0OGEvVGhod2JoOE5t +YkZEeVpwenQzdmRGYmU4KzZ6My9sempyWThlVzczRFR2NE1OVERnMXg2RjNQL1VmL2VncURlUm96 +ajN2eDlRNFB0SWhQeHJSWEh2dnhwRGJZYWNMelUrK1gwK2lUMzk1NFFUemZZY09INy9Wbi9WbC8x +cC8xWi8xWmY5YWZxL01aTU5XZjllY3QrM04vQVBWUFBoTzQ0b0NYRUpFMmVLNUF3V3p3bkxtczgw +d3pOTlljZ2RzamppWVFnV1FJVUJnaEVLYzNJbUlvSERBRmE3QjFESlcvOG5kUHN4ajBZKzkrN1lP +WW1RSVdUU0lDSnBUZjg0Z3hwSG5Mb2puaXdaUmh2elgyRXhPQnd3NExZYXJSUE1MVlJjU2E3N05h +a0E0N004QnRqb2c5ZDZrTkZnM1NhTGpWSWo2RHh0Qnd3MitkZVRIMkFCdE1lTkJVOFlnY0grcFpn +YzdCQ1h2NkFvUDEraUdtL1Q2TEpRYXV4RmFuR0h1SXpoNFlyYmVYc0dGU1Z3YXhXR092dTNrNWlS +bzMrc1o4OW9nbkg0UEtiVDFoUGo3ODVZZmZRUUEvem4yM1ZpNjg0NTlXeEhmWml4dERucTFuZENs +UGVnR0hXTDR6Qno1aDhJd3A5TWN6OFJscFRUeEd0ZGM2R2xFYlhITGl6ejhqamx2WXJNR1JQT3B4 +T05BYlRWbnJzTU9SdmZEaEdCYTF3WThqY1JqYmQzbGpXam1zMVFjNFlZU2wvcXcvWWJUZTN2cXov +cXcvNjgvNnMvNnNQK3RQR0R5N1ZYOGVSRXYwRmtocU1ZT2F0SUVCL1RRc0dQSVVSZlFDQ0c2dFow +aUlHQkFya1RsRmE0STlQZ1BwcitjVkpRYkN4Q2NlWkNzS0hpQWREdHVzRWFLbUJxdTdpMGpnUW81 +OWpPSk5qTDBhckFaMXdTV1BQZkJsbUNjb3hJcGhQL0lpQkdReW95YkNJamVEaUtWbWZHbUdOMW4y +YWpvaDRNS2NldVdYMDY5WUVJMWgzbjhEUUt6eSt1eDN5Mk1pdVBBbHQzdEVyS2w2UUNUdWVGT3JP +SHFsRHFhSmtCd1F2c3VCVzF3UmtMNkk2OEFKMS9aWUp4Yk02c3FReXpPMXExdGNvck5lci9YVTVU +UFQwMHlNZzBQWTlUQUdkVkRCNGJ2K21vOGdQZHZjczU0QjhPbVoyUEFiMXA4R1BmKzdDalhLcjAv +V3c2aEd1Y3d4c3YwK3l3RzdmT3BRbTN6MjRIbk5OQWNBRS9ydVZ5NzBFUlozdXBiZllRa1hEbkd0 +enRRdHB0cHBIRyt3V3VzNXpzUjNDTUJqSHoyNXJOVVgvYUNsYUxiK3JEL3J6L3F6L3F3LzY4LzZz +LzZzUDUrS1AvZS9BVVVZTVJDVnpSWUk2aElBTVlvWEZFaUMweXdnTlZnaWhBT2wwV0tJU2RSSUVz +Tm5DUkZGak13aUJwSzJnVE9RSmdaeVBTZG9RRFhDTTI5VkVFWUVjQk9rdHkxYjRKalZIbStmUERj +OFZ4ZWlZUERYNXdneTdOTU1zZFFEcDg4YW9TNURQQWFHQVg0MXllTjdUS09SbXVTQXNGZXo4Y1hr +bW80YmVCd2NZakdnK1A0N0FUSGw4QllDSi9BU3BtZTdkN2pVZFBraUNIM0FrYnNjMXNMaWtrZnZ6 +RHZRaU1sbmZZUVRMZ0tLK1ZPVEhOYmpRQzU5SVhSWWNLK2ZlSE40aFd0M3d2VE1RVTBiOEsxQkov +NzNHdFJoWWkwUlJnczBvK2ZxOEJ6K21ESnZER0dtR2Q5ZEJqTXlHdVBoQVFmbXJNTS9EdllRbXJy +T1EvRitjK05USHJXNDFDdVhlRFFTNDRwaHZmcm9qTEY4eDZObjFzdW5icnpGK09iRWk0N2x0a2R2 +N09FZDJQQ1pnVE44cVNHYzdtRTUzMkdzUCt2UCtyUCtURS9xei9veldxZy82OC82cy82OFpYOGVC +SThZQlFtdVlPUnU0ZGRDZ0pFZ3pWRzROVWozV2JNVm9XQkM5Unc0RjBBQVI4Qkk5N1pFWWNBREFx +VGhNN0FNSTQvOUdxK0I4dmcvNTRWWGd3bmV2Q0VPUW1BbE9BVWFoTVlVNGlFSDJRakk4QXd1dGJ2 +RVhjRmNoUUNEWnhvbmwxZ2FpUys1WWxBNVBiTU9EMFRsc0xOSFBVUXF0NW85dDhldkxIaXpCRGR6 +T3VpSUIzZE1za0lhUHVCVm4rZUVJd2N4NEI5T3NkV0pROWp3clIvcEM2TXpncndFVEJ6NFVZdjRP +YlJnZzBWcytaYjNLdy8yUmN6MnFpc3h6SWtOZy80NmVPVVdBKzk0Y2JnUU0veWV5YVhHTmVqc3cx +ZHFsUitIYTRDSnl4ajRjbGhZWjRpM2EwWVhkS3BtejZ6TElhQzNZc0lwRG15ZXlZY1RhOXoxMno0 +NGNXcGVESFhwaVRkLzl1SlpQTGw0d1VHYk5lckZvVno2SUk3MTZwRkhuYlFpdis5NmdJOGNXdEdV +M3NMa1lIWEhVZjFaZjZxbC9xdy82OC82cy82c1AvRms0THIrckQvRnVWVi9Ic3ppSjF5a0FTS3Ba +RUFnUzlNTUFaR0JlSTBSRE9HYXBYa1JMY0VyR0JDQUVRa3dFZHV2QUdLMUR3QnhORU1CZnBLMlQw +NUUyQ09IUWpRV0dYblRvaWhZRENRclNCM201RFVRdzNqaU1Sb3hXcE1obitkeVdlZnRCa3plUEJn +TUtsNk1hcDdvUFlNTkx2VjRLNlVaOXVLUVdOVERuUGJoQmMvMkVKa0cyZVBBTWMrcytOZFU4M2lD +MlNXRy9HTGFweWNhYlEzZWNJQmYvWUlCTC9Lb3h6NXI5Y2QrdllWUnZUbVk5TUl6SW91QTNNVTgz +eENkYnpIMklCaWUxZSt6bVBLa2QvcWsxd1NOWTdsaGg4bCs2OXp0azhzQkVJTXltbHJOMld0T1RI +aWpHVDJ4enQyY3ZVeEM0SXpEaE9aOHg0bThNTGpraGs5OHNYRklMeTdQY1ljVHVkVHRtY05JdjNP +WTBJbyswdm5qWE9yRmcvM203ZlhjSGhqb1Z0L0NvVC9rK0VOTytjVE5RUXFyUGZTbEp0Nm9QK3RQ +eityUCtqTmMxNS8xWi8xWmY5YWY5YWZybHYxNUVBcmhBMklEVWlTekVGa1dJaVlHOGd6eDFnR2ow +UXl4NitjdWp1SVE3bTJCZ3NYYitBT2NnT1ZVbEdLUkJyejQ1aFNnS0VMenUvUUswUWpQeEJHVHVl +WFZYSU13RktkSTJNUjkrR3Zwd1V0WW5uc1RnNlFNOVlyckdUemZuYi9mUzcxeWFhaGZRMUFEQWRz +alB3R0x3Zmlld1FBVGsya0NBN2prRng4R1hLclRjd2FWODVQdmY4UEcxMVJpY3NjbDdPSnFGSDZJ +eTNkMTR0Z3pPWWxWUC9SQXMyR1VqMURsZGdpRVkvWHBUZnBGREhBeGpieGlxVnZmMUlFSHNjV1JI +eC93cUZOY01hTUZlOE9YV0dxRUYwOWl3YXpmTUlvQko0dzRnY1ZuK3dsYVRCZXV4TWFMZFRFNm5m +aU9mOGJ3ZWZzeEdHRlRVN2d3QnovOTBJb0RDSWV3aWUrZ2pYbGR5UThMQStJTHovYUpRWStHZFhq +R2d6d3cwUng4OHRBaFhtTkdPWEhqc3p3MEFwZjRlbTNPZWpFOWs2LytyRC9ocWovcnovcXovcXcv +NjgvNnMvNVUwMVB3NStFTDRUR0V0MENLQXdaZ2w4VElCUWJwZ2tqa096QU12S2E5TmxoZ1RRT09j +QUd4WG54RUE4NWsvaG9XUWFjaDdqWUhzSEpxbHFMOXg4dUlRakFjVE9FTmtZYTRXMnNFRi9Ma2dz +ZVFqMGlzaDRjaEZMMURNd2MvZ2FyZlBmL2g5RG5PL3dOZ1FvWlJveHhrNm8xSk5GWjk2aFJmYkpm +bjNpTGdoRWpzTnk4SGdjSGx1ZCtOOXlzWDNoQjVVNk4rdUt3WGYyc1pMbkNteWU1aXdLVEpUQ1Fm +UStrSFhQZ1hCOS8yVzRNUDgzS3JTVXo1Y0lObkJ5YXNUS01lZWR6VlFZd0V0OEthWjNLRWI3d1JP +TjNvazlqZXFvZ1BuN3pxOUhZcUI2YnZzRVh3WXBtSDFXZno4bm1tWmpIWG9LT0JQVEFHajF6Um1U +emlFTHY0RGdGR01ZOG5NZFZOMTNTQUQzenBzMXcraTYxK2E4UVRWLy8wTEw4dW9PL3VOQ2FYZXF6 +akg3cDB1S2dKVm50alVDWlhqMHVPOExqK21WeStXd3N2M0RnVGt3Yk0xWi8xWi8xWmY5YWY5V2Y5 +V1gvS1ZYL1duMC9Gbi9zM29DWjlFUVFoTmtobUlXTVl5QkxBZDJDUnBEak5BQndZQlNKRlk4VUFU +cUlJbkhEczhablFGQ3NXQWpVWmdlTEZVTjRRSVVNeFltbnN4OS83dXYzcG43QmhObUFOam9qQ1FK +UjlNTHZiUzVEbk9OOThpSWxnNURHYVptZWUrT1R5REUvd3FBOSsrQ0lpYjNtSW5SaHc1RGt1aWNC +ZVRZNFJHRitENEZTRFp5NEhqanJNaXlPK05mTGpUQjc4T1NSZ0pVYWlrQU1QOXVMVGVudDlWcGZl +K213OVRPbVpuTXlrWm5mYzRVa3NhNngzV0RBRVVlL0JmUlVRZlBLS0E1ZmU3V0UxZ3NNbi91M1A0 +WDBLK253anArL2Voc0VyajdoRUxUZWQySU4zdWJmbTY3enZhOURwaTN4cWRCZmJQbnZrOUV6TjBi +VG42Wk1lcVZOZWVmUUdKcng0dmdhZDNzVDREaU9IRUI3TnE4dGREbGlzODRlTm10UWpKcTNpQWxj +NUFNV2tjWmoxRUdaN3hmRE1XbGg1VHkwNVdQUlhuK3JQK2hOMzlXZjlhVS85V1gvQ0swLzlXWC9X +bi9XbldMZm96d09oQkVTc0JPMG5iT2F5S0tBMXlYUEJCU0oyNnlYV0tPdVlDaGthSW82a2tsdXZV +UEgycC9jcHhOc0pJbCtEenI0Vndsd0t0STRBSFE2S1Y1aUM1WkRYLzZkVHNHZ3FzbHoyYUlxY2lO +a3hNZUdCeGVVTkVERWE0c1pFTGppUTZUUGl4Q1JNZ3ZmbWhKQ1pDRDd4NVNFYzJIRG9lUVRtallN +NW1OU3U1dUFpRm5FMWdFR0oxZS9QSjdjaEg5d01nQ1BmQ1l5d0NFWXVBb0k1M010dEhkSFo1enRj +OGxxalZ3U01NL3VER1FhZjdYR1oxMGNpd3BHTEJuSnd5N1VDbnp1OEs3NnBGMS9XcUJkMm42M0JP +NDdneDRtY2VpQVByQkdzbVBMVEMyNGNWTVR1TTl4eTRBNGUrbUlXbjhVVmh5WmNzRWR2MjQvQkFK +dmM5SVBQYUZNK05Za252enkrTTQ4NGVxOHY2b21wMVV3M2V5Qk43Y3lySHZOcWtrY2NPZXlUUjc5 +NXlETjQxQlUreEZTemVoaFNmcmg4VGcvcnovclRIbGY5V1gvV24vVm4vVmwvMXAvMUp3eTM3TThE +WUUwaUhnVUxKcEdmZWtPaTU0TGFqSGpGNXp2eEVZdDFFaWxBd2NUcG4yTDJYQkVPQVFRQ3lKd0tC +MEFEeEdkNGUwS2NQZFl3bVdJVTRqdVRXVXQwY0NnY21kYXJCU243ZkFhamhXVFl2T0h4aHVVYzkw +czAvUEpwbnByaHl6eWl4WVdSTWV4ZEFjNTZlU0ppK1dDWC84SGd3NEcxT0lSOTMrd01GdlZFakdK +NjVpQnlhTUZvYUk3RFJ0UHNWeDhzdUNRRytmUUpCK0xJYVoxOVB1T1E2ZkFzaHZ5dzRrME9jOTVx +N2R1YXlhMUdNV2xBSEh5NzFFQ0k2bWZTUGJDbVRqSGtVaCtjRVp5OThPR2JOcXhUSzhHTHhXQTRr +bGN0OWxxbmg5YmhRZ3ltTmZaQUgxSFRuQmg0aFVjdDZySlBickgwRWo3MXF5ZmN1QnYyMDV0K2l4 +bnM4c0l1aHYwdWErVzJONXpBbVQxNGhJVkIxZXVRdFVaczJOUVpYZG5ESDliTEFZUFlkTUNnY3FT +WERsaWZjNGpnb2Y2c1A5VW9adjFaZjlhZjlXZjlXWDhhOVdmOTZXN2NxajhQaEJNYklJQnBoa0RJ +VXFoR1NFZ2dOa2ttcVNRQWFnSlN0dmlabDhpY09CcGhYd1NSbjRpWlFVNkF4RUE2Z3BsWVhuTnc3 +YUZ4ZC80SDRuSWdNLy9hRnNFcnhOQkFhK1cyZDV0blRBTVJTakNNSisvNWF3TG5RS1lZNmpFbnht +blE4MThKVXdmczhycW44ZmlKUVJGUDNEQ0pBZWNhOUJ2blg2UGpRUTZIb0lZWjl0dGpQWU13cXQr +WDl4eG05YWpCZXJ6aTBMWGluanptMGxRNGNPcE9GTXZmN1BOZFhUQXdGMzdGVW9jTEhyejRqSGZy +R1JvdXVXSTZ4ckpQSFRHUmVaL3RWNi9ZUkNzL3dhb0hQbHk2Y0dHNDIwTUxNTnNMbXg3YmkxL1Bh +TXpBditkeTRkcWhtY044dVJ5Ulcyc05qY0pMZ3pDNDB4RHNobnJVSmo1Tis1eTRkSWxiaG1CRXor +U0FYUXpQOUFSSDlvUVhCNmRjZENPZm11MUxQYmdYMTFxNGVVVzkxaGppMEtjNjNjMzV6T3o0bHJQ +K3JEL3J6L3F6L3F3LzY4LzYwOTc2cy81OEt2NDhCRmVFQllvRlJpRE45MHhCQkdzeEVFd0ZxR0Fh +WlIzUWdpb0ljUXBVTE1MRjhOZjdtZ1lRRWNvcHJqbjUxcUJESUVNVFZVUkdSTVN1WURua0ppTHJH +UFRFZHY0cllkWWpLMkxiTVEyRUNSYno4dHFmRWZ4RWc4Z2NWakVvUHNTVTIxNU5WYWU4dnR0UEdK +NnJqOWpOcHg1eHhkY29iOHZ3WVJBbkhPcjBmejRjM09valFnSUtCM0R0RzZJNXlPUVYwM00xNlVl +d3FOTWFuMkdBYWZzMDZ6UmJMUVNVZ3dhM09KUmJQUHRmblFQRGV1dGNqQ2UzZUFRSGwzcUlDTllj +V0FST2RIVGtBSUZKcngwa1B1dWZYcmh3N0ZjOTlGbE11c29mRExEUWgvalc0dEIzR09RZ2NpYTNI +cWQ3RUk3NDZjaG5RMHl4NGNLRmRVYndpcU1PR3BLYmRtQmZQUTkvTVFhK1lGV1AvdU5TWEx6Z0NB +Wjd2TmtUU3o2eFlJY1RuNTdEVHcvUnNQMzJwbzRZR3phNDlnK3gyV3RmZEZkLzFwLzIxNS8xWi8x +WmY5YWY5ZWVPK25QWDFaKzM2ODlEd3hHamNZaVFSSkV1bTRnSldZSm9nRTFFSTdBazFpSElNdzBo +REkxRmpGOEowRnppVllpM0g5YjdiZzhSS2d6Sk1RdXhXR09mL1lwRFB1SWNITjZrTUR4QmF6NXN5 +SWZUTS9VczVobnd3WUY0ZC9ua2ZqRGc1SlNQTURWRFE5VzQ0L3FtUmt3TjBTaXhZY0NWTjAwNFFi +d1k1dFcrdkV3OUx2bkUxRnh2Z2hoaU9aZzE0dUxFUDFHdGtiakRQV3g3NE13ZXovQUs1ejZiUFBZ +UkNuRzRjdURoUi84MEYzOTY2bmxpNE1rOGpuRUFQOHpxZHVISEpZLytFUXJlZkNjY1Z6NmJVd09l +eFhZZ1d3dUx2SEtHYzNqMFFSOGRacDZwQVE1enVGMkR6RDBISEhQRW9ITG9nNXp3Und1NGRaamho +azVoRUNjYUVzc2hKSjhoUHV5dzJLOTJ1ZWlacVdBUks2YjFETS9waTM2b3ozTjkxMTl4YUZTdXJC +T0RRZkdqcjNMQ3JINzlzaVlIT015MGErMkRkcTZla3N2ZStyUCtyRC9yei9xei9xdy82OC82cy81 +OFN2NDhGS0FvWUV3S1lGSnl2L3U3UCtGTE9Kc1VHVUVRTW5FeW1yM1dDV2dld2ZhNk5CdlFGZWNN +cEhoRFlEMVNmRGVRYXk5eXhaYkRYMlVyVHJQa2gwMGNwQkFDY2NDMlJoc0NOUTJlR05TYyt1Q1N6 +NlV4Q0RLeVR4enJDTlhhMDhEbnZ4Skd6SWdsQW8zQXdhdGphT2FDQndmSWhVdCsyQW1SRWRXamNX +TGlTRzdyY2F0Mk5iZ0l5Y0hqdVZybHNBODI2NGpUbkdmZUxIbU9ZL2psY0hBUW1zTkRMU3VPeWJV +MURTYllEWFhnV3gzeW1sdURYdGVJajJmQzhZYUhFT1dGUC8xWHMzbTVmSWRGNzJDVW0vSHhRM2pp +YmkrR0MzMmtMM000dGRaZWNZaGRYRDFYTDVQb1hkNG93a0JIYXNTZk5iRHFFU3o2Nm03T2N6bmt4 +cXZQeStuVUxoZmVmS1pKUE1tUk9wZ1BEdC8xM2pONmROakk2VEpublI3amhNYnhyTDl3d3dtN3o3 +RElsY05KSGZhYjkzWUlaczlXOTVOTGIzQmhEYXp3MVovMVo5YUlYMy9Xbi9Wbi9WbC8xcC9tNnMv +Njg1Yjl1ZjhOYU40NjJJUUVHd0RVTk1VTGFMRkdDUUkwVU5ibnUzbkZNWWlmZmhGQVRBZ1JmNGth +NFl2UGVQYmJLNWNHMndPRE9OYklMWWJpRklJY3hkdjN1WS85eEg4NFBCQm1EK0hKbzFpNWNvQWcy +Q0hFUEJwSE1JWjQ2b2Vma01RbUxERU45Y2lEQytzWUlURUljZGRPRHQrOUFUSXZ0MmR3aHh0M3Rj +SG11UnJod2d1T21CMG5tZ2szTHN6TFpTLzhjbmttTDV4cWR1RkEzUFFOVHJVd29QVjQ4OHpRZE05 +aWRIY1lQR01ZQjQyOXpLYTM3dko3Sms5NlEwUzB3TURxd2lNRHFwVXB4RE9uaDJjdnhrb094ZUU5 +ZmNVcFBPSXpydlg0a3d2djRWQStGenkwaEJ2R3NNKzg3NnVOdVRNc0R0U2haZ2U1WFBxSnZ6WG9I +R3JpeUNXTzNHcVJsMW1qY1JqVWpLTUhnMTZ4NFYxdTgvU2gvdzRYOGVCZ1JyblU3aTR1L0hqQm9m +bGN5WWRIOFdCVHIrZldxcVArckQvcnovcXovcXcvNjgvNnMvNnNQOVg4RlB4NWFKUzNDUnJFS0FK +YWFKRm5TL0Q4aE8wbjJXeTB4bHdNN1R2Z0JBYXNnc1QxcXdSK1VrYVNRb2tQUVhuREE3REN4VkdF +TmNDS0xRNWMrMlpoQkVHc2lsZEkva050dVRWZERnMFFrK0FJV0M1RFBOZzFqVWxoUVl5QkRIc0lY +R01abkZCMlRGeTFhb0JuOElndEJueE10ZmtuejhZZFBKcHUzV0tleHN1TlAySXhEN3YxdUJKRFRU +akNsYjE0TTFaMEl3YjdyY0VyUVFlUFdpSTBNWFA0UkVRK2V3dmpNNDVQUHM3ZnpmY1pCeEdXdTly +MUVBL0w4eGpPb1JoUnVlQ055UFVEcHpDSnljaEVhMDRkY3NBcTFzYWJlbkJDUjNqd05zMWJFSE5F +S0Y0TUtnWWU5SWdKUE5kZjMyTWdkVElETHZFc2xqM204QTNyOGo3MTRjZmU1TExXM1dHaUh0eXFu +K2JGTjJlL3ZMRGcyaG9jNkljMWNybUxUUk55V2JmWTlXVXU4Y1VSRjQ4dzBEZU02c1d2ZzRPRzFC +THR5MnVOeS9mNnMvNnNQK3ZQK3JQK3JEL3J6L3F6L29UMXFmanpVSVRnRnRvSWRKcGlnU1IrOHZi +WkdvVktLb0RFUUhsbW5jL0Fpa01naEtjNElvZ3hOTlBibEMzOFNwYTNBUUFUbXVhSzRiTjlHdUR5 +akNnVTdsY2N2R2xZZzB6VEZTVU9JM2grR3VFY0JFUVU3akF5NFlwbEJpenFWNDhtK1NldzVUQ1Fw +MGFpa1FlQjN1SVFCNHlwTFcraHpHc1dESVJEbEdMQlpZK2FGOWNZSDAvTTRLM0NTOCsrYVorTHli +ankycHNHMjIrOU9mRWNLR3B4bUJLS09MalJINXlxQTNld0VIWjRNendYMDZFZ3Q3M2l5UTJESHFt +VitCZ083MnFUWDF4OGljWEE3dkxySWJ6dzRGSFB4TUpkdEdJZnp1MHo3M0NETlJxUUQ1ZDY1N3Yx +Tk9SeUNPejN3WUpyTWRRdkxpN3B4aHJQWVNkMnNmQ1J0MUhtMU9PNXRiZzBMNjdQZXFmUGFwSmZI +SnFTUnkyKzQyRnJuMXJGY1hpWTAxYzk5eGxQeWJHSDVWemlXd3Nydm5PdzZMTjZ3cE4rMnc4VHJ1 +VjAxWi8xWi8xWmY5YWY5V2Y5V1gvV24vV25XRS9GbjRjQ1lpNUZhS3J2Z2x1d3pSMFRDQUl3d1po +M0ljZDZJdmNkYVpvTmtNYm5kK0VWZ0FRa2E2cGZNV0FVeFFBTUtMTWp6WHBZUXByR01yYTNTa2lB +d1YvVHgzVG1rU0FXTVVad093YTNQWEtaZzhObnp3MUVhcUk1TWNSMVI1NmNzT0ZIMHhqU213M1BZ +SXVodkExQVBNem1tSURnaUV0VGZVZTQzTlpwRkE2SlV3eGlWUWRjWi9QdWR6OGUwaWh4elBuT2VB +NVJYTUlQSC83VmFVN2Y4R2lQT0hMQWFQaXNUeml5SGdiN2M2Z1JTUXhqajU1czdzR3IveEcwT2Jq +MDNtVWRQR3IwdS81cWhoRmVNZWxrZTM5OUk3ZjltT2ZXNjV1NGRLWU9jZFdGZnh5YVp6QzZzVTV2 +WUJHYmJod00xc2M0NFF4bU9kUm92K2M0dGRibHVWck02YWYrV3FPZTFHYS9aMnBSRXd3NWxOU3Nu +LzRnb2gvY3ltbk9uaHhPREtnT1F3eXhjVUczWXVnWDdjQmpINDcwenpwWC9WbC8xcC8xWi8xWmY5 +YWY5V2Y5V1grSzhWVDhlYXpvWjVIaVlsREVhYVNGYVlBZ0N2S1RzMkQyYUtUdmlDVXluNEZDcElL +SlR4eHppZ0RNSHMrOUtSRFRSYlRtckZ0eVI0ek1EVWVhNEo5ejloMEpqTUtncDZEdmxrQmtSbnpl +Zm1Rb0dsNjFhTVNhWkV6Z2d0Y3p0YmhnSmo0NW1SZ25SSy81N3RiQ0Y0R0hEeUxYQ0h2bHNWYzlP +UlRzdzFGdzRRaFdEWFB3V0tjZXNZMFkxTHg0bXU4dUo5NklnT2psZDRpcHlScDU0SUNKUU9CblBp +STBZSG1NeHgxMithMGxQdUxWYnpYRElXZTBZTTJ1R3hIaEhGN0djaUJiaXhkOTBsTWFpUERrdHdZ +L2VvY0g5Y0ZndjdWd3E0TUdmTllESnBYRHZDRU9YdUhKbW4wK1dPbGlkWFkxbGJ3KzB5K0RSRnU0 +d2RzYWJ1cFFxN1g0VjVQNmNLUmV2TUFaczhvSmd6VmlxZ04vOE1DdUZoNlMzMzU3Y1lBYkI0dWNZ +dEN6Q3gvaXdvTm5hejJqYS9XazUvVm4vV2x0L1ZsLzJsTi8xcC9xcXovcnovcXovcnhsZis3L0Rj +c2FiQVFxdU1XS2tkUkZESDZhdGNsbjY0aFZvVUJHNUlJVGhidWlDYytiSUVVQzc0NVFaREpDQ3JO +ZmZKZThZZ0QzcVErL2RVbXpSM0ZwdkV0K1lyRE9ITXhJUllDZjJIMW1YRU5OdmlNTERvTGR1VEVS +a1NNR1NlcENPSUdxeXp4cytFR21mVDdqd1dmMUVMVG1FUjFjNHNIUC9CR0YzSExZYythK3JDQzhK +Y0c3TnlydTNrNVpyeDROMVJ6TmxNOWJHdUxDTTRQNkxENUJ5R2ZlWGZ4d2FnMGh5VVhvaHB6MjR3 +NW1QREsyUGNtblB3eWhadnpDTHo3KzVDTmlkY0tDSzgvWE9JTUgvNjk4NkVjM0ZqdzRrbDhzODlZ +N25PSEF0OXo0aTBIaGx5czU4Z2VESHZvZTA4TGxPYzNrb01XVC91TFFKWTd2OERFTVBITDRUc2VN +SUc3NlJKT2V3eVIyRGxzMXdhZGVNZFc2cHArRGdsNDI1OVFTYmNSZzhsanZzenJndHg0V09kWGhX +ZUxqeWxxeDB6dDExcC8xWi8xWmY5YWY5V2Y5V1gvV24vV25TeHpmNGJ0bGYrNnY0QklJSWdHMmVJ +VTF6MnhDcXAvdUxYWkpBSXdnaUFmSVhzVXdGR0RFN2ZmSi9TNjJoQkl6QVdEaU15NUNOTUozQlpn +VFd4eUM5aDlpSzk0dzcvZlh4VWFHeitJam5ObVFENU5hQ0ZBdUdBeEVJSW9JelNjbUk4QUdlNXBL +UEo3Sko2NDNDRWoxM2JvWVVYekVNcEw2Q1IwdW4yRWtKSHZVbzdIcWROK0RZM0RoMVI1OGVSTm1q +NFBGZW9MRHR6MHhXa1JySHd6cXhadCsyQXViUThxOE9XdmhjemVQQXhlT21GUGVIR1RXNFpzdzdT +VkFKaVVldldBbTgvaFJ1M3NPUkhxSkFGY2o4OW1CczF6TTRXVy9PaU5Jc2ZVSFhybkVVS040YXBU +TEhuMlZmLyt3bUZqMjZjWGltTy8yTWxONkhPUHBDd3ptY0dNOUhJeHZIMzZzd1lzODRzSnVMejNS +aURsN1VxTjFQdE1OakE0cGZhWjk4L2JLbDdWcnJNRVFYajFqWmx6aEh3N2Y1UlhET3Zuc1g5NG1O +azdnaUdicnovcXovcXcvNjgvNnMvNnNQK3ZQK3ZPcCtQTWdRRUVVQVFoREtwaFliQ1lRUDRrRGxn +WVJ1dWRJdEY1d2N4cVBBQUwxbVFuRnNWYk1nTklrK1FBak5QSHRrUStKeEVLNDh2ajFCYzM5OUVk +Ky9DeHc4aE1CUWpYQnZFTHNVUXNCaStPNVlTOFNrV1hPd1dQSUNUTlJ3Q3kyUXdFeHNEQ3dtc1ZV +cC8zQkxDK1JxWWR3UEZlbnowaTJuOGdKS1J5cUJVYkRHa2J4RmsxZCtIOXNVQnpKclI5cHRzKzQ4 +U2FKVVBBV0xHclNCM0hVbFA0UkMyN2dYMEhOdW9oY0xIZmY5Y3BkTHVLeGxxQndiWThZWXVKR1hI +Yzlod0hPN01HVmY2WWFWa0pUZDNqQ3AzWHFsQnRPYy9JUXVKcVlRQjc3N0pGTGJrYktrSWZwdzVY +aFVNV3Qvc2FnNHJnSGw1alc0TXVjMnVYV0o4L2xYcjVuM3Ryd2loZDlORThuNWhnSTN6U3pCaDJl +SGNBNU9GelcycTh1aHhjY1l1bVJ6dzUzMnZRZFY3Z3dqMXYxZUFabi9WbC8xcC8xcDVqMVovMVpm +OWFmOVdmOUtjNVQ4T2VoS0FJakFoc0ZCeGdCUUFKTnpEYlpBQkJnQUZxZkpwZ2o5alJFVEdJaVJN +MjBMd1lsQ0Rua1NueHZFWkNnS0lWNlc2T1puaE9ZWDFud1prbkJ6QW0zNHV5RmdaaVE0bEFRSjI4 +UGlGZ3V1QjdtWnNBaWorZkl0WWFKQ0M1NDFHUzkvTzdlZU1rVnc2c0ZSd1JBY05iWlE0akVpOE9J +ektFa2orRVprYm5VUWl4cVZZK2hjZUxBWWkxQnk2dDJlOXp4cjRrNEZGZGpQYmZIUWFFMkYxN2dW +SzhhY1FTclhIS0lyZmZxZ1FOMjRoSFhNM1c3RTZHNE9Uamt4SzN2OExvY09Ib08yK2FlZUdxWDI0 +RUp4eDZTb3dkenNEQWZ3YWNYY05IYTR6ZUcxb2l2TDNpbEtiVjZacGdYVTJ5NDhVSi8xb2l0WHBk +bjZzQ0R2dUZNeit4VmozaTRZSEoxMmFOMlBNR0dLN1hMWWI5L0lVd3NHSG5DZXRqTld5K3UrdUdX +UnkvRWdsY2RudEVTSHQzbHhqSE1PU3pxei9xei9xdy82OC82RTQ3NnMvNFVwLzZzUDYyNWRYL3Uz +NEFDRHBEbUpMamlKRVMyTng0Q0JqUlFTWkFtSzRyWU5CUzV4TXhrUUNLVUFBRUZUaFBsUUpENGlO +RkU1bFNvNW5sYnc4aTdaMzZLdDhmdlg1dG5WdkUwRlRaRklSTEpTTXMrYnhEa0VCZG1Cd2lTREc4 +czVGRjdEb3p3WUs5NVJPV1FRTFMzVm5MaXpIcTROLzdVaUs5dDNQQmdQNUZiWTczTEd2c2NISVNu +Zm5oZi9zQ2J0eWJZaUFkbWZkQWdYR3VhU3d4QzhCbHVjeTdQeENFTU9lUW5VSFdhOXp5OXNoY210 +YmpNcXprSERzTjRtNkYyZFlrTHA1N2loWkFjRkQ2Ymc5YzY5WXF2Unc0aTg5YXBVejcxekxHenVC +eVMrSlpmSGJCNWJvOWNjT2kzZUdxbUw4YldBMzBtV25XYVcrUFBjRmNEbkRpR1U4L2xGOTlhdFR1 +QTZjMWF1UFhMWHNiV1oyK005QkZuZU1RaG5OdS9pU2UzbXZldDF0enRFUXZHTmYvRU0rZDdOR0l2 +Zm1BU1M5NGNtTmJHQnptdzRKTmYvKzJ0UCt2UCtyUCtyRC9yei9xei9xdy82OCtuNU04RG9aSjdn +QmdMZ0ZNUXNpVFZNSUtUR0RDZ2lGZng5aUZDTUc4SUpFU20zMlBYTUxFMGt3aTlKVUF5OHdLVGd0 +TmdSYm5rMUVTNEdCQW16eG5VSWZESjk3L2hRUWdFbitacW5DYklCN044WWlFSzJlb2hhRU0rOFRY +VkFlTVE4YXNQWWpHZXZJalRCTFhiWnkzeXhQRmNMZXBYMjlZeXVWeE1acjhhTlZ4ajROTkVzYTJC +MFdHaUhya2RacnQzNnRHOENGd2Y3TWY5aW1ZYUNRdStYZGFJRlRIWkowN200WldUQ2ZSTlBCYzg5 +cXJEWnpIdGw5OVFGMTQ4czE2LzF5Q0RJVDNGbFZ4cVpXemM2NjMxMWp6RUhITWE5S1d2K0RLSE03 +Z2Mydkxwa3g3aTIzTnhIQ0kreTYxbmNvcUpCK3NNMkJ4d2VMU2ZBYUpSMnRFdisvU1AzdVNPNXRX +YkExTE1IQjY0UzYwdXZZSlhQUHYwUTYzeWlZa1BYS2lSTDhRVEYvNGM5dkxhWng1ZmhweDZSaWN3 +TTdKYWMwalVuL1ZuL1ZsLzFwLzFaLzFaZjlhZjllZFQ4dWZoZDVvMVNTRENzTm1taUV0Z1JTQkhj +S0xWVUp2OWhDdWdZaVJuSW1RZ2p1QmNZZ0V2WG41U1ZwaG1FSWdMV0taV3JMWGltZGR3eGNIa09R +UEI2dzNSZzZESGhIS2JoOHNlR0loQ1hQRUp6WFB4eFRiV29QUGRuR2ZJZlBHWk4yNERDTnFsSVJx +bVR1UlpxMzUzdVFnQkJyODJRQUNJTmUrTmlBdEg5bWw0RGpaaVE3NjR6SWt6T05Uak9iSGpUOU53 +bDhiSkk1NW5hYlJuOGpFbmZ1V3gxcnhZTUJHVG1QcUkwNnlUMXpxSEJvN0VjN2NXcCttejllTGpR +Qjlnb0JVYzUxQ2lEL3ZFOHJhRnNleko0WVFuZzc3VWJaMThZb21wOXpFb0V6RnMrck1HdlpyVFBi +WEp2OXFjWWIxY2NvY3JlOVZ2RGM3a29uTjlVQ2NjZXFGV2VPeHhNWlpZTkpGRFVrN1BmYllITno2 +blZqeUk3ZGw2WW5EU216M21tUllXOC9URkI3NnJHMDlxcEYrWVljQ0pHRERVbi9Vbmp1clArclAr +ckQvcnovcXovcXcvN1gwSy9qejhwSnhFQ2xXOGhJeGtvNHZZSlJZVVdEOHRTMlk5QWdUeVhhRVN4 +YUFNcGNuZUZMa3pqTGtJVXp5NW1FRXM1SXBEOEF4am45eXdNQVZ6ZXVieSsvSW50dnNsVWp6NXJm +R21nR2dRNHhtUmVhT2tSdlVaRENTbU9jTHc2d2NmZS9kcnQwNUNnZ2xaR2lrT1FWbnJtVm8weEZy +Q2dsVXRjTG9USkpLdGt4Tk9qY0dUNXhxc0dlcVVGMWZpeWVOQWtoOHY5dUhFNVRzTTRxdlhITkd0 +c0s3bWgxVXQ1cTNYSzhJbVFtdmdoTWRuZlNKY1F0cjRFd3MrK2ExWEY3TVFHWjU4VjBQeTRzSm5z +UXgxV2E4M09KZEhEbmRjTXJDNmFFZE9NWWhUelhRQnJ4N0NpRWY5VTR2NCtOUXZNZkJtcjlxSW1E +NE16L3o2Zzl3KzA1R1k2bEVuM3VTRnhaeWFhRTVjejhXVHkxNzVtTlErQjR3NVB2SGRIQTNoeWo1 +eGNqam9xL2g0Z2hPdjlxalJuTy84SWdZZldPdVpXSExJVFZQV3l3Rm4vVmwvYnZ5SlZYL1duL1Zu +L1ZsLzFwLzFaLzBwNXEzNzh4QWNLZTVJRmx3emtLL1liZG9RZ3d3Q1VRUlJLVjZCQW1xaTVnRHNJ +Z3ByL0VvQm9KNXBsdmgrT3JhZWVNWFdKUEhOV2J0dkhnYW81bWlZSWhTRllPWmp0UDM5K1ltSlBN +TWNyT29nZEhNR0hBU0RCTS9GRThzUVZ3NEV3OEFzZnZVaEJDR1IwR0cxeDJmcllTWTZPSEJGVEhD +Yng1Rm5mdklYUDRaUWp3WVF0ZmlKZ1hPSGxlYTZ4TUFGM1BhNFlITnBscDdvd2M0Ti8rNXFGOWY4 +bW1rKzY1ODNHZGJxa1pnK0U1bTh4TEoxajNpSTJoN1AxaGhqVG9lZVBsdXZCajBSUjQ5Z2hFYy81 +WWJCVUplRFVSM2laUjN1MVpPM2cvcGtuWG1jaUFtcmVtREM4ZG1qdThXc0xybVoyMW85bDFOK2RS +cE1pQnUvRmdPemVYb1VYeC93REt0NWNkVHF1ejZMU1NONFdRM08vbkJzUDV6VzRzRXpkY2pyZWJR +YWZka25IKzdVcUg1NFl6aDh5K0ZRd0FtZDJKOThhbERyOXYvYTMvcXovcXcvNjgvNnMvNnNQK3ZQ +K3JQK05FK1BUOEdmK3plZ2ZyY2RPY0FqeTBKSkFaQk1nMjBVaUFDUm8yaEVBa3VnQ0NGeUlwRkE0 +eFdPOERYQ05NMXpQL2t6cHozQXk2Tm9ZT1ZEaXNJMEdqYnJrV0UvRWp6emxnRm1PQTJGaUdjK2dq +TFVJZzd5emNOa3JlR3RoRHJFRllkd05CQU9GNnp3d1E1VExyRUl6WjBvbVZJK1RVUStualNKYU1U +T0FiQUNuTy8yNEExUERneDQ4WW83OFpralBTQk9QTXRsdjNYeTROeG5jV09FNWVocU9qM3o1c2gr +dGVEWFBoeUl0M3RubldlRXBBZXBCZDhHRWNQbldwRk5YZXFXWi9zNXVleTN6c0NYK09JNWdBZ1NM +L0o1RzdLSC9PdzM3M0JUcTE3WWh5K2Y4V01QSE5aYmd3ZThPamdJM1JvYTNENE1UeG5SREY2ekRs +YTEreXkrZnhJOEJ5U081SVVOSG56WlIwTnkycXMrSEtwUlhIam94anI5VUM4ZThXTWVyendrUHQ3 +a05KYy8zSEw0eUttSDRzaUxLN215ZG4wNGQydnJ6L3F6L3F3L2NWUi8xcC8xWi8wcGpsRi8xcCsz +N3M5RG9ZU0tHQnN5SWJna01ZSG5TRkNZUFQ0ajNHV3Q1eEcwWmhLY056Vzdkb3BBa21ZZ25rSDlk +ZnFyWStvSGc4NVB6ZllHcUxkQjloaElzc2Q2NWtRaXNzVWxhTG5GazQvZ3hUUTB3NXNxZTEwT0RN +UWFEUFRZQUpvckpvemVEcWtoNUd1TXoxbVBWTmdRRzdGb2dPZStxd2ZKMWxpdndaNkx4VEE0ODJz +SjNnNDVOSEpBcUNNRzFXUWM0d1IyTmNraGhzL21yQkZiemU0eGhNOEVCcGM2aVJ0MmZaYUxPS3dU +aTJGODN0NFBMdGhoRUFNbU5ZZ1ZiY0FJRHo0aUxxYldWL2tJMVROWFRJd0wvY1dCZkN2ZTZRT2Q0 +ZHBlR2xKck9JSTdXQW5hRUVjT2hyVVdYclZaRzJ5NDBDKzU0Zk9tQ1MvcWNLakxod2NIUFF4aTVR +OGM2NFBmZDVqc0UxY01OY01XazZ1RlNhM0pPdk42UTZ2dzUvRER3eDQrZzluQlFHZnFXdDNNUGoy +WGorN011VWRiOVdmOVdYL1duL1ZuL1ZsLzFwLzFaLzM1VlB4NWVPZ0RVTUJLYkpPbVNPSzdJWmlF +aWlKMHhTQU1NUkd4eGpFS1FJanlYWHp6dmdQaEoyelBtUXdaOXVXbmJtczBBUG5NUWlDR2dna1px +UzkvOEMxTGpJdUlETGdZek1YWVNETTB3MC8vTUtnRE5tc05UVlVMQW1FZ0dnMkVZK3Nhck52b0lk +NWVOZVF6WEdLSkM0TTVEYkJlZkxIWG9DTVMzL0VxbC9vMDBETUdsZE4vc0k0UGMzZ25WdndSQTJ6 +bTVDTWlnbVkwR09RU1UzUFZLejgrcmRkTFBaVi9CVHd4N1NFUXVjVlJDNTV4YW8rNmlZSkI0ZGQv +bU1TMnh6emgyRXU4bmhHYjd3U25McHpvazczaXl3ZEhoT3V1N2h6Y3pLZFcrZFRydWZyb0FXWnh4 +TGMzT1hDdXZzY0hHdk9yWDcxNFNpL294bENENS83UWNKaklsYjdRaUZoeTBUVE53K3l1SHpuczdC +WFQ0QVd4OFpQRFVxM21jYVFHMzkzbG9nVjFXTXN2NnBQYkhWWjVjQ0ZPL25EQXR6cnJ6L3JUbnZx +ei9xdy82OC82cy82c1ArdFAvRHdGZng1QUVaZzdFcGJjS1JTQkd1ZTdvalFqalNka3lRRVZPRUxV +Y0c5d2dGQVF3VWl1Y1JvRHBHWm9ubWZpRXhuaVhjUkN0T0tKdzNBRVpvMTRZdmc5ZG8yMFYyNERk +cmtjSE40SXdhNXh5QkpQVXhGQlBQYnRtTGpXbVZjaklqVUttWWlMU1hMNHdBd0hIalJQdyswamZI +ZzlOMjlQRElwd3ZPRE5ldjlodWVlRXBUNUN4eVhlNE1BVjNQaExiblB5eVlHLzlFck1IRHdhYnc0 +SDFzcXBYam5QY1g5eVByeXB4U1dmZVNKVDkvY2FnVERGczA2dkNRZjJjQ1dIWjluSFVHSlo3eG04 +OGhBaXpjUkU2aFZYYnYxekVPSUxkL0Rod0NHaEIzaFNwKzlFS3g5Y2N1c2pIZ3lZY2VFNVB0emhs +TTlnRnJGcFRpeWNoay9ZZmJZdkduR25EZmlqZTlyY1B4QkdON0RobkNidDFSTjNHSENFTDVmOTdw +NkpZZDNtSEM3RU5lZ01ENWxYeTVwMERFb0g5V2Y5V1gvV24vVm4vUWxUL1ZsLzFwLzFKNXp5R2Jm +c3owTUFRZ0lJV0lzVm9Ha0tVaXlEV2djNFFzMFJndlVDYXhqd1luampvU2tJMFFqem11YXY0OFhX +RE9ReW42SVJBNVRDNWRBNFRWcFNKNy9HeXl0SDNnd3hvWCtxZWcrUHk5M2VFU3VtL0dJYWlJRkJQ +SFhBbExkSGFtSVFkYVNwNWh3Nm1vaEF2TVFRdWF2Qk90ZXVHVndhRTRINHpoQklobHRzZGNrRkk2 +SGgwSnNyTlhxanBVNDRyTi9EY09iRnNZK3h6TW0vaDhaZ2drT2pYZko2UmlCaTR0WmVzZXlGaGFE +ME5BZXJQc0dsMXZSYVRPdUl4M09tMGdzWGZuSHB3Rk1ITEdyVmYrdXMxOFBsWlhxNitBZVRYT0xT +Zzlpd3dpaW4ybWtESHpUaHMxeE1hYTE0YXZBZEozS0YwK2lWcVExekRFTm41dkNFaDdQK3UzMEdt +L2xvQWdiOVU2KzFPRkFUYmNGc1hwMDRUcyszUHpOb1VqNXJtUkNYMXF0dkQ1dTUyNnRHZXp3VEJ4 +KzRVb2U3dXRTUDN4dzh0S2QyTmFqZG50UmJmOWFmOVdmOVdYL1duL1ZuL1ZsLzFwKzM3czlEQWdM +MnhVYUJDVUp4eUpaTW9CaFJNRVFDaEVqRklFdHdoWHJ6Z1RDZkVXRy9lSXdEQkxEaVd1ZU5BbUVw +V0N4WTVIVlhuSDNiMkdrRVlmaUpuTWdKTFVRYWloRUxKdlAyRytwaFdoamsxaWh4REkyS2VheDN3 +ZVVuYy9nUmFyM240c0RsTzBHcVRjM3k0czVhL01teGZNMWhnM2hOVjRkbWVnNmpldnprNzE4NkU4 +Y2RuOFNEWTRLQ0FTNlh2T1ljTHZMZ09meG92bjVZQnp0T2NHYk9jNzJNUWZHTUM1d2xMdUhKalUr +bTBodkNZVVQxd0MrdW5EQ3AzVDZ4eFBjTUp2emdEQ2Q2SG9PNjVHQlF3c01EZzRxcEpweUlRWkJ5 +aU9zT2cvN29sZDdEbnl2MVdxdC9uc2tkazZoZFQyQ3hYMTVyZldjeWE5V2xEdlhva3plTFlvcnRW +MmZVRGgvK2NDMmU5YkF3dFA3cUZVM3JJUTdWcEY3eDdZZEQvdmhLcnp6RGhYb2Z4MWw5VEd3WXpP +VndTcS9yei9xei9xdy82OC82cy82c1ArMDM2cy82ODliOStmQTNvRWhWTEVCcGdLSWw4RHpOUWJo +RUVhdDFhL0JwUEtCRWdHQmtNQlNRTGlLd2xtQ1E1YTBJd1NvTWlRQW50NzNtNVNGWU9aRUZEMUkw +bVdEOWhHL0FUaGd1Qm8xeHhiUk96SkFNbzRHME5ORXpuK0ZoSGlTTERROFJpYVA1NHVCRERySFc5 +SE5vdWN2cGpsaENoMmx6enpxMTJBK2ZXdkhndndFUXp4c2l1ZFVxSjY0WlJPN2xZL2JiaHhOTjFN +eUltVG4wQUNiOThCYnUxYXRCYzZEQ0lxZjhlTXVoNUlDMHpsNmY1WlpYVEovbDBFdEN0VWNlOWNr +UHI1N0k0VzRmenN4NWc3ZGFHWHp3TXhwTzNlV2pDWE9FTFFlZVhPclVZekdzSlZKOTBUZjg2NWMv +S0NKYzlaaHp1Qk82WFBxQ2ExeFlCNmZodS9YYis0bXh2Wng4YW5mSSt0VVhPbGd2REdmcXg0bG5N +S2tUZGpua3c2bTkrcUlPZmFaakdvQmJITS9WR1QzaHpocTFPaFNzTTNCRlkzaDNUM3o0Y1ZGLzFw +LzFaLzFaZjlhZjh0V2Y5V2Y5V1g4K0ZYOGVFZ011QUdJVUQ1QUxvTjAwRy93a3IxRGkwamdnZ05R +Z3dWeWFTZVQyQXVpTmhYVVJoL1ZpeFJTYVJYaDVLeUlHRVNJcEFrSStUSVFuL2t2UHZtbWYyeStl +ZVlUNktaOUEvZlF2anFHeGlvUWJEbnZFTW1KUTJHQncxMkFFTW9uWTZpY0NKS3NubnpVR1JtdkVX +QzZtT2Jod09ZUzhDYkFIZitJSHY4TUxoMzdGZ3BnZFhIREZjSnFwRDlhRU80MlZVeXpQNGZDTU1Y +RHRtZjNpdWUvQk9uaUlDejl5NHNYdjZNTmd2Ky8yNGNvK1FvVVpmbmQ5V0NGUFBOcUlhVjFxTjA5 +YzhzT01hMzBSUzR6czAxZmZIUkxtOVJVZlBvdUZRM0hFZEZtTGY3aHhwbVo4TWhidHdLdytCbE9I +a1Q1di9OR1pQWHErOWMvSW9ZUkR4b0I5MTgzZGQxamwxa3RyWU1DdkdQTGdpR1pYYjhaZ2tadGU2 +RjMvNU1jRnJ1MFRCeGN4clJqVzRVcmY2TXhRdTVyMDNqNEd0ZDV6dUtQMStyUCtyRC9yei9xei9x +dy82MCtqL3F3L2pWdjI1NEVFR3hWREdCSkxSb2lDbTdjSjBRcEFxci9HSlY2RnJEaW1DRmQrVXZZ +VE1QQ01qQVJDMVd6ckNVRk1jUkFwcHFJQVIwSUVIMUZyRkV5SjRhZDVoY2dsaGdFYm9qelRwRFJP +UFJFM1lZb2RZaUlvKzJEVUpQbkVVaS9DNEJFTER3VHRHZnhxOWN4YXZPMzNhWjc2emlhZS82RTF2 +RWdXbHptc0l6TDVIRjRhcUo3SHdyRFg0YUdoY29zdk4veGkyYnVtbjdvWXlUcml3b1UxdU1GZkJL +RmU4WEFqaHhpWnN3Y0hMbXNZUSs5ODFsZlBjUUNYMnUyekIxN21reDhQQkdmb2tZdTQxSW9EeHNh +Si91S0JkbkNORzdYWUR5OWNkS2RtYXcxNTRDQllQZkU4dGNxVC9qTy96L2JqTk1iQ0VRMkk0OElQ +UERpRlE4NXdJeTRzY0RFYXp1RVRVeXg2MTN0WWFOSjNmeURrd01Hbk9tRVJGejV4OVFxbmFsUi90 +SDdxOEg2NUZVdWRNT29CYmRLV09QYlVuL1ZuL1ZsL1dsOS8xcC8xWi8xcDFKLzE1NjM3ODlBMFRm +REFwQ1JBQ2dndzRKSXFGQWpCaVBwQnZMTmU0d0JSakRseHZQMFJ4eHd5TkJzWXpRUGNYaURGOHhN +M1VCcVhZcEdMVUFQaFNGV0FOeXJXYVlRWUJxR29nOWc5aDhWQWhqaStPeXpnZ01mUWNFMlZBelpZ +NVJZTEh0anNSUkp4d3JTQ25IckZraHRuc0drR3JuTFpyNUgrdWp3aWd3dW5taUt2TjFwaXVjdGhy +VHplbHNsakQ2eDR4cW5tV2lldnkzT050bWROTzVkMWVNS3BOZXJadHpRakVIV3FCd1pyWUxiR2M3 +R1lER1pyQ1ZFdnpha2JGcUtGMS82dGR6NnJYeTNtOEdtOU91ekhwMzNpNnJ2Kzd2ejBYbjcxcU05 +YU1XR1dVeng5YzVEaERrYmZYVGtjMVd4dXVUYXVodkdIQW4zQWhnLzV3cEVhOVZrTW4rbFRMdk5p +NnFQdkRDZHVlRm1EanQ1b2lKbmg0QWVZNWJKZUxmVEgvSVk0OXN0bm4zN3lrZTg1ek5ib00vZ2hC +NVVheEpaRExoelduL1duV1BWbi9WbC8xcC8xWi8xWmY5YWZUOFdmQjBJRk5JbGt4V2trQWdSRk10 +RUlTQUNBS1laWXpQdnVjd1RGQkEvTm1tWkk3cWR0UlNwQU13bGNUbUR0SlZwRWlVdFVDckhIWlJB +SkF1RHl1K1VJUVE0Q05VY2N6K1IwUjZyaG9KQlhUc1dLcCtHWlU3UFlhaVU2OWExSkpxYTNHN0Fn +R0c2NUNCSS8vbnBiM1pxdFB2dUowTncyYWZhTHYzVlA4d21TZU56VjZ1NTMvVFVGWDdpUVc3TTBT +RU1aUzI2NDFTNVdSSVF6TVhBbUgrNFRRNzMyZXE0L1ltbTJQT2JGMUNkMWl5TXV6dUFWejFvbVZS +TSszZkhud0ZJZmJ2RUN1L3Bod29GRFNSdzlkNWhiWTY5WU9LV2Y5QWV1WUJIWFhWd2MrMnkvbmxx +dlI4d05IMnhpbXJNZUhrTnNuM0hxclkzZXlLTlArSVFWTC9Ram44OE1paWR4eGZSTUhXTHM0VFpy +SDNSK3Zjc2pIbjFiNHkyZmZPYjFRLzl5OEc5OWpEZTRHQTVlbC94eWloWGNZcWZmOXVzWmJzV3NQ +K3RQbk1GYmY5YWY5V2Y5V1gvV253WWU1SzAvNjArODNLSS9EdzF5S1dxTG1RM0EyUVFFNGRuRXBN +eXNHSUdzMHpockVBNWNDdlBUUGNFVHBiVkVhaCtnQUNMSEhwK0JGbDlUWUNBY1lOMDFXZVB0dFVk +K2Y5VVBPUEx0VFlNMFYweTRFV1JvTER6aUVUcE03aGx3SVZKZXNjeXZ3U2FuZW9oRGpRUm1uMGFy +R3paN0VJa2ZEU0o2OWRzSEQySEduREcvR0dxRjBYY0NkQmpKNFh2TTdYQVFUMzR4d29YdjRWdGNR +b1VOeHpBa2ovcmxKeENIaXZqNFlsTDc4R0V0SE9vVzN4QlBqeGdWNTlHQkdNd2hONTdWSVFhY2VQ +RGNjUENxQ1M5d2lLRU9ZaE5YRGZMaE1mWGdFMzVyY1JGRDJpT0dXdkVKa3hqV21iTTJmY1paY3Vi +d1ZCUDkwUzFlMDJ1Y3FFRnRleGhQcjczbDhRWVBUOWFwVFIzaVdDOFBIRERRcVQyZWVTUHB3cjBM +QmxqeFE2dnFFZ3QzK0ZhYk5lR1QvdkVycC8zeTVTRENDd3oxWi8wcHZsRi8xcC8xWi8xWmY5YWZS +djFaZjk2NlB3Ly9vMm1BSUliQUZPSW5abVFpbitFTVJTSElKWmhFaEtCUVFNemJSNFFJOUZPNkpv +b05vT0o4ejRIZ3M4S3RGd3RZc2IzeDhLWWhacklXZ2Q0SytZK2JmVWRxREtwb2ErVVVqeWdNRFdW +czM5WGxUWXMzQUJseXdZWHNHQUV4Q0xKZUkyR0JUVzc1R0hMNW1mWHlhbjVFcDA3N0NTWWlGd00z +dU1DeldqVk9mZWFaUm42NXhUUHdDWE9NWng5dWZTY2lQR2syUG9uVldudGdVT01LZDBSRUFFd25Q +L1BBalhzOEVaQmUrYXlXSEF6cEtWNW9BNStleXdPTHRmYkFJcWRjOHRPSVd2UUZCK0VvM0VkWHk5 +WE1xMWNkZStqTlphMGE4UldSeW1PTlBxclZjM1hEYU03RkVLdWZ3U3cyWHZYTXBkZjJ3aWwrZWsw +L2VGQ0Q0ZENIV3kvbHd4ZmQ0RWMrbC8zOElSY2Nlb2RUQjQwNUZ3elcwS0w5OFFzTTRWcGR0S0Uv +L2tCVXI3MHd5aTIrbXZDbFIvVm4vYW1XK3JQK3JEL3J6K1dxL3F3L1o5U2Y5ZWV0KzNQL0cxQS9T +UVB0cCttUVM0ZzJwQWlKRlpnbUFXSE40M1ZJQjBTemdmUFRzd1loTHVDSVdPTVJSVHdPQUxHSkV3 +WVhRZnBKWHp3RFFRcDIrV2VkWVhoc1VNMWpBT3M5SXlwRFhBMFRFdzU0a0pxaEJ2WGJpMGo3aVVX +OEVFYWNoTzZ2b21GVnIzWHFoVmxUbU1ZYWpkVmt4SXNCaHdiWWgyTWNHRGdpWm12VFJMaGpVTTBS +RTFkcWRhVkg1dURDQ1Q3aHM0NW9ZUkozR3oyR0lZeHdEci8reVNPL0EwK2ZIVHpxSnd6OWlPbXQ5 +UngrWEtoYlgrekZteHllaHhQNzFRRW5EdTIxVmgwMEJZT2UyUXU3M0RTdzg3TmVQbUtYQTMrd3FK +dlJmRmFmV3NLVHZURW9zWnV6eG41NzVOUnJQT0JJejJEeVhJem94N3pQZEtVV09OVEd3TUZONzdB +d0ZuN2t4SXYvajZzWTJZVzNtQ3U2a3RkejNOQ0RudWk3WjRhZTJldU9CM2djQURETFczL1duK3F2 +UCt2UCtyUCtyRC9yei9xei9ud3Evano4RHpBMkk1bklCRU1pY1NOUVVnUklpaFNYTllBZ1F3S2ZG +WGdhNEg2TDk1TTNJaGxTWThRUlh5TVVDQkJnR2dKNEd1K3l4ajVtaHdWeGl2WFBWUHZKbWNDWXpy +Q1hlQkZpWFF3S013TGxVYU1tcUNsRGJ1c2pQSFBxZ1FlQmNDQVRXWEtxMFI3WXJXTktjeHBKcFBL +dVdJZGtoNE80NHVmd1VRZHUxRWJJT0ZLSGUvZ3dDTTJlRmNmZ011ZHRoeGh3NFZRc24rMlJ4M28x +K28rKzdaTmZQMTB3TzV6TXk2WG5EZ2FmSFlSeUVJVzF4QzZ1SHNOdXJacmtrUk1HejhSVXYrYzRn +dGw2R3NHYk94eTRXWnh6ZDNqSVNTZHJnTmxyenZkb3hGNzhpNmt1YS9TWE9kUW9Ceno0cHdkMXdp +eS8vcWpGWHA4LysvemIxMUJ5cTRjMnhhQmRkZE8yZVQyQUIxNDRQQmVIVnVGUnI5N0NFSjRjVGc0 +OTY4MkxhZDZjL1dLcVI2K2lCZHhaQTZ1YUY5dlVKNFo2VXJ2bmFseGRUbXpZNnMvNjA5cjZzLzZz +UCt0UFdPdzFWMy9XbjJxc1ArdlBXL1BuSWJFUHhMN05ISkNhNXlkaUJDZ3VCbElnc1NDSnlBQUJ6 +SGVBeGRFRUFwV01ZVHhIanVmRUxMRzNBOXU4QWVjaVdya1ZLcjQ0WWlJTklUREFRbnordzFnNFB2 +UFJ0MjBNYjdVWTBSNjQ3WTlCa1dFOU1ibkVjR1VneFhxMSt5d3U0bFlnZzlVZWVCRXF0b2FKdlNL +OUNvRklyZkZaTEpqVW4zb1lFVS9pdWRScVhUaUlPQndPT1hEVUl4ZEQ2SU00Sy9qQkl4WThlSFgz +ekR5aDQ4QkJZbzBhSElnNEVKZkI5Y042TmVvYkhONk95QUd2SGhPSHVPcXpSbXhpVjUvYXJkVlBN +WmFQK1U2RStGdXNzMDlkRGtOMysvQmpQUXgwazM3cXZWeE1vNzY4TmNLZit0UXZyalhxTVdCSUgr +Q0RtUWE4cWZPY2J1eTMxMy9RYndRbmJPS1p3emxNNnNhTGZkNUV2ZmpNRzdmWHNOSHdIbWl6ejd3 +YzhZTDh0R1V2SG5kK2NCcnEwWGM4eWMyZzFqR3NmamhROEd3ZFBQdUh5SENDYXoyVHd6NTU1YWsv +NjgvNnMvNnNQK3ZQK3JQK1ZKTlJmOWFmUm5EQ0p0NHQrZk1BWEtHSU1SR2pMZUVESXFJQ1hxQTF3 +cENNVFBzUXJla0VBaENocFFBSkZKb0NDYzkzY1loRzdCVGdFZ2NoY2dEc3M3Z3dFWjZtYTZSaUdY +UVBsV21Hb3VGSHVqMklONWdiSG9VVHJYM2laQkFRWFBMNTdBQllnVXpUWVBOWkxNU0xiWTN2NHFs +N2hUdnJDTkRueFQzMTRSSDU4bWtVampWYkxiaTAzMFVveEdBT0xuV3UyQ2FmT1BiSlJ3Q2E2MkNB +UVMzbXJjTUIzc1BYR21EbThFa0U3dFo0UXlTR3Zvb2hwc3RiRG50d1NJRHU5cW5GRlpHcE0zWERM +QWE4K2h3TnlDOEgzaHc4NXRRTUQrd09RSnF4MTRIdG1YbXg5VWx2emFjdTh3eXVIOUdKbVBiREJZ +ZTk1bUhHNHg0TWc4VisvejJGdnNEcG1YcHdMaFp1Y1ladmVWMzZ5SFF3MElVWWFyRVhWM1FkdmFu +Vkd5aTQ0YkhIZnZIRXB4ZTZQM3Q2L3N0d3NKOEh6ZjFxMCtHdVJ2enBpeGo0a0FNMldPdlArclAr +ckQvcnovcXovcXcvN2FrLzYwLzduNEkvRC84VDRINDZWWkNGaXZXY0dQeEVLNmhBNWpRVVlPTEpY +a1FCelh3S1VKeUdTcVJZRFpGY0VjeHRIdm51akV2UTFvbVBGRGhnaUZqbDFIUy85b0JFYnhzVTZG +Y1lGQlFTWWJEZjBEUzVHQVZHaEsxQnQ1N3ovODhHa1pxRlBHTDFlUStTd2UyemZVZ1Z3d1dUZllp +TlFUVWhoRHJJa0srWmNtdWcyQVJnSGw0eGlNdWNtSGlCUXl3TlZwZlk4b3R0SDI3RXdZVjVmV0Vv +bjhNbERIaFlvVXdlT0Z6d2VoT2tIOFJqamMrZXcrVXp2SERneklVcnZYREppUlBjd3NRY01NRXJs +K2VwU1ovRTlkZnUxb29McS9rOUxDZXVQakVyTE5HY2ZSdC9hdFJ6Yzc2clh3eVkxQ0ltL0xSbmpX +ZjZ5YVR5eUN1V2ZQb0pIL3o0ZFhqa3NNQzdPZnRvZGcrRDRkSWVhMmg5T1I0c01NaWJBOHdlOFhF +cVgzeWdCN0I0SmgrZVl0Qm9VeDMwNUM2Zkd2UldmbXZFendHUFJ6elhuL1ZuL1ZsLzFwLzFaLzFa +ZjlwdjRLRCtyRC9odjFWL0hoSzVRb3lmZHRNMFpHNnpSelFDYXBaNUFmTW1RR0RyL0lRTEVDTkdO +Rmxqbjk5SlhrRlBFUWkwWG9NOUEwcWpDSHB6VEFNSUFJWUFCZDdGbklqeHhrUHVDREYxMkx1SHln +eUNnMGR1Y3dUa1AwcEdzQ0ZIVEFJcjA2c3Y1TW1uVnV1dHRWOGo0QkVQZnZpSUYzZHFzamR2WnpS +SnJXcldjRHc1VE9DR1NkUGxWck8xdUZDUHVQTHJpV2U0MUJNTjFsQk5kNG1yVm9MU3B4aEJYS0tB +R3k0MXFBMFdZcEpmdkx6WlVIdk1Iak40cHA5eWlxM08xQjV0eUFlRGZOYTd4UFFjNzNvQmcrL1Jo +MmRpbW9kRjN4MHMrMFpwNHNnQnN6M3FsOWNhSE85YnhJbVBENWY5bmhueVdHdVBYcW1IOFdDM1o3 +Rk5idno0SFA2TlBVam40TWFqUFhLclQ2MTR4cmw5RGw5YXhhMTEzaERoQWc0YzQ4L3dMTng1cHVm +bTFiWUh5blhBcUw5Nm56MDViTFlmdzYyODI5UDZzLzZjblBWbi9WbC8xcC8xWi8xWmY5YWZ0KzdQ +UXhEQWdaTmNNaHMwemlaZ0pTQWF6eVQwM0Jwa0lNcmRQcUFCVVJpU1hMdG5Fcmt6akRpQTJJZFFR +RFJBRG5FUmd6aWsrbTZOSWdqS3hWQnlNYW41TGRUQk1qVXdBVlBKWjlpclBzOWhaT3I4M3JRQkh5 +eHkycGVHd2dLWFBVUk50SElTTnBLRFVTMGFDQjllekc5RGhtQ0Nzb2FwaVRNTk11d25FTi9GdHc4 +L2hHREFMelo4bmhHdytRak1jMnRnOVIzZkVTNU9tTjZocWc3NDlNcXZYeVNIdXZCb0g3RVNhSEl5 +cDM3Z3hSeis1SGQ1SmwvNnBHNzZVSVA4NW5EdE9jUEI2VzBITHZGbm5kenExa3ZQNUxQUFd4WDFt +WVBEblB6d1d5T1BPSENJcjNiODZyMURUYTNXTVJwdTdkUFBySTNtYUJ3TzV0SVgrR0QxTDM3aFIw +NDV0cGVEUTExNHd0c2VCZ3hFcTFPL0dOR0J2ZTZ3ZUtZK09lWFBldldKUVUvMG9XNzE0dGgrT2Rj +TG94ODZzcmYrckQvcnovclR2ZjZzUCtWV2QvMVpmOWFmOVdmV1JuTzM1cy9EUm91QXRWa1JGZ1Bs +K1JJd0EwR0tCNVRZRkJKUldDODVVUFpJckJFU0VqN3phNm9Za2xxdkFaNzdqaVRrV21POUhKNERE +aE93ek9YM254bFRmS1RDYU1TZ01NR21JWVlEZ1dCYzlzaHJQeElOQW9JUkZuTml5cjBFaldEWFlO +Tk1CUHNNbjdVK3E5TWJBK3ZrWDhGTXpZaUgyVjJEaU1SNjJPUXpyUE9HUkc3bVViTkxENHcweDd3 +OU9GYXJIQThpbnJ6NDA0T1lGYjg0RTJ2SDFPbTUrb2syUWxPSFdsTWZROHVwRHVJaFl0aHhHWVBh +WjcxNDhzc2huNHVHeElCTlhMSHNWU2ZCT1NqVE0vblc0SE9KSzVlNDlzTW5uNzZwR1FhMWVhdUNU +eHJCa1p6TWFrMEV6VGpXT1hod2l6ZS9CbUVQekxoMDRRczJXcGJmWHV2VkEyUCtPd2w2Q0E0OVYv +OXlNNzIxUnd6eDdkTUh1QjBLY09DQlh0VktJOVk3cU5XNSswZFBZdU5BLy9HSlEvalV5US82ajcv +NnMvNnNQK3RQV01Tb1ArdFArVjMxWi8xWmY5YWZNT1BTZFd2K1BKQmdFUkFDQVNTUXhReG5vYVR1 +aWdKQ01BUWdTdkVST0VEdUVSRUFpaFhmWHM4VmFrNGVNY3dEdkFhZHVMNXJLQ0xsVjVEUEN2WTkv +NzgraUZ3aGpnZ1ZwUmpmdldudzJRaG01Q0ZVWG5IRU5OSTQ1SWxKUURCcXVtWm9XSnBNZE9LcUQy +NllyQ0VnWk10bEx5d3dhYjZZQkdNZkRPN0lsMU1kNGxpakIybTB2Y1NYQnNHTUU1K0pVMTZ4elB1 +T1J5TFFGNS9GbHR0Zm84c0ZremRBZmtWQmp1VDBxeDRPalJ4cVlxalQzUVVUWTh1Zmd3Q085RDhI +dE42TDYzZkxmWmJQZXZsZ01jU2xLVGlXaC9rc1BpeHlXZWU3dXpxdGw4ZGFiMTNVNGZCd3B4OTFt +cmVlWmwzbTlNTXoraEZYWHoyRGt6Ymx3QzM4dExTR213UENJVW9MT1BVWi9tZ1R4M0twelhyYW9C +ODZjOWlMYXkwTzdJZERmZUxULythWTlUaW5IZm5naGRGNisvRnNQM3hxTjA5WGVWWi8xcDlpcU5Q +ZEJWUDlXWCthcnovclQ5L2QxV205UFBWbi9hbk8rclArL1AvVm40ZEpJZ1FtRFJYSVltTGVabzlv +aUZCU1lOMkJrRUJoTHNHQVRtSEFLUXJCd01taldLSkhFQUNJMDVBWVZFeDdHZDk2SWxLUTc4aUF5 +elBFRXdDTUJ2TVFVZ1NuZ1laOURDS2Z1dUJUa3lGbnpPWUFzVVpPOWFZSkdzVzBhdGRZMkt5eFZp +MUVvejczSENweWVsT2hUcHpJb1g0bWtFdGVJdkxYMitMQUpEL2N1QTBYdUNWUWwxaHl1M3kySHg2 +NUNOWTlSbkdaOHlzSzZzQXY3aUltTVdEeUwyakJKTDc4QksxbWQ2THlITC82S0tmYTVKVkhISG53 +SE9PWlYwUHdlT3VDbytWNkJDY2U4YXRacjlJVHRZcnJ3bys3WjJKWWh4TjE0Tm5Bc3puOHlKMWZ4 +YUJkZjVEQW9DNTQ3S2NmbVBRQVp0L2xwbmthMUR0WTdUT3ZIOWFLN1lJSkJoemdoUmZrb2dOMXk0 +RVQ4M2lISlZxVVJ5ME9iWmpzOFYwOWVGYS8vdUE1bk5MT0duUTBqUXQ3NnMvNnMvNnNQK3ZQK2xQ +TmVwV2VxRlZjVi8xWmY5YWY5YWZjdCtMUGd5RDlVOHErU0l4SXdwUXdmMTNyYllQQUVUSmc1Z1dY +UkF3QThqWWtodlVjbVpLN0VKSTlMckY5RjE4UkdpNHVMUFlqVVNHYTRxL1lGVWpZOFBvT2k2RjVT +TEV2alRjWXkzZDUxSVEwYjR3TVloUXZod1RSK3E0WmNycGdVYXUxTUZnbmhscGlScm5OMmFlaDlt +Z2tQTVN0RG9jQ28rT1dvT3dsVm5OeU9uQmNSR2RlYnFLU3p4N3IxYUErZHpubDl0eTh6OHdBbDMx +aUVCTEJxQXVQTHYweEQ2UC9Wa0RQek1zdEo3SENUaVN3d2ErbTZJRVE5UllIOXFWZk9CTER3UzIr +UHNzRjM0NHhEcHdPVjRLM1J3eDlJbWgzZkJHcW1KN2hVNzN3dU9oUFRYQ3FsYlp5QU5DUEhQQ3Jr +MDdOeTJNUC9IaFdrLzNxY3NCN3pnd09ML2preHpsZTNGZFBzeGJmY01GaFBUMVlvMlkxeW1VdEhm +QlFET2V5aDdicDBCN2Y3VmVQUGRiU1pBNkQ1TkE3bkloZGY5YWZjdGFmOVdmMnVPclArclArckQv +cnovcnpWdjE1S0NiL3A3Y2FneXlGYWNCamtpV0ptSkNnRVVBTFlrOE1palFBdHRoSnFsa1JBc05v +TXZJVkpjYWE2U29tV094YlVjNXpEVmV3ZmY0aldybGZldlpOK3hPOHcyTWJNQU14Q0ZDd1p4dHpo +bUxoazVPd0VXYXZRUVFhWmo3UDViTk9yV29RRTNuV3d1c3pEUGJCVHF5YXAwYjdpRTUrQk1jd01V +Z2FKaTQ4bnVGRnpTNXh4U0UydUJNVEZ4cEhNTDY3OUVTdFBzdkgyUEI0Um5RNEk5Z1Z4Z2hNTG9l +YXR4L1d3ZWJReU5zUUdBbUd3UFhCMndsek1PRkFmQmhneWR1NEhEQTBZSTN2RGlHeFlnajRGc3ZV +cE9kTW9lYm9pRDY4c2NHdHZvdm5jdzVyei9TV0xyMVZjZkRrSUZTRCtKN2gyeUVscjMvR0hKKytx +MTN2WVBZV1NMd2MyakdvZnBoVEc0NzFXQjNSakQxeXVxeFZqN3M2OVJ1ZmN0bUxPM2c4aHdHSE1T +VE02c1l6dkdwU296VXUrZDMzSUpyZXhUZmkxSi8xWi8xWmY5YWY5V2Y5V1gvV24vWG5VL0huNGFH +M0JacWJUWUFCZ0VSQUpkQThpVFZVRVVBQnBISFdLQVFvb3BYQWR6RjhKdzVrS1VZaDdzaHhseThr +TXJoMTRzc0ZtMktzZzlHY3c4U2N0eEthcEhFNVBEU1llTVUya0FsSG5zTU9ZK2JFRTB2RFZqUmpJ +SGpnVjdQbjhuaXJvRVlHOWl3MXdJd1hkL0hsZ3NrUVIweGkzdnBubjl6dzR0ZWJMckdKUTUwd0Vx +UERhZzA2eHBjVHZ6SEhpbkFFYkwvdjV1WE5sWG1pVjUvK3FJbmd2RkdUWHl5OVpscDVjUzNXOW5q +V3U4dXQxcGlKUWRYbUlsUll6U1d2MnJ4bFlYclBjU092WE9yQkp4MGtwajdUbGo3aEF4N1B4WkVi +L28zaDBCcHU4WDhhOUxMQ2xkTmQvVHNteC9aZ0RtYTE0dDdCNVJBM2h4TUhpUGc0b2t0WVlSTGJI +ajJGQTI1OE1ESXNjT0JGWHgrYkN4ZjQxRi83Y0tKdkJsMnBoOTdzdFVkTXo5WG13SUlMUDliQlkz +L1c0eXVjMTUvMVovMVpmOEttMXZxei9xdy82MCtqL3F3L2I5MmZoMklsc2tnZ3pVZmNFajhCaVQr +Q3RjRTZva2FHNGdDM1J3eUNzNWRvSlRhSE9JMUJBS0xzVjVCMUNGcEREMkRpdEVkY0pBRXJKbEtR +NUhlNmlTcHZBTVNVVitQZzloTzRCb2podVFHN09CcE5XTVRHRUg2S0oyQXhQSXZRa1FTWEJoQ0Er +czBqSG1IaXFzazZuejNId1pwMDFtc0MvUGhTSnl3NGcxa2o0ZEFZK1hCbERaUElnNU0xNk93Vmww +RFNoNjF0MXNpaERybmx3cWRMVG12TXJVR25WM2l4WGg0Q1ZDdGVyUk9UTVluVWYxeE56UGlRSDVk +eUw2N2gweDZDaDA4UGlWWXQ0dmhzamg2OGNaRURyanlENVJ4M3k1dURDaDZjV3Njc2FoWExYampW +REhzT09JTFdSNWdNTlltbEgzc0FHTU9wSHFoRFRaNmJoOTNoRDRmY01HV2ZmM0ZPL2oyb3AwNzVQ +S2N0T3ROanZiVmViajF6cDBjWEREaXhCeS91bnVOZURMbG9EOTdrb0lVMTRPQjFpV0VObk9MN3Ju +NllyYk8rL3F3LzY4LzZzLzZzUCt2UCtyUCtyRDg5ZnlyK1BEU2VrQlJuUWpLTEpkd0VZekFFSTVM +QUFMZGVrNUFpb01DU0lJSGhDRVdERmFmcGNsaG52WDM3ZlVpejltemkvVFpiVVhESVM5U0tJbGp4 +L1g0MTR0eXQwd2dFYlg5R1ZFUkhxTWlKTUJndEI4cGpJV1VRQlN4cUpoaUVxTjArWkFlcnoybXNI +TmJKalhoRXd1NHVqa3VUeE5udkl6N0NDeWNFWnc2UGNxclZaeml0d1lYOEczZWE2M0NTanluaGtF +dE0rK3hYdHpuUDhJUlR1SERpc3dQSjJ4c1lJa1F4UGZjdmxabXpUNC9sZ3kvNzFxRFRjM1hMb1dZ +WWNJQkxQUlZQWDd6eGdrY3Q1dURSYjVwU2s3NDZET1NqRmViVEoxak4wWlEzUjJLclhTNDEwaDAr +WTFDbXNsNWV1ZUNtRWRqbDExKzRjWW4vblJzTk81RGs5Vnk5Y0lnSkg4MTRnd1N6ZGY0d3NKWkI1 +WkhUSHJqZ2RZRElaVDJOd0tHbm51TlBiUzc5eHJWKytvNFgvZlBkSlQrc2NMcTdQSk1qdm5IVm4v +Vm4vVmwvMXAvMVovMVpmOWFmOWVkVDhlZmhyM0ZkbWlDQjV2aUpITGsyUnV5S0pHaUpOTnQ2NUFH +UEhHRDkrZ0Nna2xnbmhuZ2E2UG5HbkhXYW9ibTVrQ2czY2dnVkdjbGhFQ0dNMWhLVkdJeXFVUVpT +eExSZURLVGtPWEZyZ09JMTNMNE16d2hQVGpqdHQ1Y2dZSTBCeE5Fb2x6WFdXMmROekl0NE1RaU1P +YXhWaHp2ZThPaHVQZHpxRXh1MmlGb01nc01GRHZGS1VPWmcwWmZsZXZxaStmRExBWXU4c01HaStZ +YWNlRk16bytIQ1h1dHg0VUFsMVBPZ3U5dTlhdElyQmlVK1FvM3h4SWZMWFErMjNxbEJERzljNEpG +RGJIbGlMaWF3SndlQ2VWeW94ZEFEOWRpckR1SzAzM2NtTVZLVFBmRFJuWDJHK09rUlVYc3VqdjNF +Ymc5ZDBNZ2Vjdk5kN2ZqeEhTWnI0ZmZaV3l5MTA3eVk2cFRQV25sZ29tOGNxRXV1MWR6bzJKemE4 +SzBtV09oQkQ4VTg2N25mWi9iWWo5LzBVKzM2SDYvVW4vVm4vVmwvMXAvMVovMVpmOWFmOWVkVDh1 +ZmhwL0tBOWRlbVRLQlJRRWtpZ0lGc29JaFRFWUFURElGWTY3bWZyQUVTUEdETStmMXNJdEJjRFkw +aDVOd21UVjROV0NNTndmYkNCTGdCcUovOEZaUlkzalpvbEFZUUFRRWhpdkJqWFBnSVREN0ZpKy9O +RWpHNjVMSkhmTGdTWXczNnRmUDMvOVgzWU5hWkV6c0hERTdNTVpFTEhuVVpzRnJyMG54ejh0bURJ +M1VRb2NNTHIrSnY4NmJKUkpDWVJDS2ZLNmJGUFk3RTlubEZNdmpGZzF2ZHdjQTQzcmJCSzA4T0N6 +VXpyLy80ZlhuMjEveXpWejUxNGlyWTNXRldtM3g0OGxudFl0RUozck5XUEZqMFp2ODZmbXJDbzRO +QzM4ekJUMmVFS2paVHFFRThISW5oZ2tjYytuREphNzExT0RVOEoycmloaVg2Y3M4QnVxWVpMZUJZ +RGZMaDBsN2F3bzllNENNbVY1ODR6SW5MYUUwZSsra1FSdG9TVXkwT1dYa2NRdDQ2bllmZitZY0JU +czlEZUNpWldOR2NHTzR3aU9HaUFmanJ6L3B6ZWE0LzY4LzZzLzZzUC9kNy9WbC8xcCszNzg5RDQv +MTBtbUtSWnlGQ1l3WmlScHJpQldOUWdCVmduOCthSXc0QUN2QVRNUklWU0JqaWVPNDdjU2pJWHVU +dFQ4UWpqcGdUSmdTSTZhZC9na0d3WjJLSjYyMkRHSDdhdGwreDFpSEZad05tMzJHV1N6eHhERVNM +aHpoNDNPVWxNRGtqRW8xQ3BydDErRUcrM01GTlJKb0lPOTQwWGZOOVZ5OHV3cEU1V0J4QTRkNGVh +N2Q1STJqWW1GTk16K0MwM2pwNXhYS1h3enJOeFM5OG0zdndHUGp3cXdOTUlUZnNPRmFYM09yWVh3 +dFl2azRlOVVNY0l2T2NTT2pCWjNseDd3NmJ5eHlPYWNJKzlhb2xCaFdUZnZEbmNIUXRqM1BSZzNu +OGVJTURyOWdPR1BnY1BOYmdXRDhjVU9Mb2xYcDkzakdjV2VjN3pacTNWMzE2R2U3aHdxMGU0eFJX +ZkRHbW5zQmhqNXJ0aHdOWGFuVDV6dUJNajBmOXdMODdQS3VGNllrWUxsckRBOE14cDdkdWFvQlhY +Nk1wdWFKUnh0eHJjS1pQOVdmOVdYL1duL1ZuL1ZsLzFwLzFaLzM1VlB4NUlBVzU3Z0lKYUpOSlAv +VnJndVlCdDAyYmRSSUJES3lnQ2pmbnVmVklKd294ZkY4alRFRklWRUNLMW5RTlgxRU9LTVFRRzBJ +VmFCMkM1U0FreE9mUUlJb1QyNkFia3BFdEhpR2ZncnNzZVhLTGs4WnFoam9kTHVvV084MU9mWVNG +YkllQlBZYjRpUU9iZFVpRUd3OElsMWRPZU5SbnZlYmw0TkdJTmRCd2dGdGNXOE5zeExFR25ZRnJ0 +Zmx1algyTTQxbWV5Kyt6blBEanptZm1KR1k5Z3owRzFWTllDRlZkeE9NdVA4eDZJSy9QT0xGT2Iv +UkQ3WExFbUdLcFR5NWkxeE52b2ZCaXJiajI2eDF6NGhzKytzQXhYRGpCTVE3VnJwOTV1d08vTldM +QVpBMTg0dURSSEE3a3p0QXp0ZWp2OGpMOHBOZSt5N2Y3aG52NFlaYmZ2c1V6RjJ5d2lPUEN1WHg0 +OEpsbTRMRUhMeTZjdXNRVWo4YmhGb2NwclZFUHZ1bE4vdFhMY0lkRHZNcUJNekhveVJzMWF4eFM2 +cWcvNjgvNnMvNnNQK3ZQK3JQK2REZnF6L3J6MXYyNS93b3VFUk9KeEFvVFZCS2tDV0pJZ2p6Qi9K +U1BJRC90V2lzd0FCcmtwMmZCL1hXMFJpTkhjbkdCVjRSbkFHbzR3UExhUjR6aTJjZG9TRFU4MDF3 +a2VhT2llREZYV0RPUVpvOGNpQ2RvQTBicjFHQmVFeGlEYUpCQUVPcTJUN01kQWdoaklzMlF4K1U3 +WTZaWmlMZVBjTlp3azhmekZlMThKZ3ExMlNNdm9zWFJTRHlhSXdaN2NuaTVjTUpZK0NCMDYrRUpQ +bnpqRUg4eGpQN0pRVkRXbThjSGM2aVhjZndxZ3Z6V1JUekVpNnZzd1lmZXFFc2ZySUVCOStyUVkv +bXM5UjEyZU9VVi84Vm4zcmhyN0ZFYmZPcHdXT05JSEgvOVQxTjBCSnQ2ek90cjlLTmZubHNEQjc1 +d3VoaG5xRGNhZ1NHRHNQR3ZoOWJDQ285ZUI3L1k2dlQ5RSs5Ny9lcEhENzJka2t0ZjhTTTJ6dUZR +aHp6aTRWVXRjbGxuUFQxbWoxZzVEUEFDaXprMXFpZHZOV2xyTlRONDR5bnIxY1lIY3VpZnZmVm4v +Ym5hcnovcnorR3QvcXcvNjgvNjA0Q3gvcXcvZy84Vy9YbjRINzhqckNpQmdFZUFUWVMxQnAyaWlJ +N3dpWnI1Z05VTWE0Q3lIbURKeGRJSTVndWhoS0NoRWJPOVNIRUhSbkhKYVQ5aWlOSkFMTU9hajNH +SklQT0trZ2QydGNobk9DZzBIZ2tJaE4yODlXb1N4ejV4ekRHdmRRaUN3VDc4d0tkMnpiQmVUTGhq +VUkyMlQ0MCtpNDgzVGRZUXNUWEFIcUtGMDRHRE4wWVJWOTB4cUxqaTRJQ1E3WWZWTTN2a0VSZS9o +Q01YZk9JVGxkNklZUS96RUtENDl1UFNvWUJMaG9DUk1kVmhMMnpxenNGbW5UNTVybTlxVXlzYytD +VjhSdmU3K09ZOWh4ZW5hc1d6UzI1dnF0U3FKamtJbmE3c3diMURRWjVvUWYzV3dMY0d1SnFRenR6 +VGZ5YUV5M2Vtc0JZdmNzbXRSbnF4TCtzY0tQSWE2cU1adllaTFhuWGxzTUt6bUE0TDJPQ3dqZzV3 +YkE3My9JTUR2Tm92bnpyd1pEMmVvaHM5NGdWNXpWbXY5ajFjaHhNWHpQVm4vVmwvMXAvMVovMVpm +OWFmOVdmOWFlMVQ4ZWRoUWxGQVNLejVFdVk1SWd5RkNhWm9pWUJST0JESUowREFKRldJei9ZcU5r +S1Rnd2lCOUZrOGdtY0FnQlRMZ0lnVDV4VHNaV01qUXo3enpKL2Z0VFpnZ1ZrK2pVYUs0YkFnVU91 +dGhTRnpHb2NBR01SbkdBY1ByTEFRaFlZUnRpRysyQnEydkV3ajBpeHJjYUNoUGhNbXJDNnh4U1Er +aldKQTljdUhhd2FGUVI2aUlqWng4WU1iZUNQNFBlQndPTnpJNDdzWUdtdXRaN2pUWkQyUVV4L3NK +d0pZY0tCblBxdFBmSmM2eEJNTEhnYVZFMjlxVDY5Z3czTU9kSEh4cHA1ZGM5V0VlcmJlNFpsMjhH +WWRQUEtaMXg5RExIMkZCd2R5V0FjL1hQa1ZGdXZWNjhEQm9mN3BsYUZlT2NUQkQ4ejZaQis4ZENB +ZWp2VEptN05UMi9mYkEvWG1ra011WE9NSlB2bndxaWI5VlFNY2VxdjM5dVJnRDk5cXhLRTNZTDd6 +anBoNEUwYzhPT0Z4ZDhpR014ellYMy9Xbnk1MTFKLzFaLzFaZjhwbnZ2NnNQK3ZQK2hQZVcvWG5J +WUVQa2dDdkljUm5jWDZpTlFRRlNvTTFBd25XTUJWUktCNWg5aUpMa3lRRjNCcUZBMnFmbUQ2TGh5 +UUZLMHhURUF5RHdqd3pGQ2N2ck9LYTk3WkJmSU1BekN0VURQT0dZbjJQMEsyQjJkQllkUk9WK014 +SFFKcXNWbHlZc3lhR0UwczlHb3BvTVdJR0hNZ0xzLzFxbE5jRnQ2YjU3REJ5RjBzT0hCT21DLzdG +Tm12c1o1YlVMclljOXNoSlFOYmp5VHhoMndjZlBzemp5bjU5SVNhMTRFRXV0ZWtKYkM2MXdBNGJE +c1MweG1kWWZNWTNFZXFadDBrK3crVjN2OFYyN2NFeVhNbExaQXlBRDBMMysvcE1JNDc1VTF0M2l4 +MVdPZW5RSGpqbHhyM2hMcWYrMFpyYXJWZXI0YTRPK2hFWExybmt4aVBOT3FqTjRVOFBveCtmOFNZ +ZkhmakRBQTdteG9kNlhmU2sxK3FHd3pxbTAxdjUwbmZQYVY1K21PeFRvM1hwRDY3Vmd4ZXhZSUVU +TnZYcmgvcnN4MnY5V1gvV24vVm4vVmwvaW1QKzFGYjlLUllzOVdmOVdYL2Vsai8zQjFEQkJORlV4 +VE9aRFpwNnZxVzVYMkVnRldDSkpkU0liZUtBODlmZXdFWmt3QkNocEM1NVlsREY1eENRVjhFS0Yw +TnNSQ21TK0RSUUhpSkRzQnd3RVFYU0RBVmJTOERFRnhPS0NZTTV6OFRObklhTENaZGExT3JYRjlR +dEhvenFWWitmMkQzUG9XT2ZQV0xBTHE4Y3lENHhuYUxURFB0eDZqcHpuMitJMUlKdm9pYUtOQy9O +d1E4dTVjTTV3ZnFNUDQxMUpRZE9UckZmZGcraE1JcWExR0JlSGpYQkFDY3UxQU4zZW9Zdk9Ud3pU +eURXaTgzTTFqa0V4SWFkQVdOUTgyck13V0UvZ2EvQmhqOHhyWU1GWnZXSVk4NHpXUFF1QnNVYm5I +cUJEN0hVakYrNXdyYzVReDY5cEJ2OWdQdlRIL254RmZ4eU1icWljOXFoUTFwejF5djl4Q3ZNbm91 +ZncxQWZySlBYR25xRkJhOXl5WU5iZU0zQmozY3gxQVduT0RpUlJ4em14SlBueS9QZ3dudDZ0LzlC +K3ZDc3Z2cXova3pQNnMvNnMvNnNQK3ZQK3RPb1ArdlBXL2ZuL2lORUlWWUMzeEhCRklyeTJSQ1Uy +QkhyMXdPUUFaeTltZ1NnbjhKOU44d1JuU1FNQ3FnQ3hVR2FmQnFsQVJydHJrbEwxaENxUUNTa3VY +N3lGMHZSR3FuWkJJcGcrOFZTSUl5S05wZ0g0ZnRzTUNKQ1RXbXFYQ0hYM1JzaVdPRkR0RHpxbFVO +OCtOekZrWXVnY1FZSDN0eGhoMGVEY1JpVGk0TXp4RnVydVpvUHE0YkNpQnYxd2kwL1lXd3ZabjIr +UCthUWNjU0FTejdEdkJpK3ExVS81R0kyNi9VRkx2WGFaMy9NRGJlNmNHQ2RQZFphQTc5MXhHa05Q +SG9JdjRNTkovWXdnRDB1YTlWaU9KQzhJYklPaC9LZmgvOWxuK0ZhZnozRGtYazVzOThnYUJ6Z0Nx +L3dFYk5oRHp5NGhJL2c0Y0VGakg1RmdRWVlCUGJIZXFRRHVmVkhYTnFEM3p6K1lkSlhhM0hrTWsr +TDhZLzl6T1dDUVV4OWg4VmUrZUFXTTRmTjhqZVkxQTR2L2F4QlIyL3FoanZ4NjgvNkUyNTExWi8x +Wi8xWmY5YWY5V2Y5V1gvZXNqOFB5UzJ3MktVWk1TaURiY05uRUVRYXJsak5RclR2aXJiZVBCQitX +cGRRd1lBakVoQ3hIb1EyZWMxdlE4WXc0bHNqWnhwb3I2RlFmNVh2VGxRYUp3OGhHY3d0djh0ODlp +R0NRVDBUbDFBLzg5RzNyYWtNalNBT3RXaU12NnIyV1R3OHdBQjd5SElBeVkxOEJoQWZibm10MjZa +UFBaNnJFejV4OGJzY1RBems0OFl6UEJBUmZIQ2VYTjl0azhTSFplT1BDSHgyMlVNb2FoQWY5M2lV +ejREZHNCOUhzTHFyM1Y0aXNGNXQ2c2NWd1JHeXV4enFXWU5lellRTCt4akVHdDlwUloyd01TaGVZ +SkZEZmVLY0IrSDl4bFkvZzY0T2hpTzhiTDFqTU0vZ0N5Zk1aajBjOGprSVhlcVZ4M3Bhd1BkaW55 +RVhqQTVabW9RQnAzQ0tRejlxZ1ZmZXZROE9XdldHRGw5MEw0Zjk5cWdoeG9RTHh6bVU1WVlYVHpr +VVlEQ1BWM29MUnZ0ZzlvY0s3dVFVQTUvUk1tM0F0UDBidnNRS1YvVm4vVmwvMXAvMVovMFp6ZFdm +OVdmOVdYL2V1ajhQaVMwQ1FuQUJBTGRCZ1o0Ym5zV2dCTFZySmdDUUdnT005WklaNXRKZ24rVUFT +c0ZJVTd3Y0JJaGtkN0c4U2JCZUhzVVpTR0NPWUxLTzhEdzNGRXAwU0VXT1hEdW1VUDk2bGJVaFUy +eUdJeHppMUJBWTRiYldHZ1NxRDM3cjRYT0hUZXlJQzVrUmp2MTV6bUR3d0p1YU5Fd01nM2gyN1hD +aEtjU0lVd0xiNW94QUl5YTFXV3VQWGxpalRubk55NzF2SWRiY1krL0p6ZVNlaVVuNGN1UEFmaGZ1 +ZlNjcy9WZnZHdlJxdUlnVVh0L2xnMGVPSEQ1d3lhWDNlcU5PR01XengzUHpZcXZIZ2VBQXBCTWF3 +LzBlS3BOWGZHOGRZYUlSNGd4ZnVMZWZlZlJEVGZiaVJLL2dTTjF5K0tlZ3pkdnJ1L3p3aUI4VHFF +OGY0YVZGcHZZZExsanNWNjhhMWc5VHQxN0JRS2N3eWsrTFlxaHBEK3pCaWg4WVhUbTBjUzRubnNS +d0tJa2pONXpxTmgvT2FCTnU5ZGFmOVdmOVdYL0tVWC9Xbi9Wbi9TblhqdnF6L3J4eGZ4NENDd1lB +WWdqQUpnM1d3QTA0SkZxM2paN0xmeUFkRS92Sk8rYVF5SGR4N0RNUFBKQ0lJYW9VdW8yY1BZQUNq +QWdpZHdqWWh5Unh4YklmOGI0VG5IWElWNVJDTkVrczMrV3pQNE01L0ZXeXVaYy8rSmJMQzcvd0l3 +OU5aV1kxSWprQ2tnUFI4Q0hJUHFRakc4RWFxUloxNEFENWlOVXd2SG5PdUJGSkRPbzdqSWE3K0l3 +Q3I3d3dhcGg2Q1ZKTVBjQ1ZuUEQ1TERmenFNRisyQWtkdDhhYWJZYjQxb250Z3Q5ZVhLckpNNzBp +dnV3aERzSWtmSHYxR2c3NzRKZFBIaHFCeFVIbnN6ZUc2aFFYSjU2NXpLdEZYUFY1UTBJak1LZ2JS +cm54cEsvNEZoLy9lb3hUM09NRU43aTFIemFjV0k5cnd6NXpzT0JYdlF6RVRPcnhka3B2MVNDLzN1 +Tk5IUXhLZC9hcDEzcTFXR3ZlWFc1NDVYT0pyMmJQeGRNckdOVHN1WGwxNFpQR3JZbWZWak9qZDcx +VG8zcjJrSjFZYW1WUXZObGZmOWFmOVdmOVdYL1duL1ZuL1ZsLzFwLzRmU3IrUERSSVFFMVFER0ta +MGFTQWFUeEJDRVpzL2twYWNLUWpTZ0VLczRlSXhYRXBHQkgyQWJva1R6NTNJT1dVUzFGcG1yVXhv +R2NPQjNjRWF3Z2lZZEJRTVJXaW9RaDlmR1ZZNisyQXZacG52M3dHdkhMQktVY01TVkRJSWtyNGtB +YWJ2OHEyeHpQTkpVYk5jamVQUjhTTGp3LzcxUThydnRScnBGWmM0QTluekNBbndicUlZWFBNR3V2 +RndUT1JhYWg1OGNTMWZoczdCOTNaNE12aVVBOGpQczZQMXhYd2RTNkhvQUczTlE2NDRDSWNtdkFN +Rm9iQmtmaHFGOHZoeVJ6MndKeGM0Vmw4K0JuR0duMVRFMDNwbjJkcUltb3hDUnBPZTN6WEQ4L2tn +aGQyOC9iUmgyR05PbC8rd0p0M1hpNzZ4S25uMXRxclYvTHF6V3B4UHZzRFNteTlWWWYxWW5obWpY +cFNsL1d1NVhGcWhFczlMdHpFdk9iMFZON3dSSWU0RTBzdk45K3NlWFUwNkRPdWw3UHBvNTdhSzMv +OVdYOGFjRnRUZjlhZjlXZjlTVlAxWi8xWmY5YWYxdDZpUDQ5ZE9PUzdNNVdmam9uTFhZRSsrNG1W +SUJLUVFSbEpJUkdQeE9ic1FSb1Rpb3Q0UlFOTTJBcG1UaURGczk4aEFBeFNDRmNoTVlQY2luZFl5 +Q2tIa3hIWGllMzhYWGh4WEpvZ2I0YTFHMnVlMmNPd1JERXRYU0poMTBSNTRaUVRPWERLSjRmbW00 +ZkJIbmp3cFZGWmF5OVJJRmxzdFRLcytBUUEyNG5yYnVONmhvZlVJNjZZT1hDOFNYQ0pvN242SVRZ +OHVCUVBaNXR2R3JyWFZheUc1M0l6ajF6NlFHQnl3Kyt1SGtMTmtIZHhYdnVpbC9MQ2hWZDQ0Qk9i +MEgyR1QzeHJ4ZlJkZkxud3BsNTNnbFlyN0ltbmZ2emhDWDh1V2lCNmZOampPMXg2YkkrMWNPT1M1 +anpmTWJWYjcvLy9DRDVyY1NXV04ySDJMTzdwTlY2aUh4ak00eExQZUhLUXd5SytlWWRSRGtUeDlG +MytOZm53SUtaOW5zTkJLL1k2NE9DRkh5OXl5ckgxeklWYldQQVNVNStIMnNtWitMaVN5NzMrckQ4 +WFovMjVXT3JQK2pQMTFKLzFaLzFaZjhwZGY5Nk9QdytCZ0FNRWNjalZISUVRRDRBQmhIV2E1MWNV +R015YmcvM3Bkc0FDNEsrZ2lkUlFGRklVQ3JDMTRoT3pXUElxU2k1dkVPUzFWcE1WNGpQQ0FVV3NZ +aFVpUHBQNUxwNmhpZUxMSTZZclE3eXRaNHEzVHdPSnduQWd3SzlXNjhTV3crR2lualFKQm11UW1B +TnNtejIxYUlydkVhRFBtcVFlT0t6ZjVsOU5aWGd1RnhITEVRUExxMEhxU2VQbHNVNStuRnFqVnRp +SkVBZldhK3JXTmJueGFaKzh4QWUzK0JvT3A5N1o3NEJWWDBiZVpPZ25MdTB4bjFya2h5ODR3b3M0 +RGtFY0VqSk03bnRnek1GaGp6a3hZTi9EZVhLc29BZTM1ekY1REVsbnhBdVRldFNtSm10YzV0M05H +NHdxaGo4ODlBVXV1Y3pyaTdYeXFzdC9Dd0dQQXdmUC9nVTZlUFRPdkFNVHQvaFRxMzJKcVJaL2tK +bkhwOTZJWTcvbjhPUE5kM1hwdXpwaG9UVmM0ZzVlKzYxaFlubGpVQWVmZ3dEbStyUCt6S2cvNjgv +NnMvNnNQK3ZQRFBHMm52cXovcnhCZng0Q1dXQ2pvb0dRVkZPSkIxbElSRHFDRk9GM2lqVkFJZ0lF +VENFTXFsa2FKNGJQaUZBTWdNaXd4eDJZNURYRTFoQ2tJRXgrQlFGc3JWOVJZSGlDSXdiWWtBYWJm +RWpSSU1KVFI0YjgxcXFMS1JnMHcxcW10ZDVlc1pFQ28vb1E2Q0lPR0I0VFRyaHJvcmw4bGdNKy9G +aXZyZ2pjNVRrT2tXOXV6VHljNFFZT2RZdEZqUGlVbnpDc2NWamd4ejdmeFZOWGVxWCtIY3c1OFYw +T1hIdThiVk9qSE5icU54eTRoVUVzNncyaWdkTWFjL0xMSGNFNU1PVHduSURXU1BNY1o5YXJoVmhY +MExOMjQxNFBETDNHc1RwcGhpRFBnK1hzZzRNREp0eGFqM2ZQWWZLV1VKL3RzODVlY2F5SHd4Q0xi +bWhUYm5xREc1ZjZhNjNhN0tNRDNPRU5ULzcvbEt6QktjMjZ6T3VubXZBbHBycmdnOGs4UEhEcUNW +MVlCK2RxYnZpQUh3YWN3T1BOVTliRkl6aHhJSGl1WHpuWWFCdm0rclArckQvcnovcXovcXcvNjAr +YzFKLzE1MVB4NXlFSll3RHFwMmtGQ0thWW1NOEd6eVEyVDhocEpJQ0FBd09BV0JxbklFVklpbEJD +UUtKNGE5S1p0OWNhOGNWR3ZrWXlrVHhFcjJBeGtNYTB5TFhPZWpFMGp5R3NnV0VQbGlIbkhIZTdG +aTRZR1B1VjUzN3NRZERxRXcrSjRoR2EvZVlSWks4MThNRnFIcUV4RzNIZ1RRMndxaDBYRGhWbXNz +Zis4R0NmT1ZnOEp3bzU1TGZHdkI3Z2p6SGtjSGNSZ1hWNHRCYjNtdXBPMEtmSnp2L1BLQnhIcEVT +cjBVUUFENHl3eU92Q3MvMEdiSElScGJVcnpPRWhodzN4UkZoRUY3M29qWnFzd1lmYzlwcTNucURs +VkxOTGZQZlVpbE9ISDV6MjZxYyswQUpEN0s5ZlRHMXdXQU4zK0lUWFVMOWE5VmQ4UEtuQmZoelJo +RDNwTmZQSlpUOXV6Rm1ISC9FOXQ5WWF0YW5kQlRQOXdxSVA1dkJzdjk1SEszaHhod01uMWpnOHJN +OGhqNk1jSHRaWnN3YWRXc1hDSzU3cXovclRxRC9yei9xei9xdy82ODl6MUovMTUyMzc4NGp4a0dn +VG9sWlVFNFNnVjh4REVLQUtJVVFHVW9ETFh1SkJscUo5UjdER0tsQWNSQ1BOY3puTWhUaVhKbW42 +ZmgvQThnS29jZVlVcitDSVFid1F2czBaZ1NrUWJtdThFY2tRNDFNZmZ1dURBTVEreFh6K0dvVjRj +R1llcVhMS3o1QmlXaSt1V0RqUUdPUTZjRnh3T0J6c3RTK1lZd0pOMXhnMTRzQm5EVmFydkRnU013 +YklBWUZUL1lFQlJqWGlIRzdQZk1ZcnMraWZ3V1RlTklsdmpjTVVOclhBWjcxZStoVVA5Y2hyajZI +T0hBVHlwMDd4eFNNMGEzd21VbUpWbTdkUTR1RXk2OVZ5eHIzK244NU9yZnFiUHVJaTVoWERCYU42 +cmZjZE4zRDRqamY1NlVzTjduZzgzeERkTHhhOXllK2hpeU1mbk9Jd2I4eWdQaGprbDRlbWFjcGhE +WU0xWWxpbnY5YnUyOERoelI3YTFRdHhhUjRmY0pwWEQzd3UrY1Z3WU9MRHIwOTRUaU40OGxsc1d0 +YXJCNVBQc0Y3djZzLzY4L1JSL1ZsLzFwLzFaLzFaZjlhZjllZlQ4T2Noc1VCSm9BaUwvWVN1WUlV +YlFHbW9ZaVFpRG8yUVlNSE1Qc1pBRERMZEl5NWsrcHltU2k0ZmtJaGdNT0FVNnE3UmhJSXNCVnNq +dGpzU0VlTnV2K1pZbzFtRWFFN09ETVF6cUdmbXZFSHdVN3hCSkF5a2ZuczEwWjNCMUJZeHdPZXVk +czJ6aG5rMHoxck5Fc042bUt4bk91dVhzMmtBZmtLKzlSb25KdHptOEswWHNLbUhHT1hBQ1JIRm1Q +YUVOeGpsdFJhM0dlTElJVFlCd2F4MnZmTGNQRXo0RlYrLzVIV1h3MXI0ckpYSE14Y002blduRlQw +a0lnZTIveGhjZkgyMkR6WThNRFFUcVFGV2ZjQ05kWElTcjM0SGo3MzJCYTk1UGQ3RFkvaUFDMTk0 +VlYvMENUdSs2Y0k2ZlplSHVjVnhpTWl0RHB6Z1FHeTgwNGVlMGhoOWlFT2o1bkFyMW1wMmFwYlBY +b2NkWGgzNDZ0SVRIb0ZETEh0ZGNsdHZyOE9ENXZRcVBhQS84Y00xYmczNXpGa25mdjFaZjlhZjlX +ZjlXWC9Xbi9WblJ2MVpmNHB6cS83Y3Z3RUZtaWdrSUFBa0VCdkFBQzQ1c3dhQnlBRktRcUFRbjJZ +S1NJU0tVSUNFZVI1QkEyZU5DOWpUWkpkdG1EZ3VnTTBwVUN4RTV5ZDh2NGV2T2ZMQzZ3MEVBU0RG +czIzTTVNMndWZ01RaFhqL2pMR2FORjF1dGFqTEhzM1ZkQUt4QmdkeXhuQWFRVGdhZytDODRiQXVo +Z3pCNnZFOHdyTVg2UVl1SEVRRWd5Zll4RndEZitmOEo0cUpSeXk0WUZJajBlUFRjL0Y5TnlldWVU +VVJQYzZzd2J2REZHYnJjQ1NuZlhCNXF3TWoweG5FckE1NGQvMVZaTEFHdng3Q2tCNFN2cDdJcFdk +aUx4L2JneE9QbnVNMjlab1hFNjlNampmMTB4djlHZkxxblhuRFd2RnhwU2Zpbk5xNTIzbThxY2Qv +Z0EwcjA4T0pSOGJ3UEljYkxjRXJ0bWN2dk9lSFYzZnk3NEU2ZU9oZERmYkxaYTE2MHdkeDhMbHJw +NSsrVzRORDYrblEzWVUzRjN3T3hCalpYblhLRFlmYW9oOTNOZFNmOVdmOVdYL1duL1ZuL1ZsLzFw +LzE1MVB5NTZGNUNoWllZaHMwUndJL1NaK05QLy9QWlRVT3lYN25IT21LVUJCd0Vtc0M0alhHZW5H +UmhYelA4OWU3eEEwQVFQYkp6ZkMraXdVVEFTSkFJZllpbWFpOHRSS2JHU040b29FYlpuYzVWeHpU +UERYQTYwMkd0eGcrbitOdTk0dnBJa3g1WVZDenhoQ1NXTUdnM2pYZEVMdThqUGpjTlVFOTlxL0pa +bWdXbk12allGYUxXZ2xKTEdJaklQSFZvbm5pdzRVUC9NS0hRODJUeDZGaHZiMzRXZnlEUlI2OE9r +alZEYTkxUktnL09DQUNuMkdVRngvTXBhZm5RZmRkZytxWDlXS3NPS2MzZTUrYW1OK2NaL3FvTDRu +cnVkb2VERGg0bU44aHBuN1A4T091cHRYQ3hKSFBSWkJ5TXFPWU9LRkpuTW0xbXByNXhUYThpQU96 +UExUbHNHVytISmJ1dE9HejJQb3NuOXJwQURhNHZSVmlNaGh3Z3hPOWtnZm5OQkZUd293RC9Gc0xn +em05TzNtOFg0ejZya2FYUHJsaTBPaExicnlLUTdkMGdHTkRmSGl0cVQvclQ2UCtyRCszSi9Wbi9W +bC9idTc2cy82c1AyL1hud2N5WGZsSjNtZWIzQmxPZ1E4YnBsREUrR2xiY1pKYUE3Z2tpZ2JPSUZo +cmthcHc5elFMdUJYd0VLaGhTRWFRdkFyUVRLUWdpTmcweDAvdjRqazhDSlRvWWdiRXdHYWYvRWc1 +eDkyK3ZXQk1zVFhQZnlpYm9WNEhqajN1MXNpcDVoWHJZUFljQnMvVE5EWERsQmlhQXovT0NHQXh6 +MTN6MUFoekJFVjhhV0N3RW84M09XdlFFU01CTEVlVFEwMXc0Uk5uNHRnam4rK2FES05lMkx0amVv +ay8rTVcwQm1jNGxOZm52QmtqQ3JFTklvZlJHczljOEVlY3hJb1hXbENmbkhxT1YwYUZDd2N3bXRk +clBXY2dITUNFUXptczBUY3g1WU9SS2EzRFAwNzBUcHh3b1piMHlYcjh3V3pBcFM4T0hWbzBoeVA5 +SVg0OHFsYzh3di9NUjkrMitvbXVmSVpQL0JoWkh2dmx3clUxdmpPWU91ekR6eDQwTTI4TzkrcFRT +LzZ3QzVlNGdrMS9yVGVITDd6SmJmM0dHSk5iNTJBMzc2by82OC82cy82c1ArdlArclArUEVmOVdY +L2V0ajhQUUloT29jaXlXQU0wMFJ1R05lZzBYak9ZUUFHYXUwbG03NXB2UUNwR2s0QTJpRU5TalFV +V2FNMUNKSEFSdGM5RUt5ZFF5WXNVZStTeDFqUHJFUXFIdkd2UUlZUVFWdkNUT3lKUnBJR1UvRFBF +WXI3NHpCc2ZEaDJrcVlYQTdCSEQvcnh4VUpmblBpTVBUOFRxY0ZDWEFUL3hxTlZhbnhrVU52dlZn +eHY3Y1NJMkxJeWhRZmcwaDZNMTJSd3E5cWZwMW9sckQ3eGkyYmNtbVhsN1BZUHJISGZicnh3bTVs +enc2QStoaGtlY2lyVTgyamwxbXNNdjd1RlJBLzdWNGM0c20zdnF4SUgvenl6OE1ZYjY4Q2V2Tld1 +ZTZZODdidFhJSkRDSVJ3OXF5aUZFa05aWnIyWUhLeHhFRzgzQWkydDhpTEU1cmdlVCtBNWdPTXpu +NEdJNGUrV3dsODdXaEJOVHp0WHQ5ZEEwbDMyd3lnMmZ0VDdUanRyaHRrNGV0ZUxDdlA3NlRKOTZB +eStmdUhCa0R3L3NId0REclp6dzJ1Tk83MkxBcVEvMVovMVpmOWFmOVdmOVdYL1duL1ZuL1FtSCtm +anNsdjI1L3orZ0FoRzV4QWhXREdJMFVFT1FySWtJa01CejMxMzJBK3V1NlFJYjF2a3BING51QU1T +ZzlubE9SRDZ2Q1VjWUFNcUxQR1NLeTd3S1lTUXh2RFh3K2ZFYkl1SzB4cVVXKzlNMGEvMk9QSFBD +WkgvbTVOSUU2OTNWaDFBWW1RVHhESUw0TmNuVW1JWnJEQ0s5TGZBTTBXcHl5R1d2R2p4emgwdnpI +UnhpaWF0K21EVUNiOVlhNGpLb3k5cnNGOU02ejN5V2wzakVJZ0Q3NUdaU3VmUlMzYkNaZDFnUmpq +ekU0cS9NN1krNTFZM3o5Rmw4ZWVoQmI4UklyMkRURC8rQnRqaEVIckhwQzI1aEVUT0dvd1A1MUcy +djUyTERoRTk1MVVkejF2dFZndWdHenprSTlVZ3Y1S0xiUFpUbThvYUlxZlVnM1Byc0dmN3NWUmRz +T0dNMmZjK3ZLTUFuTCs3VWpqdDFQcGhzVEFvYnZ1QVNqK2JGazhlOG11SFUwOVhrWU1TcE9BNHp6 +Mm5hTUNkSGFvRkJuVVlPRTNQMVovMXAxSi8xWi8xWmY5YWY5V2Y5V1gvaTl0YjllZmdKV1JLaUZj +UmlUUkVjWWNBZ0daSElWU2lEQ2dhUXdBVHN6alRXR0FqU09DS1J3MzVFSTFTZU5ORm5SZG52QWl6 +Q0NsaHJHWTJ3aUl3UXhEd0ZmMWx5c2orQ3pGQ0R0d2FJTWVkdHhvNnBGOG1Fa1lhcFN6TUl4Nkdr +UHMvVXAyN05oVVdOYTZaWlk2OUdxd1B1UFRRbXRyZzRsTVBkODVqSU91U0xRU0F3ZWViTnlrS2JC +aEtDdUhudUhvT0tUVUR3d2FzMmE4WE84Rng4NitYd21lQWRMbnFuVndSTmtQZ3oxTzB6L25JWXdR +ODdibjFuVURqVVN4dGlpa1hjK0pYWE9xSVVUdy9oZ3NNNmRaaW5EMzJYRHlZNGNLMVc4NzdEalRO +eHdvZDlPSGZYZjNoMlRDL3c0TmRuWUZhREdITFF0N2VFT1JUMWtqYlZRQjl3eVVON0xuSGh0MDVl +MkprT0QydkE0WDM3TUJ6QkM0TWFyWUVSZmpIRnd2c2FkRFJFaDdTVFB3RE13MnlOUzE3cjlCOStl +T3ZQK3JQK3JEL3J6L3JUT25XWXJ6L3JUNlArckQ5djJaOUhHdWtod2pXZm1Wd1dTZWluYjBrUWc4 +Ulg1eWQ2eEFKdnpZcHdHb2hvM3cyaXRNNGVidzhpVGcwVFV6eTV4TkdBQ05tbGlXTEJoWGlrK281 +UWJ6WVVKWFlNS3VidXZXSWdGSmdOVFlwNEZPd05rVm9OT1QzTEFST0JNWmk4TUdxZytPWTBCU1oz +elZJUHNiczJ4bURBbTZGNVBvdGhqL29qY0hPYXI3bnF3STkxYSs0Wm1vY2ovRG9VWUZQclk0eUpK +dzZNMW01ZHNNL0FJVnpxaDFjY1FwVlBIT0xYS3hqT0hsLzI4UFJaWHg0ZjFLa0xYN2hRajg4RUNi +dTNSSFFCRHo0Mzk2eDNJT2V3azE4ZjVOVS9PZEozenduWG9XdXZXdVRXUzczSEdaMjQ3SVBaYzdq +a09NZmRjdkh5QjkreTgyckdHVTZZMWx2Q2NPeTVlZmh4NG9ETTRidTF6NFc3MWNiMTRJVnBOVHp4 +NU1ackRPZzVqT3BuUHA5aEY4ZWNQSjc1dzBVZVBCdnBmM2dXYy9zNHV0by9ET2VxUCt2UCtyUCsz +TnJxei9xei9xdy82OC82ODRuNDg1QU02VFlCcUhtZUtVSXdpNDBJRHFpSUdxQVZ6Z1NYeUJzSDh3 +YmdtaUdXaGdCQmVBcEFGSERXSWdLNTVxMGxQaUowV1dQQTVTZHVPUld1VU0yVTExLzVpK3NnZ0Jm +eENEME5lditRUDJJa0JzOE50UkwzSGc3ekdVRml3bWc0c0FnUFArN2l5dUV1bjJZaDN6T05VSXRZ +bnZ0c0RSNnN4NS92WWx1UGZNL3c1Nit1MVkwWEJyTmZvd2dTZmp5bEwrcXdIMS9CcldhZmR6RG9Y +UHFoWGpsOEZwOFkxUStQZW5KSUVLRkI3QXlLKzYxcGFsUEhneUduUmpwWlVYNzF5OXQvYjFxOGZZ +SEx2SjdEWmkvOFlxb25CNlc0Y3NPanh2U1dwdXdOSGx6cG0vemk2TDMxc01PTkMzbWlUME5zWnNT +WE5hZTI3dGVjZmcwQkYrTGpTMnk0NVBhV3l4NDU5QUFXc2EyVFV5d2FVSy82MWNsOHRKamVtRmR6 +ZWllR2ZkWm1ueno2VFZjR1A2Mk9aejlPYVlLUkRWanhwYzc2cy80MDZzLzZzLzZzUCt2UCtyUCty +RDlQYmQyMlB3OFRST21uZFJ2VGlNY0daUUJCQVBCY0lHdk5JMUloaEM2WndJYm5ucmxiNzY3NXdD +dGV6bTJrcGs4VEVTT0hSbXFlbU9MbkRaSG1JUnd4M3ZJUUJnendhU2hjY29pcnBnd05nWU9BNFBC +WlRNTWVKQk9KUFlTSjhIUCtmdVBDUTR6MndST0J5R1VrcHdhWjF3d0d3UlhjOG5zdVRreWtEbk8r +cTUyQjROdTl4dXlYR3pkaTR3T3ZPSklmVC9hN200UG53YUFPcHRudk9Xek1vTkhXNEl4SjFZSUx6 +L0NadmZwTVNPa0JYbUExdjU5SFVIQkZDeTY5K09UNzM3QzhXWWRQZGVDRDJBZ1duL2Jvb1hyVWFy +M0RpdTZZaEdHQ1dkMTR3d3Z1WWRKcnVmRUF1L3JYZ05kZTZoZHUxQ3AreklWZnNmTm1VUy8wV1U3 +ek9CRVBYdmpGRjFldDlPRTVEdUdBeTJIaGpqZDZqeFpnTktjbWNmeEtEU3poem5QY3ExL2V2QjFq +V2xqbEUxTWNtdlpaL2ZWbi9abTk5V2Y5V1gvV24vVm4vWmxSZjlhZnQrelAvUUYwUlRsakFjNkVn +Q0ZDZ1VnV3hHWWlNZWVuYWNEdFFaWmdRQ0daU0JDaGVMRVJZNS85Q3RZc2hXbUNKbnN1SjBLSVk4 +MDA4d2hIUEFKZ0NTN2tFb2FZQm1JVW5pTGhlbXpDeEVTWUN3R0cydUdXeHpyNy9HUy9ZMFFPbDdr +Y1JoSHBFa29jTTl5elpqSHM4N3Y5Ymozc25vbXZ5ZXJ4M2R3ZVVGTy9XZ2s3TVEwaXR6NjhxQXRl +L09KN3NjejY5SWxvSHNiVWJvOTVQWERCckZadjhmQ1htT3BLWGdabEtuTTRnd0ZHUFZZUHZxMlIz +MmVYdjk3M1ZnWU9mWlVETm1iU2F4eTY5QnBPK1FnYkwzVGwwTWE1K3RWbW5mMnc2VEcrNVNScUZ6 +MmxaL2dnL0F3OTl2WVFmdlhDS1lkL0F0eGx2UXRtTWVEekgzQ3ZBYWNQcTlYQklUWk9mSll6bnNp +aEpLNURUaDB3NDBFZCtzVjR2dU5aWGZyQ0szcVNQOENzaGR1Y2c4TTZkY3FMTjNONGdnZmUrclAr +M0czMVovMVpmKzUzYy9Wbi9WbC8xcC8xNSszNjh5QnVwQkNPSmlqU2hMdGlQTmMwZ0JUa1dRenFH +U0kwVUVPSlh6SEVMWm1maU1VeUI3dzR3Q0VRUVhtdUtPdkZsZGMrNVBodWFDWWlGR1N2Z3Z4dU5r +RVljRmxqSGg1RXhLRGlJVXQ4Kzh6Rm9FZ3dCL1BPalhDejF4cEV5U0YrQkNtSDljeG9KRGRjU1Ba +WmpmakJKNUhIdlBhS3E4RXV4aFFmNW9oa3gvQ0hsMXpXeWdtdk9zUWhJTjg5RjRlQXpuSCt5b2E0 +MXFpSElNVjNlU09STnpVTUkxNXEwUWU0bVZKY3VQVmJiWVNxTnZmVUNnZnoreDM1bUd2Rk5ua0pE +UTk2enFEUmgzanF4YVY4NXVqSWxVT2JKdVVRR3daODRGbE1tbERIMWpjWTBrdkRId1IrM1dNUHZh +bU5qaGpVNzgzandSODJhaEJYZkxseFF3TTREc1ljZUhpU1gyMTdtRTR2WWl3SGdWcng1TGxZMWtZ +UHRFVGplcVEzY01RdjBhWjk4Rm9udjE3Z04vMVRaLzFaZjlhZjlTY3M5V2Y5V1gvV24vVm4vZmxV +L0hrZ1UwS2JCUGZYdDBpeUlJSkJOS0VMeUhBYXFwRXhrN1V4Z3IzQUtBb1IxbXVLSE9JaFgwNWti +SEh6WEJPdDh6MHhrOGZJZmdlQkp2cXBHckV3S0FadUJjS25nWWpJWUhSdnJqVElHd2RpeXBBUFp2 +RTFoWGg5eDRWbUVwQkdpbzlrZUZhOGs0UEFEQmlRcWxtdytXeXYrOVk4RjJGcEZMTWE2dkRjSGsz +UkNQandnRHREWFBWWUM2Zkd5ZzFQRG90OEYwUDl6RW5NT1BIY00zVTV6SElnZXB0anZiM21pRW45 +aG5yMXlENDE1bUN4SGw0MXdHWDlIbGFEVDYvOERqcHowTUVlT3JOSC9Xdk80UUlldVZmNHN3YzJl +YzBUdko3YnAwNjU4VTFIUkE3RGFtYk1hSTZ1ckk5aEJ2VmlOMmhYRDhPSm1QRFNDNDZYeTZzKzNP +MlhJL2x4ekpSeXFOVWUrMmtSamh5Q3UyLzZwUVo1UEljWnpod3U5b2pqZ3B1QmFVdHVuMm5NODg4 +Ky8vWjloak01OFVVRHlWOS8xcC8xWi8yNVdxMC9kMC85V1gvV24rZW9QK3ZQVy9ibmtRWm9MREFX +TXhBQ1hJSzVnQWZJUmswa3RqUi9CVDRGRXR4WnhQMnVad3lrZStheVQxUGNRelRpTlVsT1JZbVJO +d2tSanNhTGh4aU45dGZOaEVFMG1zOEFpaVdhQjROT1BYQ0lSenp5ZmVhamI5dTNCUmxFb0NIaTIr +ZXZxMzNYZEdLRlM4MTQwVnhjd1d0dG1xVWg1bUMxVnlQdGgwbE5MczJEWTAwOXVPRFdSSFhpVTIz +bWlYb0ZOODBUTjNuVmpDczVyVldMNTliSWl6ZjFQNHpaTDY3TFhtOHpHTlJlLzFTeW11RFNzOVJs +ZU51aU4vU1F2b2tQcTNYdTZxSUwrejBUMTZIbklQUWROekVvZlBydElqYXhIUGJ5cjJoSGpPYmdz +RWNNR0QzVEU4YVNFNDZOUFpqMDBqcDU0UGdQYjRqbXVWcmQ5VllPWFB1UHlQZlhLR1kvcnBnTEx6 +RDZMRFlNK0JLZmtjVFhFM3pZWjYyYWFRSSt2WmJmT3Z2MU9MNndYcng4aGtQZC9vQndJRmhuNElO +L3RoK2pLN2pqTi9nZE9QVm4vVmwvMXA4dSs5VG1Ybi9Xbi9Wbi9TbGUvVmwvM3FvLzkyOUFMZlJR +RW91UVFnUTJBa1IwaUVPYWpZb0d5bHB2Z2V6WEFNMWFFWTVBckFjR1VIR3MxMXlHWXJRUVlZNG83 +U05DcERDWVBIdHdYTTN1bVlhN2V6c2t0NkxNRTdMUGFvRVIrWjVySGhNd3MvVWYrcG52WDNQRFlN +aGhIbTU1emZuTVJQYjZTUjFXdU1XTm9HSGVPZ2MzenBKZnN6WEZzRThqOWhxKzFIWTI1bTVqRUtS +NE1HZ3kvalErZVFsaVJURnhyWk5mazRuQ1o4L2xnQVZQYWw5enp4QWpKbE9YdHlZNDBIQTFFcW1j +bmxsejhtV2NCNHQ1T1JoRGZIcUEzVFBZWVE0SFlyN3lvUi9kdTlxdDlaeWU0SXVnZzJmNW1PZldx +NDlXYU0wK1dzSVQ0NHBCdkhMNjdtSWcrMmdIWnJwN2JGQjd6ZUhXUGpqMVdoeGFoQWwvdEFDUDcz +NUZ3SHB4NFpCREw5U0pPNzFWSys3NVFDMzJpV2VkSEM2OHJVbW4xMkxybVhwcDF6N1A4ellvaDd1 +OERpRStrRHY1WVBiZC92cXovcXcvNjAvcjY4LzZzLzZzUCt2UCt0TStPRy9kbjRkQ05Bc0JKb0ZY +aENhdEFZWkFDVHdEMmxxQmtlc05VWnBzSDRGYVIrRElReUx3NGlneVlpY214YTNKQ0d0eStDa2FT +UWphdzJCeWFKb2hqeUxFWkNCNTRCT0xBT0JXdEhuNTREbkgvYTZIMGY2UHZQTUhIOGlCVVM0WXhZ +Zkg1ZnMyZmVaWFpFT3Nwc0NDUUhYSWd4UHJ6Qk9tWjRTd25NM0lQazFUaXh6MndBU0RKc3JudWIz +cWtjOGV1Y1YwSUsxQlp3MmVjS3R4YWhSVFA4SWJYUFliRWJOMU1PT1pNUEFpRm43bGh6ZENPTWY5 +Q3RGM0dKbEpYTmowRGhaY3krdXpPNzc4aW9LZXlPZDdETHFIMkpWSGVNUlJxN3JvQ0RmeWJjL211 +enJVcGE5cTFUdjF5U21HL2Rha0JyRmlVS0wyQmtaTjVobkVYU3oxcThsYlA4OVhYNk43Y2IxUmdz +MDZzWmw4K1oxNTY2eVIxMTc5bzN2UDFPc3d3Qk11NkRBbUZvT0IxV0t2M3VIS1lSQWRHMnIwRnRV +QkdsL3RJVDZjcGRiNnMvNTh6RmY5V1gvV24vVm4vVmwvMXAvMTU2Mzc4OUFnR3hSb2d3Q0kzY0RY +eE1SR29KNVpMeUV3QXZpcldrVVRBRUlVNWsxRGhDTzJvdXlYM09XejRoUWhsdHpXSThhY3kvTnQ4 +SmdYcmpYejRDRXNCTWdsbGdZaHhWcGtLVkxlRE92eVJ1bnpML3prNVJQdmUvMFNJQzVzNHJrVEZx +TFVaMDROYXBlVGFTSlVkL2cwbEdoZ1c4RU9YbHhZdjIrV2hqTWNNUXRNNGhLNXVPYmtVd2VzbW0y +dlBEa2N4RnlEVGx6OGl1R1p2VERiN3pNakU1SzhpM3V4bi85aHUvWEw5ZFR1TUpERFBqMWFFVTFl +K2NVejlwQ2V1bnpYRC90OUptN1BZNWJNdVROUmZpMUFUQnpCbzNaOTFYdDh5Qm1UMnF1ZTdlL3dB +WlA5ZWtVRCtvbFhjVk0zZytnVG5jSHRzelVucDVmTnc1d09KRGprSW5yclBQZFpEVnYzNkU0dUJ3 +U0R5QUV2YkM1dkRkV3c4VWRqK01lTjcyTGcxaHJjNG45cmRGZ1BGcC9OMFpLZXlHTWRiWG83QjR2 +ditxUzMvT2JOR0F6aVdvY3ZldUdSK3JQK0ZNK29QK3ZQK3JQK3RGYzk5V2Y5YVYzOVdYL2Vxai8z +dndGRm5zQ1MyYUR4bnFWNGdTeldLSVZ1b3dhbzd4cHNIakgyK214b0xFRWcxRE1rSTRUb3hCYlBI +b0FZUVV6a0lOWThzQXJVWk04MVRqNUM4OWxsalpqSTAydzVQUCt1UWUvMk0xTDhkTzZ2aU4wTk9C +Q0ZIREdaK0ptZityNjlNOGdhWlVqRWhVc05jTG5MU3pDdythNXg4bXVxdVBiQ3ZwK0hiR3ZNRVI1 +TTF0cERtUERDQUNmRG03ZEhMZmgzejZISUhIZ1hHMTlpcU1GM21NN2gvNFQ0L0tlOTdjR24zSHJq +VFJuZTlVYk55UnVEcXBjT1BNTzEzUFlUdGR5d3dwUjg4b3Zqell6MXlhY3VnbGVIbW55Mm50WlNp +Ky8wSnFmK3d1aXVIcnlKN1VDMTNqNTF3ME4vRG9YMElocGxKRGhvQ240WVlCR1BvZFdOZi92VUl5 +NWM1cXlEaFJIRmg4TThUUHJzN3ZDVWo5N3doVTgxV0U4ZjhORXkvbkxnaWtILzZzRVovVEd2T3ZU +SU13ZUVQdUFXaDh2SnpNbXhkZGFmOVdmOVdYOE9wdnF6L2t3dHZ0ZWY5YWZQOVdmOWVhditQQ1FK +RUNBRWtOQlAxb28yZlBZVHZHS3RYVkF6WjYvQ2ljV2RFWWpQVUNSd3dDSUZLSVNrV1g1eXRrWlJE +T2FaWnJ1UXJEaDVOVm5PSmVONklHaVlndVNDbThFUkl4YXlyVEVZQlc1dkdoREtmQjkvNyt0bTVt +NU5hSzNmNzRZRCtZaFNoM2ptclNPQVBiQW1Kd3d3cVFVdTRwTFRNNElTVHkwRzhqV0dpY01Ua1J0 +dzR3OG1jZFdNSTJLVzI1VkRJYUlVSzMxeCtTd083SGdSNXh6bmZ4dWd3V202L2Q2S2VHdmhqbHU4 +RUFkUldwY0JyMmRxSWJyMGhUYmtWWU1lcWxmZm1SNkhETUVBbm91SEJ4eTVyS2NkZlpGZnpPVjFM +alhENm9MVDN2eGg0WTJLNXpqR2c4L3FoZDA2dWZCZ2lPVS94c1l6YmNHbEJ6anltV0dzeDZsbjZR +L05xZ3NtMmxjckhITFNIU3g2c3oyWm52bU9IL0dXMyt0YXZLZ0ZCL0tKQlFjdDA2Z1kvcEVBUE1r +YnpQamdFYldKZXg3aWwxMEhHMXoxWi8yWlVYL1duL2JWbi9Wbi9WbC8xcC8xNXkzNzgwQ2NpeGcx +d1FiZkxUaC9XajNGRmxJOEJ3b3hnREdoQXBHUEhNWG5KMXpGMmdjb1lqUks0OFFYUTZ5SW5jRHNG +ODhld2hMZlVKdzlZaEtYSnNsM051ZjZIMHBmeFNTWC9UdkdaTlloVEs1WDV5ZHUvMG9ZQVJ2V0Vr +RkU2ODJCZGVLcHdRV0RtZ2dXRDNDclJiNXQycENjeGx0anIvaTQwOURnZ2tsenpTVW13NmdwQjRo +NHVMQU9WM2dWR3hkeU00VExNM3laVDYva2Z6eHdKTFo1bnduS3Y0SW1KeUhBRTVHZDVwNTZoeS85 +aVVFSktRWlZ6L1o5Nm1WaU5iZ0kwVnMzZTJBVFMwNWFVZzl4RWk4TStIY293S1h2NnR3RFlmcGd2 +NHNwOEthM01ZLzluc0VNaS9VMElWYzBtZ05QYm4zRUthencrNDRmTWVXMkYzWm15Q0VncGxyMkQ1 +dkpZNzI5NHNPQUcvdnRvVlBjd2Fzdm5xbFhiMk42c2VSUkQwN3g2WTJYWHFwSDNlcFhEMzE2VG9P +bnBrZWJFOXNmS09weDFaLzFaLzFaZjlhZjlTZGM5V2Y5V1gvV243ZnV6ME56Tllrd1RQcHBGVUJK +RkdoNDdpZmRrQStVT1FBUUhoSWtNNmNZYXpXVVFUUUdnWnJscDJnNXpYL1gwT2Qvc09wNWhHUHY0 +d1BDdkNJMVdpRUkwR1RDVW16RWhHZ205MXhOK1E2YkdvZzBBMmJrdUl1Wk4xT0U1ZTJQd1NpYWlV +RDV6R3NpY2hFUHYxcTJYdVNQSVdOUWM3akRsWDFpMmFjZVRmSlg2c1RoelJTKzVaUmJERWJITDZI +Z1Fxd1lJSHdrUndSNEhpcm5pRUZoVWhjaDVwK1RGazlPWmhXUHVBeDgrV3d2VGNBdGw4OUVhSzAr +cWpzWThNY011QkhYUlNQNnJCNTM5ZUJRTC9UY1h1dkZVcTk4YW5CM3Fjc0ZvNXJ0MTJON0NWZjhh +Q0dDdGw2TjI0dmhqYmpocHluR3NNOTM4ZXlEQjhkNGgxbU50RzlOeklWVGx6azRHWlhPWUlXTnJz +U0JRYXo5QTJqcTVRazU0R1JRUXd4dnNQWUFtaHpxY2VrUHJOWXY1OWQ2OUU0OTlXZjlXWC9Xbi9W +bi9WbC8xcC8xNXpud1ZIL2V2ajhQaVNTMldKRUNhS0tmK29uSkFHSk5OYUJ0Uks0NW92UFA3SG9t +Z1ovUVQ4UGRiL00wWWswM3hDcEF3OXl0dFk1NXhUYUFGbC94aU5KTVpESzdXQ3ZPYTM1bUl5N1BE +WWFJZ09GM0lHUmtENUVpL3FNLy8wTzczcEJiZ3hERDNMREtTNmluMk8vV1NQSVFCREpkUHF2RGdT +TytOWnFzbmpYM1lNYnBtbk11K2Uyemg3ak4yWk0zR25xUU4wVHlFcTU2MW94WGdVVFU5dm9NdTd2 +ditIUTMxTGFOWjViSmlTc0huc1BnbGVkK2JMbkZzWHdFSnY4YWREQzdFNW05YWhRZnA5YlFobWV3 +NWREQ21iZHJZdU9JSHZBQk0yNWllSFY1Sm80OVJDNzJxYS83elFjVG5semZhMUM4V1l0Zk5jbkJj +TDZMWmNpdnR2VGJITzdWVG45eU8yVGM5UmgreG5mQXdHUy90ZmJqUnAxeW0zT3BCZit3ZUk1WFdy +TmVyUFFXSHQ0eHIzL1dxRjhQZVVXdmNMSi9XTTE2ZGVzRGZXNjgyV1BBNlErVCtyUCtyRC9yVC9q +cnovclRudnF6L3N6SW52cXovcnhGZng3TWlEeU5WUkRpTENKY3BIaHVudWcxSU1VQ1RORGVEbWdj +ME9ZVVprVDAxcVpnZ3pEbEVOTitPUW5LM1hxNTVkR0lFSU53cEdxa3orSWhRUXdOOWxONVJHM05Z +NFBDa2IxSWVPblpOMTFualB0dHRNYktwNmthcG5HWjExU05zZDhhOFJ3bVJJcGd6OVVPQzRMVkI0 +K0dxMU56Tk1UZVUweDMrOWt6ZWZGRDVEZ1V6N0JYVGx6NHpIenVoT0FaWGdqWTNUN3hUaTZ1WXc3 +Q2ZUN1lDRUJmaU4zdmFNdUZIN1hpV1EvVW9zK3c2b1c5ZU1hbkdtQ0V4MXA4RUdONDJjTnlET3B1 +M2o2aVZSOGVJbHhZNFlHZnJuQzRQQTlXYzBTcUZ6REo2Nkl0dWNXeVBuamhJL1JnTi9URmVqRmNj +TWl0OXRRckZteTRVcXNha29OR1hHcWlBWHEwVmw1WC9nQ0RGdzQ4eWdtTDUrTEZRN1NKazhSVnI3 +WGVISzErUnF2V3UrQ0VFVGY2TEI3ZDBaUS9VT3JQK3JQK3JEL3J6L29UbnZxei9xdy82MDg0bm9J +LzkxZHdKUUFBVVJwQ1pJcFNnQ0JBRTVOZ0Vrb01xRFhBQ0N5eE9jUWFoR0dkV0lvUnc5aUNCeUF4 +eSsyejNCR2ZXTWhITW9JVURYd0VwUUJ6d0NOangzZSt0VmhoTnllZklnM2lSYmdEUjR5WFAvRG1y +Y2tRWDEzZUROZ1hJWXUxWStMQ0oyZWE2KzRac3hHQVorWmh4NGRteUkwSDgyb2pJblc1ZXpPQUsz +UHk0b2s0MVd6T0lDQzFNS1ZMWC9DSEkzdmxoTkZsUHFKK1BEd1RFOC9xVXFjM0ZISzVjQXEzQTVv +QjVONjNGbU1xQjVrOXE0ZkJZcTJhWVpWVFQzMlhFNzg0azArTjF1QnlPUjZOaUdsOXNPY3dVUStE +NnEvUHVMTlBISDExd1F0anhDdytqZUZhYnQrWHMra1RMZUZUdmVvamNMWFFpUU1kVDU1dG42Zm1I +SGplTk5xcnI5YkJvdlljVExEN0hCK29DYS9XNGtjODllZ25ITzY0cGpYMXV0dUxRN25nRmxQZDlH +Qk4raStPbkE1eG4rR29QK3ZQK3JQKzNOcG0xSi8xcDVqcXFUL3J6L3F6L3J4bGZ4NkNCUURRaWdj +Y1VacUJBSVY1QTVEaXJQZGNJNWtCNmRab2pzQ1MyOC9RREdtUE5aNHJ3SUZnTFlJSm5ZamxCdEl6 +ZDdsT3NQY2JTNkZ5d0NjZVVxMDl4L1ZmOHhyUjJHdmVrQThHVGZQN3lYNXE5OWYwbmh2SThYdklh +Ukx6K2ltZVlEUEVsQjkrNjhYREN5UGlRRjVFTXcyaCtxd2UyT1R4aGd0ZmNIdFRndHVZVFpQeFFK +d3c2NEY2Y1dKZWJIeUtwYkg0MkFOcVJFckE0dUFOVitJVGovM3lhN3IrV0V1a0xyWEo1Y0NDU1Mw +d3dJekRQWkFtWnc1aE9TTWdQRml2ZHdTbUZ2SHhSZ013bXJNZU4xdjdYRXpDaUdMcGpkd3dxMUZN +V04ySlVRMTRYQkhQSG9lLzV6ZzB6T01MRHAvVkw3NkJnNWdDWG5mOStmekhmM3FOeTB4TUVuM1FF +aTA2dUZkdlU3TzNSZUxpQ1A4K3EzVjFOdHptRHpMY1dDT0duUEREYTQyWWZ1ZWRYK3lUV3d3NTFJ +TjNNVngwbEVPU1J2R3kvcGw2eFZSSC9WbC8xcC8xWi8xWmYrcE4vVmwvbWpma2c2SCtyRDl2MVor +SGlTMTBDQ2RFUUMwa1NJR1JyUWt4cUxzZ2lnY0FVTW50OFZsUmhyaklKaVQ3dHNnUkFLSVlRRUdJ +Um9UbjhvVjA5eFhvN05Wa1JjaVJnaGtLMlo3dnVKSkhYTWpUeEhPYy8zOUJuaEVpZy9wcitsUEk1 +ejhYalFUeEVLSkJDRCtiZnI1aGdzUCttQk5IbnVITE9uVnFDaXl3TVpuYTdNR1JRWXp3eXFzbUY3 +NElXNU5oSTNaTk14Z21CNDZheGRKTWNleVQwM2ZOeGhkTW5xL0ovTy9rWlFTY3dhMW5ST0F3OG52 +WGNzRmdUbS9GT2crSDgxOWMwemQ1WVlPYlNkVGttWnd1T1NKczV0ZGpXUEFkNDV6R092ODdBMnZ4 +cDVhZG03N0txMWE5dzBWNnZBYWRQZnNmVjA5ODJIQmpIaGE0WEhRQ3J4eWV3d0U3TGNFaHJ0NHlJ +UjNqVkh5OCt1d1pNOG1GQy9vUVZ4L1ZBSys1MVpuRGF6aURBeWQwSW9jN2JsdzVnUDMvSVlraGw5 +N0FxTWYrSTNsNXJlRUIrZkVSZitoaERnR1hldXJQK3JQK3JEK3RyVC9yVDdYVW4vWG5PZXJQK3ZP +Mi9YbElKckVKeVFBVkJEZ0JOTUlhUlp0REJHQ1NLa0x6a2VJekVER041MklvR0xGQUVTaHp1eFNP +WU0wMjVQQlpjNWJFS3lhRmlFTkk4anNFL2k5Nzk3OHIyMVZkQ2JoZXVidWxUcUtFaUJDQ1pFUklD +TjJ0L0lRUWdzQVlHd3NENFJvbGhMVFM4Z1A0T2ZyUEtKd0N1ZWMzVjQzaml0VVBrQ3FOSlcxMjFW +NXJ6VG5tbUdPc3E3Tjk3Z1dwMW0xTWcvQUhNd3dSWFliODZsSXNnU0x2Q09jWWg1bkZRNVMzWmNU +cmVjWUtFdWE1V3lPV0d0WHY4bFlJZGtMYWcyRWFLYjQ5K1BOZEhmZ3ppQ01pem1FSG05eldHbXBX +bXpteDhVc0FudU1NbjJLTDRVb1BpZDNCb21kd3dVb2c2WjluQklzZk1WejR3bVZ3RTRubmNxWnVu +NjF4aDEvdnJDY3MvY0NMZWZqZzBhODk1UFlnUFArSHRBNFZHUFJEcjZ6RkMvUDU3QUF4cnc0NVBI +Tmc0dm9jbU9jdkxzc0hGNDJJbVpweDc0S0owZkJINUhqMW5XNGRDT3JkZzVObUpnOCt4RkliZm1B +U3gxbzgwUUw4OElncEh6NXhtOE1TSCtyQW4vMTA1bENBRTVkNlo3MWZ1UkFqTmNPQkQ5cWhDZGp3 +NS9DQ1hhM3ByYmhpV0ZOLzFwOXlwbTZmNjgvNjArZjZzLzZzUCt0UG8vNnNQLyt6Ky9QaVRZSUFR +QkNNcE1nR1FnQUxQVmNnY2ZqSlYyR1NDR3BPVUNDSjJ4ckRteGVOUXFxN3RSTGJpM1NOVU1BQ3Vn +bmFHbVJaTDcvdjhnQ09lTS9FODUrZGtXZXZJUzdNc0NzWWFYbGI0anZzM2tBbzJIOG1GdE9BQldZ +SGkzbU4wZEFWN0czQUpZYlljSWdCSDRQQURRZnl4WUVSWDU3RFJvZ3I4T0dFVVl6VUx3ZWNoQUVm +b1J5RG52Ky9LdlBxbHdzbkRnTjlJbUpOOTEyenJjR0xmUFlaMWhGSWVKUWpCbFdmT2htQ21HRFVp +L1FhM3RRTWQrN3FGd091SEFhRVJROTRNYzlJY0ZpUDQrVmllZ01QTE9vVXd6ejh1TUFWTHNPZjJI +Q0poUlA5WmhaRGJQaURDL2ZwSlU3MnNKejRlTjBZLy9aLzE1eDY2MjBUcnZTQlp1bFZYbnQ4eHhN +ZHFEMmUwQ2U1UGNNUHZ2TmN6OFZ6ZWFaV3ZjT0xtUEVMUFBxQ2IyK0lscWZCWmIzOFl1YkFoRzAx +ZlRzc1Bhcy82OC82cy82MHR2NnNQOFV3WDMvV240YnY5V2Y5K2FqK3ZDZ0FHUXBDa2cwQUNTeVE0 +SnF3UlEwUWF3UUdCQWh6UUdvMDBNUUFLSU1pVnpHSXQ4WndUNFBNeVF1UVppRmRzejIvTnlEUjJF +Y2NpQkpiTGxqUGVObkcycC9HSGJHUElRYVgvUEREbzE0eERQRmpMZ1Jxa3JjMVltV0lod3U0SWpJ +MTRrVWNNYjFoOHZ2bmhJOTRWNFRzamk5a0k5MmxsdURVTUJqa3g3ZXg0cDI5THIyd1JqNzg0dzRX +QWxNYkxCcHJ6cHNTd3h3aHEwZGM5VHRZWUlRZlpybGg4d1pFdmpOZVhzVXZoMnNOT3hjTW50T0tl +dXlUbC9oeitPRlRuZXFMT2EyRkJ4YVlVck00WWpDWWZiRGgzbWQ5OVp4UjRCRUQxL0F5clJ6VzZj +Y3h4dm5MK0ttWnBzempoREhWREJPTnVWc0hnMTU0YXlPM21IaGlTRG5FVUM5dTFKK2V3aVVYWEhv +RHExeldxRW5ONHRPb0dBNVhkY0pBSjJMaVk5K1d6aDYxaWVXd3c2ZWVucHF1dTdmK3JEL3J6L3JU +R3NNZG52cXovcXcvNjgvNnMvNThaSDllVENnZWVNMGtDZ2sxM2tKendCT3h6OVlyUW5JSmZWZXM3 +NERiYTBnS2lBTHNWNHpoamxneFVpUmgrRzR0QWowakltQUJWWWo0bmxzWHdjbWI0WFBtR1J6NWhw +Z0t0eDVXdnlPdURrTmNwbUplZDRkTjNnNWt3RU5VK0ZEZmZoN2MrSElJTUplWVltL2VhVkJFcHpF +NTVHRFlnNGp4WmkrODNtVGdTTzd6Vm1zTU5vMnozbjdZbDZQQmFSMWM0Y2c4ZmpSY2JMbTJzYlBm +V3Jod3FHNnhjZVlOaUFOS0xmYXBSMnpDTytObDF4S0d2T21ydGVMTDdXQldINHk0d0puOEJHa1BE +Y2xyblhwaGtjTTg0VnFiT1BESGVMRFJHSDdzVVVjTWw1eldNNVRhMU84QTJVTnQ4dVFQQXBxRUMy +WjhNSi8xdnROSC91QlFsN2plR0hyakNCY01hbmJnV0dPZmRhdXp3U08vSHVpMU9mblRBejJCUlV6 +OUZJdnUxWVlyWEh0emhGdHJyRTNQNEtaQnZJaTNCL25veUx4YzlXZjllVWI5cWViNnMvNkV0LzZz +UCt2UCtyUCtmRngvWHBqRlQ3Wkk4Uk02TUJwNGpETzF6MElrclpnR25NOEo3aWRwWUJFaURrQUtN +dHdKVVVMZ05NL3dFN2RpRUtCeFBnTXNmNHdmODJxVy9DRkFJYkRCNGlmdU5jbU84ODlDVzZOSVpN +ZWc5bXEwQzBFYXNrMmRBWnM0eUZhN2VwaHRCWEliTUtvUkgycFhDNUxGSUJwMUU0SDRHa01zNm9H +RGlPeER2bnFJVWYxcXdET2M0cHZIblhnYXBtYno2cEZMakRUUGV0L05pZStPRDI4YU12QVhjY0Vy +dmpxM3hqR29ldUQyREc5NkNiZEJkT0thSjNyNTlBb3VOY05BTDlicmg5OWpWN2ZQMXFrSlpsclpB +Mk5NRDV2ZkcyZEFlUFJORERpamg3eGxrbE90K3NkTWFtTU93ejc5czg0ZWE0TUZwL3E3aCtWZ2tV +Y2YxQ2lPZXVCUXYzM3lxanQ3OEsyUDBSYU9ZekI0b2pFWGJzVVVSMSt0cFRNNGFHZDFNbGVNcnU5 +NlFGK3JpZUZVZnRoeEtxZmU0T1A4NGVCTjMvay9iSWE3L3F3LzY4LzYwNmcvNjgvNnMvNnNQK3ZQ +Wi9EbkJibUFBNnR4UGlONERUVUprYXhwbmtVTXlKUEE3MkVyUWxPUTc4NVVocUtBVHJNVlppaEFE +dXNSNlRPUTd2YTZnRlI4ekM0R1lkZ2psbUlWcjhBWTBieDhHdVd0aFovS0RaZ1ZyMDRtMG9RWVZC +NXpZbW82UFA2VHRsZ1o2dEFRVFVDNkd0TmcrZkZtUDJ4eU1KZGFySFZ3cVVFejdZZUphSEFHSzE3 +eDRxMkt0YWxGM1BDaG1XcXdUaXg5U0pQMXlBWFRNY08wYlBMakdHYWlFeGNmNm9SRExtOHZQSWNS +QjNJNUhPeFZneHhxZ2hHR1BVd0h2NkZ1V0hBWTNtQ0JUVDFpbWhlTE9RMDU5QXVlOURVR2NZa2ZI +Ym5reFFHY01Qdk1wSGp3bCt6bGhGRSt1SW1aSVQxWHAwTkRQMkNVZHcrbDZhMDQ0dTIvMWpWMStj +NmMzanJSRGYyN2krT3VGclVkN2I2Y1EzTDI0YzZiUk91dGdVc04vTEp2ZytZWms2ZG1HSDEzU0lt +TFN6R3R4WTFuY0ltOTJuTWd6V1d1L3F3LzVhby82MCtqL3F3LzY4LzZzLzZzUDUvQm54ZUZoWFJG +RVpEUFNEcUNlZGxHQUlOTUJOcnNwMTRpY0VXMHpFc2NoaVlBcmlqNzNRMHhDQXpCaE9PbllqbUFB +ZHdGRHl4THpCUU51Q0tRbnp4TVNsaWFZNDIxeUVRSU1tSlFlOU00SWtDYUduZHV5UENHd0R6enFo +SHhuNzU1T3VJaTZnakNXZzFnQ0EyR1N4MXdhUkplNEpKTExMVXdFUTZJVm00MTRrWThuK0Vsb0NP +Mmd4azJGOTZzelI3MTVjQ1N6NldIeHhEZUxKdzNNbUxpUTk2SUNVNTN3aWRpbUZPSG5zSUt0K2N3 +dStlQVZUL0J5NnZIOW9pbEY3QXRwdUdOYnN6bndEQjh4cjkxWXVtVlhOYlRDZnpxejJjeDhNUlkr +cFYrZU80UGhYQ2dScHlKTHk0ZDBxTmZPZEJQR05YdTF4YmsxQnZyeE1BOURHTG9yM3JkSFZDMGww +Tmp0VGR6K0JWRGJiQ3Fod1p4ckVmdWE2d1pETXlNK01hWFdzUldqL3llcVJXZjVueVdTd3o1Nk5t +bHR2cXovcXcvNjgvNnMvNjBUaXk5a3F2K3JEL3J6L3J6a2YxNUlUQml0Z2tRaFV1TWhCMlQxSE9n +UEZNQUVkaERyTDVybmt0eUJoUWNBUWhURkdHNUV4QlNGQWtNRW9FejVOZUl2UWFrdUtjeEw0dEpm +QmpFelUvZ0VSYnk3TGR1RFRwa3lMTnhKeFljUklsMCsySlFCR2tHSE40Y21WUFRPVFRPMklhTUVK +R3VQdlA0a0JlQmlOVU00dkVjTCtJU2l2OVBKbmpVU094cVVaTWE0SFgzM0ZwOTJIcUdPekZneFps +TERmakNvK2YyaVd0dmVINDlyR1lRTEZ3NGN5Y1VuT0NBb1hCTHREQ3ZrVzRjeWk4UHZQWmFLNmY0 +bnNFQkQ2SGpFRzY5Z0VudGkzWHFXKzV2WWhYZlFjOG9kQVd2OVE2K05lbkV4S3ZlTXFlYytQZmMy +eUJ2VG5LNHkrRnREbk5ZSi8vbUdzNnlYeXkvQ3FDWE1PaXZROWozNk00Nmg0VWF6SWNyei9VWlZu +RmMrZ1N2T21DeXh6cTQ1TmVITmVqRWl4N3BoYVp3YkE1R0I0MW52a2ZyZUtRVmUvRUN3OUg4OUhI +aW1Lcy82ODl3V0gvV243RFduL1ZuL1ZsL2J0eUpWWC9XbjQvcXo0c21JZEJHZ1NNQ0RRbTVoQUM0 +NGpUTFJtQzNRUU9XdU94UmFNUXRJU0ZJTG9mbmE2VFpGd0g0U1YxTXczUENWTXdlQ0lQbHhMb3VO +aGcwMlBNSUR5SDJXQU0zZ3hMSnZRbkZ0VGJtSnRLejUvei9FYWxIa3p5SEI1Rkl6OUJVYXdnaVpq +SnZMNFBLU3pDZTRVSmVGMVA0ejkrZXdXdWRQUTR3T05TZHhtaWNtbU1XUnNieENtclczQjgrWWxu +cnU1aTRWMXRxd2drT0dRZGUrZFhuWUZFZkRpTkM2L1RFaFVPNDlRYnY5bS8vQnFmYUdRb2VmU1lp +dFRNOW9jS2p4N0M1WURzeHp5RUlHMTcxUlh6ejlzc25senpteGRFck5Wdmo5K0gxUlV4RC9RNVNC +bVFlejJFeTFLUkdzYXh4aDFudGNPcGJ6R1lmbmVES3ZGcnhsQVBQZmxnOHR5NkhKK3c0azlzNmMv +R0t6N3hpSGV4cTlVWklUa2FFemVXN3V2RnFyVG14eEpUVGZIeG5ydjZzUCt2UCtyUCtyRC9yei9x +ei9xdy9uOG1mRnlCOFFCU2hhTGJGUy9Jc01wZ0N5SWhMNFVBcDZQNUNDak1aU0JiYlhSSDJBSVJr +c1QwbllrRGxZU1RBTlJSUXBnQjIzNnJNbk8vaUl4cE9wbGhSM2dTTmRMa1JSWHhuN3pFWWJQYXB3 +eHNHV0F6MXFKY0FtY21sbWRscndLZjVhUmdjTVNpOFloT1gvT2JVSXE0bWE1TDlxUm5QOXFsZFRu +TXcyKzk3RElvbmJ6M002UVh4MjhPUTdtcVV3MzdjcWlFMXlROFh6UGlTMno1Y0U3YTc5VDViNjJJ +V2hwSlBuZTVxc1M0SFhQb25uM2tEbjNxaE51dmN0K2Q2eXZpMzJMNHpHZzFGNU9ieDdNMldYc0pq +blpqSjRjMmJROFYzdkNTT056NzRWcHU4RGhkWXpUbUEvRDBIZFRwSThNb1krZ1NmK0dMYnA2WWNN +SEl3Szc3VkZDN2tNYWQyZXp5M1ozMHcyb3F4OUVWLzVUUkhnM0RxazM3UUFoelc2Yk43RGkvWTRM +elhpWnJ3VjMvV24vVm4vWm4rQm9QbjlXZjlXWC9XbjdEVm4vVW5mSS9tejR2a2dBS2lTQVVJcnFr +V0VZcG1Fd21BZnBKbVp1UUNZbzg1d1gxR29oRmhFSk83cEFGbXpuT2dGS0JaOXUzbkFXV05PWG1Z +ejFwNzRKQlQ0UXkzUWxiSTdMOFhyNFpIc0FnakdpSVRRK09JeGlBT0l0WlVOU05lL1NmdkdXcXpG +MDlxbHg5VzhSSHZPV0ZZUjlRRzRtRUlIMnFSSitZTEpudndJcVltTGE3aFc5UEVJR2IxRUxiNlZn +enpXYS9NMlM4T1h0Unl4c3ZtaUhua1ZsY0VvMWJyL2VkL2VUYm40SEluRGpIRmlqQjlsa05NZ3BR +WFpuM1NneGcrSXJjUGYzcGlqN1Y3aUEwVy9JdEZPL2dqZHZITmlZTS9XSFA0d0loSHVQVFozY0hu +Mm43Y1loaytPK2pFOFB2L01Ea0k4T1hRM1Y2TVB2REFMSHFwZm4zQkZaN3dJcmREd2Zkb1YvL1ZB +VE5jM3Z3d2MzU3BQdDRRRHovbXhZQURabmtkUUdyem1XNnNjeGNEWnZYakVGN2NSZ2ZxckQvcnov +cXovcXcvNjAreDZzLzZzLzZzUDUvQm54Y0ZJaFFnb0JXdWdGUDRhYmFOQ0JEVXZLRElsY3dlbnox +VGlNK0dQWjdGTkVscVhoTlRHQ0VhTVNnVEl3VUdnakRzSjJwR0VKZklGQUtiOVM1eENSMGhjTWhs +aUlWNDhSRGhQM3NqeGtDcTV4cVV1alRzMU9CZnVEci9IMDF5dVlnMzlSQ1dLd2NIa2NtZnVCRXUz +bkJMekRzM2RXek1xY05lbUNKd2dqTGdFOE1kSjV0LzVoa1VaMkxDQVk5bllod2VCL09ZTFljY0h1 +d2pjaGVERWlzODRZR1JYQXlxNS9nakROalVvd2U0TVEvTEhueVR3MTRpMVF1MTRSa08rOVY0K0Q5 +L2wwSjk4b3NYSFltcmY3VEhOSHVnVFIvRVpBWTErenNHTVkwaExoUGlHZy9tb2hFWVBuem5LOXMv +QmxHalBMNHppL1YwRjNQRHBRNzU1TlVUdGJqakNLOXFTZi9VVHhmMjRrNXU4Mm9TRnc0NGFVSk1o +b1JESFhvQnM5eDRWYk5ZcVQ4NjBDczg3cUU1MmhPci9xdy82OC82cy82c1ArdlArclArckQrZnla +OFhDd1hXYkVrRkJHZ0xtR0dSQklRTUNNQWFiNDFnN29xM2hsbERwa0lRcWdCekdoc1JBT1NaZlVS +SklNaTAzcm8xNkpBa3R6a1ltVUxSaVBPVE43TXlwbm5yNElYTlhqajJRSmloTHMrUjl2Rkg3MzN5 +NXZ0ZmZqVW9nalZGRGVhSkhBZG4vbVZqYXhpOElROEhzTXVuRm53aDE3N05PM3ZNYVlabjF1SmlE +NDBaNnNRSjRhaEZYclVRaVlZWjZsSXZidVMyTGhqeTNXZTFpYTIrTmZjSXpvQkJiV0pheXlENEpC +Z0N4N3VjSjkvTDlrWHRFWXZEd1Q2aWh4OWVhL0dzUDh2NzhHdU4zSExwT3k3VTU4ckFoZHlwTVdz +OWg5Tm5CbUxRYUE4V0dQMnFBVTZKWDA3NTlZdWg1YlYyRDRNWjlQbnpkNys2L1dFZzJNVzJUaTk4 +bHh2Zjlxa1ZCcGp3bzdjNUtQVlRiZEdvK2hrR0wrYnBVQXphb0UxN3hYSnRQd1l6cnYxclpmYktx +MGJ4Y2VNQXNnZVg1bUEySnhmK2ozYXZXM2Y5V1gvV24vVm4vVmwvMXAvMXA3MzFaLzM1TFA2OFNH +Z0R3UWdNQkRGRnBBQ3NPSWE4R05RZWlUMVhERkErMjdjR0hhR1lCMEJNUWtJdTRnblBmdGVTUDJz +MENuQno4aE8rdUREWWh3aGdGWTA0YnpvVTdDMkV4b2xCb0VpTFFZbk95SGNDSUJULzJYdkZQR1B4 +VFQyZWF6b1JXaWRuUmdoVWo5eHFYRHl6RjJhMWVhNStkK1NxQjBZMXJJbHY5UnIyd0NNdmJ1MG5H +dnp2VzVXNTVDY2dPSEVycm4wUmhEbzFWQnk0WExqYXh0NDRrZGNhR0t4bmtQd0xXN0FTT2s3MWVB +K2oyVXRnMjRNUlBiejJFNm5QZW1jOWJOYlNqTmdFbnJXNG9STjdDQXdPZlZDYmZ6SExXek4xd0tZ +Mm1ISE9RUGFxRlZaRFRmLzQzcC9zZCt0ektLaFo3OTJ0ajBHdGUvUDJXNHZGL3crU1h3ZlFPOXpC +Ump0cVkyU2Y5VlNmbU1MYm0vVFJvY3JNOE9CYUxkYkNtZ01NWHZsb3kzY1hqR3BSdjNrWHZ0V3Ix +aHdhOElsanI3czhZcG1UWDQrMzFzRW12L1gxWi8xWmY5YWY5V2Y5cVE3WTZzLzYwNmcvNjg5SDl1 +ZUZJWkJQSkFobnNFK2JkLzZQYWdVenAyQUE3RUVPRWpVSVdZS2JrMVFDM3hYdjhsbGpOZFdhR0FP +b05kS0lDZ2xyenJtc0YxTmhzSm1EaDhqa0pYNmY1VEUwU1dIMndBcVhYQWJpNWRGVVdQMUZiUGtO +Z2xTWGVoblVQdjlaWFA0TURVSmlCS1RaT0Ztc2t4Y25tZ3JqMlhkZHpNUW90blhpSHRFZXJEaElv +OHpqSGdaRFRlck93YUI1dUxLUHdOUzVvcDE5RzJjdXVQQ1RRNG5oNUZPdjJzWDNaZ2h2OHVDSlVD +T3FmYk0wRnp6Nkw3K2NtM2Z5eUd1ZEhNUm9XS01HaGxlclB1UEdIVDludkd3OWVpSWYzUEJHdEhp +U2ozamhFczhhZGVDTXdQR3VUL3FaUDBnKy91aTlyY3Njamd6Y2YvQ3R6eThHUGJaR0hyVTdnT1VY +MzJmclVvZmU0Y1ozc2VHd1Ztd2M0anY2d3hmOHRCS2R1YXRGSFM1NzVNR3hnOEIzZXR0RFpmakVa +ZlJqWDNLcVJ4NHgwc2M5ekdaZC9WbC8xcC8xWi8xWmY4SmJmOWFmOVdmOStReiszTDhENm9GbUNr +WUlpdkRjY0k5QmdaTVlhQVg0TFBnV05YdVFlOTRzWGJkUjF0c3JSaG9DSk5ORWFNQnJQb0k4ZzBW +RHhBWldrMzMyekUveVlpSlUvSmhRRHRpdFU2VHZmc0ltT3JsZzl4TTdFaEN5YjFKbTJLOXVBdE5R +Y2YzTFZQWm54SUF4bVZod3FnbFg4c0cxQjlYVVptaUFKdHVuSVJvbWpscnNzMThPYytyV1BESGdY +V3h6TjBkc0VTSHoyT2U1T2pVWUh2V3Q4Q2RIRGl3SEhtd3dNWkM2MWU5UXM5WWVnc1lwN25mZjdD +RUlPVnl3NjJ0d1dpdm5xZVA4NjJ0d2VCdXpZcDQ5NGQ5aHJOYnR3UXl4NU1laHZvbWp6K1o5WmxC +aTNnTndNTnR2YmZhb3hYbzQ0WWJMWFIzbURmVjdvNlMvRGxuYzBhSzFUS28zTkNqbXUzLzlPN3Nl +UnJYcHZUaGlxbGM5THIxU2J6Q0VVenpxMng3QTg0eUcxZUl5cnc0OE95akVVODlpdVBYTlBPejJX +ZTl0RmcxNlR2TjhZc0FHUS8xWmY5YWY5V2Y5V1gvV24vV243L1ZuL2ZrTS9yeElJSm5DaU1CbmdF +MFNDN0VoWDBKQUZab2lQTE9XQUJBaGhxVElCTVk2RFpORGJBUXgySnAzQ0xMWE00YVd6MmROUUt4 +OTdrd2tyemxGeUtFZytlVXhZSFlwVUZ6Q2haMzQ1RU1PQThDcENXZmZlWk5qRCt4RTVBMEtUQkdo +Z2JEWFE4VWhNSjl4SXA5Y2hPQ05nZjNtRGZQaWVDYVc1KzRhR0FQWWp4OXhOWktSMUJxRHdxWnVP +VFRWcFJaY2VvWXYzNjNCaFRweThCaTRndzBPZkRzRTRMVFd2Z2hZVG53d0hGeXd3eVh1UFg1Y21t +Tml3M2RjK0wxMDk5UmxyMTRUZi9vanA4TlB6ZkphZC9yK3N2ZjBSazczMWMvRTlMdm82b1FOZHc0 +SmE5UkVYN0NweFpEWDRTb1h3OE51UFFQNGJKMFlldTB2YzU4ZUg5MzVYWHhyeEZRclhWdW45K0Vq +QjVUY0xuemJ3OFRMeWZUTUNOZk02MjdPR3RwMWFPT0FCdkZnVGk1L3VPRkh6WjdsRDU1NHg1NzZz +LzZFSzNvVTl4NS8vVmwvK2x4LzFwK3JuNGxaZjlhZjlXZjkrWi9abi9zRHFLQUNBT09oSWpWUFVt +SVExTHlnQWdvRW5DdGczWDFITHRJbEZSY1lPUmhYWW1ERlJoaERLSndwNVVVZ2tXbWd2RVFLRXpM +ZC9XVE9vSEtKYjUrQlNHdVFqVXg0eFJFYk5tOEZ2SUhRTExWRk9FU21JUXd2cmd0ZXVUTFVBcXVZ +R3VqeUU3OWNjR29FTExpeGh1aVlMbnlvQXlZWFFiaWJod09QNnZhWGVXR0xRZUYyaVNlR3UzeTRK +MzZmM2VHQ040WU5INGI5WXJyVTZIZlVjUUN2dkE2RU5jbHZmclc4cDA0OHlvYzNhM0pJUkt6aFJq +L3g1ZGNJeElRajNPdTNXb1BIZmh5cm13Rmh0NSsrNUlXTklYQkJad3dabytGWUhCY01ORVhvNXMy +dDBid1JHMTJGUjhad0tJc2QwK3ViTmU1K2wxNis3Zi9FY0hEcG4wdjg3ZW5rZ05HZWFGZmZWM3N6 +WjUvZXFHWGZpTjFxc1E0UFlxakZ2QXRYOXVnRkRNRnZIU3ppNHNjaGtJTldEK3JQK3JQK3JEL3J6 +L3F6L3F3L281SDZzLzRVKzlIOXVmOElrUWxKMHh3Z0pUTE1DeVNJU3lNazhGbFMrNnhuSG8wSFZF +RUlBdFJhY1pIaEFrZ2g5aWdBOGRZRGs1d2FrVmdBdStEeVU3U1ljb21mUWhqZUdqOWR3MnR0QktM +cERnSWlRSXE4TVdnTURBZVRxY1Yzc1RLUTYxazQwa1FOd1FteGFoSWhFNkRjREVxZytESEhWRGdU +dzRETnVqeGpMRzgyZkllTFdlVEVsWHNPT3JXbHliREFnRmR6TG5IRGg0RVRZb1VEbC81bE5aZG5N +QkV0WVoveHNybHd1MXlPS0dHVVR3NDkxQk4xdVJ1TGJmZ2dicGprd0kzMStydXhiL0hWRmozSUM3 +c3JCN0MvT0s4SGVoVjk2TEZmZjRERkdyM3kzQ0dDVnh6SWExN2RMbkZTcDM3SUlhY0RHaloxd0dL +ZGVmcXczeHNxK09TbWtlQlZxM3JrOWhrMytMUlgzNndUQXc1YXRVNDgzOFhDdFJydDFVYzZoMGNO +K05WLzJxRnJjZVVTVnh3OXNjWlZmOWFmOVdmOVdYL1duL1ZuL1ZsLzFwL1A0czlMM2dCWW9NbCtn +Z1V5aXdTMFJrS2tKZkRIOHhPdG9uTWgyQnlCQWFKaGlOVVFlNUdGWUQrOWJ6TUhQQUhJZ1Z4NXJk +RlVZa1NtUVJRd0FBK2o0cmVvSWRWZUExYUZhVERzMWh2aXdnUTNFc3haazRQQWZKcEN0TjZnK0l6 +a0RKOVRzem00aURNSEFtNElCbjdQMUErenVQWjRiajdjZU82Wjd4cUZjNytpWUcwTWlqOWl3QmR4 +d1l4TEdPM0Yzd3BoUHFkbWwwTXBnekRrd3BWZXlSSGhFb3RZaG53NHg1KzRhcE03dU4wSk1RZUd0 +emZaWnoyRHFoc09CNFgrNENVR1ZWTUVMNWE4NmlOcy9GdnJnS0lMOCtvd1R6ZmVQcWxiREQxMjEx +dDYyNE51K0x2UEpZYmZqL2RtQ1M2YVlXUy82Z0NEei9ZNXNHbENuK3ozWmlrSGlKclZJVDlPOUZj +ZjVNYTVPYm9WWjNrWm51MFRaODA2TmRoRHF6VHVHYzV3YjQwWWVMUFhQQ3h5NjRsbnE3MHhPMjV3 +V24vV256aXZQK3ZQK3JQK3JEL3JUK3VOK3JQK2ZIUi83citDU3h5SzAxQkYrNjVnaXhUcFRRQkRF +TEQ1RUU2c0NuSVJnb0tJUkVIV1dHdWZ1RWpYVk9RcTNuNmY1VWllRmQwMG1IanRNWkNJRkxFMVYw +eDRrSDRNZXQzbTJXOGZUQW96a0dOdkRJcllOZjRRY01aMU1WaGpqN2RJY01rWjgrTkhUdlB5SXgv +aGZ1b25Icy9sOWh4K05Yb3VidmlNTUF6UDhJc2Z6WVJaWHQ5eHpnaWFMb2ZjaEdadkRKU2N2dHR2 +RGZ6SG9BZXpvUWNFa0xkYXhPNGloSDAyY3didThjR1VZcWs5aDRHWWFoUGZQSU82ZHQvVWFZMERC +S2RxeEFsYzU0QTgvMEtZM3FnM0JuQTVpRGZ2ckJHWFNHRksvK3lCMHhzaXRlTkRMRFVST1E2dGcy +OE42bUFhN3RRVXM0c3BMNDdVVGJ2cThOMjg3ekRqbVRab1dPME9NK3ZXTERkdTczc0MvK2FmWHRq +dnVSNXZ6Y09qT3MzbkR3VFBjSFovWUlocER4Tjc3dERFWS9oV3E3aHFoS0grckQvcnovcXovcXcv +NjgvNnMvNnNQNS9Gbi91djRCS1hoV3VhS1ZLQWtNZFl3SnNISG1pRjIrenlab2dBaUI5WkFpdlVH +a0l5TDU1WTNzekl4VndBMmFOUkFHbXMyTzRhQ0d6TXEzaUZ3YUZoREs2QmVkT1R4aEdXQmxpN0I4 +eUlTRzVrMlNPbWd5VDc1UFhNR2cwUkUxbUV0azJmSVNhY01PQkdiTFhJQ2V1S2Fwb2wvcHB3OGlL +WlFQQnBmVVJ2enJwOU5yVnF1TS9XeHNEaXFwOUkxUVRUbW5FT0xqand1dnVuYjJLS1FSQzQzcm9t +aDJFdjd0VW5OczY5T2RtRGN6REhvRHR1SXNyaFNEeHc2dXYyZXVxVEo4SXhJam9pZ3d1SHRHTGRt +bmhpRWpZODlvc3JQc3lweDd3ZU9jVHhEck02OE0rd0RpNjFpaW1lUGZpeEJuNEhoTFZpR0xURmZH +S0pxV2U0OTVleXJkY1grc0NEMkRpVTg1Y2ZmSDE3WmQ2YnREMDhKNWU4c091RnV1WFNOL3k0cklj +SHYzQWE5RVduTU9nTEgrbW5Ib3NwcDcxdzROaDZhL1hIUG5IMDBhVjM5V2Y5V1gvV24vVm4vU2wr +L1ZsL0xsZjFaLzM1QlA3Y2Y0VElCMEpRaEthc1FZY1E1a0MyUU9ZUm9pQUpmUll3QnJWWExIUHVR +RWltZVFwV21HWWhBa0hXSTFRZS8vbFhzUWhBQkVLMnVGbHZuM2hJa0JOcFl2cThZOVpZci9nWVJx +d1Y2bHdFQnFjYXhOUkFKR1V2SEo0VG02WjV3MEJvYWJvR3FBOUhhUnhod1pFREFUZnFYUlBPc01a +YktjMzFEUGJ3WmkxODV2QmozbUVGazV5NGNGa3JEdHc1dUdDVmgvREVNKzl6WXRtM2RjOGdZcm5r +dGxkdC9sTThMb2ovaUdmRzhHK1BtajNURS9ob3dBRWNNWm5IOC8yaDZNRGVnM3J5NjRuZWJyLzAy +cGk0OXBoVGgxN29KY3d1dlhiWW1jY1hqbUcxUjF5aVZvYzExdUtjSnRYZ1lKQ0hQcmRYdHhyVXJG +WlhEZ3ltczBkc1BETUUwNmFYK2QxNjM4WFZiNzBXR3gvdzVBOHJmQVNuZWp3VFExM0c4anNZY0tN +dS9PcWxQeVQwV2l3MTJZc1R6L1ZEL1RTTmEvV28wMzV4NjgvNlU4MzFaLzFaZjlhZjlXZjk2Ym1y +L3F3L0g5bWZGNXNzRUJBWUFoZkEyd0NCZ2N3YWwrWjVya2pmTldXTlBSZXdnQ0pCREpla21vMWdB +dEpNZ3ZRVHZEMTVFeVdIMkFBaUNRNWlJQVNrRUJvQmhVUWt3YWdRSkJDb2ZCcVBQQU54OXNydklt +cDd4ZHo1d2FSdVpMa1R4SnUzMzlxbVp0Z0RzelZpd1JpOHNNSnByL3pxem1FVFE4T2k0V0xDQTUv +ODZzZWZ1UElTbTcwYUdON0ZYSU1PcDNENDdObStxWmhjOGxzSFYzcG1hTEIxOG92djdtRHlKb1R3 +SGFydUdmYkJ0LzJmV1BicURZR29EK2Z5d09iU003WG9oL25zSTA1WTZjREFyMzAwSXFjZUpKNGNh +NnJKaTE4WHNjS3I3L3JrcmNscVl0YmpURHh6Tk9YTmlyM3BwZUU3bytWWEV0WTBvdzE2YyttRi9B +NWhoNVVlNnFXMURLSTNhbGFYL0xDclRWNDlFVjhOYWhITE9uMkRRVS8wVDEyODRZREFoMTZJbzNm +eStTeVBtdFNxWnV2aUkzTnExQlB4NDczNnMvNnNQK3ZQK3JQK3JEL3JUNlArckQ4ZjNaLzdLN2hB +RXhFd0VoQ0R3QlpxQk1JbEVFZ1NUZEpnSWlST3hmaXBXQkgyQXFHQWJmVEVSYWc1c1JSQ01BR0VR +QVZxa1BqMks1RDQ1RUdnUEdMQko2NWNLZDVBSUl5d3VaalpzSmR3N1ZlNFdzV0d3N0NmU1lqTGMx +aTlMYkEyQXdkdzN1ZVBnZVFLWjhpRzEzcFl4RkFuYlBnMXAwNXpjQ0ZmUHJ3U2ozb0pIMGFpeEFO +Y2VDSTArWEF1MXg2Q0l3eHhQVmNqSERHb2dRTzlnUjIySEVCNDg1a1FHY1N2WXNDb0wvYUlDWHNN +U2tpd21jZWJ0UVl4Nmp0OGF0QS82K0ZTbnhGaHk2bEdNYTJUQnliNVYwdnpUQitJMVp6WXZqTVNE +dVRIdS96aXdhaGY0ZVcxanBsWG8vcmt3d3Q5aWVzTm9MVzA2QkJtVUR6TDcvQ3lSdDN1MXNpL3VT +YS9OVGtNNEdQbUdIa1B5OEVodDd2MTV1VTNENWMxTkt0M1lzcHJyVmdPYXIyalVUenB2M3JrOHF6 +K3JEL3J6L3JUbkZqMVovMHBULzFaZjlhZjllZWorL05pZ3dESUpnRGdKQ0cwQ053elFTVndCOVlh +YzJtU241cXRKeXhrdXBzSFFHTGlVYWpHK09sKzN4Wk1ETTBJR0NUNnJBSEV6VUJpYWhEeUZPSXo4 +b2c3Z2lTS3ZhWTRsN1gycXMwZTYrVzFWKzdzY3hkSHZsemVNS2h6eHhERlVNRkRTUEtJeTV6cUZC +OG41dFJpanFqVmhrOENJUXdOeEp0NTNCSzFtbDB3aVdjdDNqUVBCaHpCWjQvOTF1QlVmZXFVd3pQ +NWNMeVFIVnFERzJZOWdWc01vcEdIZVBWVHozYTlOMnczUWFoSFRybWpCZGc5cHhFNHJEUFVJRDZo +d2lCSEJMc0duWmo0WU5Ma3B4VllnMW05QnV6TUtKNjY3Q2RvYjI3Zzl3d3ZjdEtRSHNBSEY2Nlk0 +SXlYclErM2FzV0IranhqU25YUmdUZEkrcndZQmlkc2ZvMUJiY3lLVjNzWVNCN1BYV3JEUHoySmIw +NVByZE1mZUhDZ0h2aVBRYzgvdTg3NE9VVGhGdzhXQjQxKzJ1TUFNNmNPWE91RmUvMVpmK0pkUGZW +bi9hbTIrclArckQvcnovcXovdlRzVWYxNUVWZ0FDeVFET2dKSGhrMElSaEF4bWZkY2NnSHNrMFRE +TlFOaG5vdWhVR1Q0THFtRzJZY01aQ2xXL2dqV1B6R3NXTG15Unp5RmFaSzc1OVlpWHo1cnhQQ2NV +YXhURC96aVdvZEk1alFINjlrM3ZaazFqQVVMSWU3M1dTdFBCc0pnVXFlNWU0T0toMlJ6THVTTHJm +WWNVTVJtcllhb0JVYlB2RUhRUlBrZFl2YktqMTk0NUJSSFhkWTdLTVR4blhIZzJFTm4xaE9HWjBl +b1I2eGlFSkw2WWZ2SDkvNWt4Y2NFK3NsWXhocDA5c2dCbS83b20xN2JoMHU1OU55RmE4Tm5OZUJU +ZmpxQmpZYkVVbzkrdS9CbW5iNmx2M0J2ckRHcHZUanduL1hERlVGN0ZwNXhKNWFlMnF0Mi9NRDNP +c1pzak9OeUdOaXpoL0RFZi8rYm4xc3V4Qkk3djY1aDBDN3R5ZXNOazdnTW1qenlxdGRlL1JJdmI3 +elNVMnZ3WUI4ZTFzQ3pWay9ONnpWZWFVZjk2dElMV1BRS1h2eDRyZzQ4aWw5LzFwLzFaLzFaZjlh +ZjlXZjlhVjM5V1g4K2l6OHZncjZhWUJwaHMwc2dZaE5VOHdGMXQ5NmNvZ1FRVk1NMUZraENBbENS +RWpQam1tbWFZZDR6UDVGckZJTFNlR0N0OVl5dzdmZGNBMkJERnBQNXJISFdyTGltR0FLRFZWSEln +SUZ4TlFkdU1ZbE5mV3JRWk1OK3hpUVNSUHB1alRnUnVyd3dJMXNjOHdTNHdwMkdxUlg1M21UQUlE +YVNmWmZUV3A5eDVtSWErd2hXYzZ4UkQyN2t4OHNhNzNhZzRCVStNV05PbHpyeHFsZDZzb2ZkN2VB +eHJCVlhEdHdTdnp2KzVOYXo1VzVpNTJEWkEzcnk0c1FkRGowZ1hQV3VKdGFnTC90Y0gxMWk0a2ZQ +N0xWR0xXTDZiRTRzTmJqakVPWmRNL01yNE9IRkd5RTE0NURRWGI3amNBMDk4ZkFrQms3MUpRZnI5 +bXRxc1lkNTFDb2Y3TmIrOUR0ZlhFN2s4bmFJUVQzSGdkd01xa2JQOElBN0hPajU4ak00OGEwZjFz +c0RoejB1MkhLd0JEdk1obnJoVWZQeVBQcUV3eUdZdzRRT3hMTkhQakVkb3ZWbi9WbC8xcC9xcXov +clQzY2MxcC8xWi8xWmZ6NjZQeS9Jc3hHd0dBRkFwa1hHL1J5QTVoQm9uNkFTQ0tTUU5CbHhoT1lD +ZU1VekJZWXN2NHJnbVdLUmF3OVNGUzYvZlFxUWh4QVFSWlRta0M2bTd4cU1ZSG5kN1luQmZUY2Zv +YVY0VFZLWElUNGlIRUtFNERNeU5YcXZFYUk1bUdHeUZ4L1dNUVpCNGdYUjFoRlM4S3FGRWZBb0pp +eXc1eTFBbXFodWZOZ0xqN1V1K2RRaHArZHF3N1B2Y3VZZzFXeDM5UzBQTTNDcVZ2andwRS82NVZB +VjI4RUs4d3I3eGxOd2htUHg1RklyRThBQW96N2lobG1KenVHazN5NVk5SGh4ekJwcllXTmk4Y3pq +a281d3RQWGUvbkRBZ2Jjejhza1RFMWhIc0ZrdnB2WDNlbGdkYk9HL1dzNzk1VzV2SnRVb25uWGVq +c0dBazUrLys5VlBmdmE5TCswaFJRczA3QThIV0QzRHRXZXd3WnVhek1zTG15dHpjRVhIRGhubWhw +MVdETDNFZ2YzdzA0Z2Mrc0NjZWtNWDdxbEhMUDZvUCt0UDJPclArclArckQ5eFdYL1duL1ZuL2Zr +TS9yeWt5SkFoaUVra0lkUWk1Q3ZFV3M5OEZsempCUVoyQlhKckNCSTB3ZDA2Y2RNOFRWZEF4R01v +RUNISWtjTnpZa3lURVFVOEhOYktwVWtheDBRS1JpQkM3R2NXaE1tSENHdGQ0b3V6d3B4aEgvSDVa +N09aM3ZyRk92Vm43SjR4dmVmMndtMGU2VENxejNObUl0bzE2R0F3aHh0WTdGMlJ6Zk1WNnV4WGJ3 +NHI2M0lvNE1saElSNGU5ckNaNTU3SnBRNzc4T3daL09xR0VUZG5YUGVaM3JsZ1pVNGl4TC85bm9z +YmNlVnd3bkc0c2xaTjZiazE2ak04ZDdqZ1RVMDR3RjhPS1dZUkh3NTU1YWV6SEhSeVdhY09vbVln +YjI3MEMxOU14bXcraTJtZDlXTGd4ZUdBRy93dWR6Tm9oY2x4S2w0TWFnMVQ0cG1XNVB2eHQ3K3d1 +dFVYaDB3T1NmWGdtVVpoVjF2NnFtOTRYczNQSHZ2MUFJZHl3NllHM0szQnAwWkR6ZkRnVnI5b1ZT +eDVHQlMrOEwzYUcrN2dYdStNWnVyUCtyUCtyRC9yei9xei9xdy82OC82ODFuOHVmOEtyaThTK2t6 +d0F2a3AxVWFFQVVtWW5pYzRrSjRqQVZCM2UySVE0SHdtQlBjMGVrMDdZQkJndnlFdnd0Y00wMmdO +V05HYUcwSTM1aGhlb2VJNUdId25DQU94OHUxOTFtc2NRaEdYaGlyZWMwTEpzTWZGVEs3RnpLQ0RN +eU43WVhDSHdUek8xSXpnbUlDSVl6SnJJMXgxQlp1bTR3L2ZzS2x6YTUxOXhLS0JPSXBCMVdsT1B1 +SmdMbk1FNnhsYytMUG5mbGhIclBMTG96N2Y0VFNIdzhQZnkydHU5Y3NCWXc0QzYrVWhRRFdFYzNk +OVp6NjZFRi9mMVVZak95WXVYUDdsTmZseVdGb2ZEdWxDVHhpU2NjVFFBL3B3QU1pUEV6V0l1elhQ +UE01eEk5WTVxSThPbUpvNTNQRVB0NWdNWXAvaGJjMEgzL3I4Y21PUFA1emtWcE84K2lFR3ZMaUcw +WnhMWHhrSFpuRndKYjg0c1BwVmtQQ2hONGE3SFBxUEg3WERnai82a1FmWDhJcGpxSm0rNnMvNnMv +NnNQK3ZQK3JQK3JEL3J6elBxeitmdzV5VU4wQmhnQkJPQXVBR3dXU0htQ1VaRGlSL2hRTVJRZ2dF +S25PUkVJN1pDUExPSEFEUW9KaE5URTVHb0daN1pHK01iaEdpUFhJb0lNVWlLRU9TRkU1RnJpb25M +TElvVkQ2bWFaTTYra0tCT1A5SC84NC8rYkgrSFdrNzFJRExEdm9oY2ZualVJb2E2eE5OUTNIbXVG +dVRhRXo3VjVabDhPTkJVYnlUVVpVN3p2TlVSVXg3N2NHZE9mZ2VmSHNIbmtoZEc5WW9sdHJWeW43 +ZGYxNjNWb1VmRTFzSWhwOWo2aGM4STJ4NThXV2RlUEp5cHdUNjVDUXUrY0NjM0xHcGZqUXpuc0JK +eDNzRFJpam4vQXBpMVB1TlBiYkRpSy8xaUVQOTBORTZzWXlnSG0vVzB5T2lwZDNtZnVSeVlOR1RJ +N1RCZ2R1SlhDODV4NE0yUFBMaDBaeVM4cXdjMjY5WFAyRFJtSGc3NHhOM2VqYWJONFM1L3lPQW5H +cFpIRFhvaG5qeXd3N3dHbmQ1dm5Na2pyME5hVC9DV3c1aW05VVZPY2VyUCtyUCtyRC9yei9yVDUv +cXovaFMzL3F3LzFZTHpSL2JuUlJERlMyaWg1QnFrRUFBaU1rVklJZ0Jpa1NNQXN3SGpKOTA4QjJS +Rk1lYlFRS0xUWkhjZ0NOMmMrelp6Q3ZUMlFpd2tXT015TkFqR21FUnpRaElCdURRT1prVXAxRnBq +bXpNMUlkbGREUVJBakFhTTNuREk0VitSMGhSNEVab0JCeUlKUVd4MTNKdE5iTS90MFF6RVd5TVBY +bUhDaWMveTJHdGVIcy9EbytHNWZEaTNYcDN1OS8vSjJscnpjbnNtTC9QZzd0UjFPSUdMMk1UWFAv +ajkzamh1eEJBcmE0a3JCd3VlSXN6dDAreU5vS3pCdFdHdEEwRHZhY0o2dmJGR1AyTlNOVE1iN21G +dzdRRStNVmZVazgvaDdJTFBlcHo1dFJFYzRVTXVQTGpUZ2xqaFQvMG4xOHZ1L2ZBSGY3eTFPY1RW +Q3E4OS9wVTArTVNtSGZuRThWMGR1STZSOUJObXRjT0haM0ZneWE4d1dHT3RldVJuS2hqcENTZTRs +OTl6dmNxQnJHNDRhVVVjYjZKd3h6LzR0dDVGWC9EVm4vVm4vVmwvcXJIK3JEL3J6L3F6L3F3L244 +V2ZGODFTd0lJWkFKSklxTmsyTUlYRjFybVFxb0V4akRWK3VnZGNFWXdPZ0dMRkFnS3gxZ0lxanpY +SVFyQUJMSkxXb0xOR0lacG9LQWhKYVpSNVpDckljSkFnV1Z5Rk1oTk0zcElRcmh6MnF4RnBSNWhu +d0NtdmRValZGSHM5enhCWHJmQm9panJNKzI0T1ZrVGl4QUVGcjlwaDhUbWMrSzZaOE12bnVZdlp4 +RERFaGRjei9NRHJic2dyRG54eUw1OFRYenptZ1EwWFo3d3NYeXZ3bStHSVRJMXdxRWVzcy83OGEy +cXdXQk9jOXNCbExVeDZoR2M1N1hIaGt2RHNzOFkrWnFVZGQ0TUdWdXlEV1R6ZjRjV0JmWjdEcWo5 +MDVEdTkyU00zTHFPZG1Fdi8xWVliYTFNMy92N2h1My8wcWtsNmxjdWRjZmRnRy96aU9nQmdVVGVq +cVNONnNDN21nMDErOWVORXIzR25kbmNheUI4eXNQcERUUzljdnNQbUR4MjZsQTllOGMzWnp6ZmhX +TzM4SlJlY1l0U2Y5V2Y5V1grR1kzbXNxVC9yei9xei9wU2ovcXcvSDlXZis2L2dXZ2lNNWlTb1JU +YTRGS2xvcHBBQW1jZ1d3RE0vYmZ1SkhJRUtpOENJUTJ5RkE0bGtCUU9sd0dPa1F5enhJRkorYTVC +QkJQWWhRWEhtNE5ROFJIaHpzdVRNM1Z5TWE0MW5SQUNuNHVGQXZqVzdad1pEcVFVV2h3TVM1Vkx2 +R2VjL3NYdW1GbXZnVUljY3VCRmJET3MwV3pQVkw0ODRhZnA5SER4YkZ5NnRONndYMXlXUHR3Zm1E +WHZ3NHk1VytJaFJDU2QxR2VhelhsNXJmSlkvaHlOam52R3lXR0RGZldxVE85L040NVB4MWhBalNM +Z1pKUWIxM1RyYWNjR0RkL3FBZ1Y3Z3dKUDlPei9jdzJYTzJ4VzR6ZU5QZmx5Ymp4bXRwekdjcWdV +bXVReXgzL21MMzlwWTlHU1BaN1RDdUE0OGVoTFh2eElHcjF4cW9DbDE0Snd1cmNVQlBQNWdVTGNh +Y0FRRFE2dk5NM3c0bE1TamJiekRDTHU1MWZkb0t6VllwM2Yyd2lhblBYREpLWloxdEZ4LzFwLzFa +LzFaZjlhZjlXZjlXWCtlWWI3K2ZIeC9YaFRQWEFnUmNLOHBDaUQzR0F0b3diYmdBYUZ4QXZpSkhS +aytFN3cxVEsrcDFtaWNQUXIxMlhvTkF3aEFRd0VLOG15RlBDRGROZEFhVjBTclNJMVVsRUpXbEdO +R2VCVklxQWcxSEJqaUlNUStqVHVId25uRGdRUTVZZEp3aERQaENtZ0djdTNIa1pyVXFTYU54QXM4 +WWpnRXhCZlRNMnZoSUI0R0lpUTFhSXAxRWNCeWUrUFhVSS9jWXRpSFU3Rzk3VkkvckxENDdIazRF +Vi8vY0pBaGx6eHF4Njk5K0ZlZlo3NnYwWXd4UzdpU0YzOXdFcUk0Nm5WRlBQaUdHVDZjTzFoZzJG +NVBQZUpad3ppMHBWZm00VjlOcVhmbURYdVlBeTUzV01YUUQzeXJVUjE2WXg5ZVBhZEJ6ejFiVERP +czlhc203bkRCcmhaL0Q0SnhhZE9jdjZEOTV2dGYzcjN3d01kQStyVjU1ek9OMnEvZmVKVUR0c1Ey +VDdQNjZxRHpaa2VOY3VGTlRGelN4QnAwTU9zWkhzT3oySExoMFI3NjlCeWZ2T1I3L1ZsLzFwLzFK +eDNWbi9YbmFrcTk5V2Y5V1gvV253L3V6LzB2b0lEdjRnbUNBSTJUTUFMUlRNVWcyR1prV1k5VWF5 +UWhja0VCUTl5OVFEeFBjZllxekYzRGlZL0lQQk5mWE45ZG1TTVd1R0FWejErRVZWQU1LcjdMM0pL +MmNjOWZKSFpwT0NIQTZtMUFocHA4RjV2QXpmczkrUmljME95RkNYWnI1ZEJJalVsTmlGUnZESTBM +c1NKazg1b0hQeHpCSWs3cVU2dTF2c3NoSG82c1ZXUGk2c1UyZCtLcDAyVTk3bDROTjhNYVFySWZW +dnZUTXpqRWlyRGwxdTgxeHdnV2wrNDRzUjgrQjVHY0JHYVk5NVpFckdDV1R3M2k0WTV3R1lubzFR +TWpiaDBHdUhGbll2dmhzczY4Q3o2NDFTZXVIdm1PUzdob3pqNzkyVU42Qm54djNuNXJhOGZ2NHBs +NSt2WXJDc3lqaC80aXQ3M3FwUmNYN2F2SjJqMEE1aTRPdnVGVWkvVTR4SWw1RjI1d2gzczF5Z0dy +di9NUkhlSk1YbXZWcGc0ODRVNHRhdEpydWNSd0lManNzVVllZWV2UCtyUCtyRC94VVgvV24vVm4v +VmwvMXAvMC9haiszTDhEaWh6Rk1aZlBFZ0prd1lwclFBSE5jRFl5TFlJVUw0bDFFdGhublhtQ1VM +aDVEWmZZTThieTFzQWJFazNWVEtBUkNvdDg4Z0JyTUlEOGVTYU9mM1hLZHhnSndYNjU0V0V3amNs +UDVnNFVCS25QaGJ3ZEl4RDVmWGNSbThhSnZYRnZhOFN3RHJraEdRWjFJUmsybitXUVM0MndxWjBC +Zk5ZRWdwQmYwK3doeG52ekd1YXMxMERpaUFnTmNYMjNkZytOdWNPRkgvdHc5NHA3QnR4aTdmb1JI +dHpxeEEyOFl0MGJXbDlURjN3TUthYjFZc25wUU1HejRSa1IyZ1B2UFErNFp6eDNmU1prdGNLVFF3 +eTNleEJNWFB2cGpqYmdFQU0rR01LYitQcFBvK0xSaTl3dWNReHIvT3FCUFF4T2kvQTdKT2dpT25m +QTI0OFh6eElURm05KzZOZnpQUWduSm4zSmdXZHoraWttelpnLytlZUFuWGowZ3dmOVZaZWhEbnRn +OWRrNi9LVVBlSVkzdFlwSE8vUldmOWFmUnYxWmY5YWY5V2Y5V1gvV24vWG5zL2p6NG4rSVNhT1Jh +Uk9TTEVLb3Bnb0d1SHVlSzFCQTYrMlR3Q0FFUkhwdXI0SUlWNUVBMmd1UVlqVXRCa09JZlJHYnd2 +M2s3N2xudmdNUHAxOVJzRFlHVmJRYzNnYjVKNmV0UVpoR20wTzB0ZmI0NmQ2Y3NRZkdIQ3pxUUpU +RzRXTng3WGpaeitxQVcwMXFFTjludU1Xd0h5OXl3U2tQNDVrWG4wZzAzMTdQY0lRejM5V2FmQnFu +cVd1SWVSWVRxNUVaN2NHbmZmSllLN2Z2ZURwQ0dlMlBDV0NFbWFsZ2hGZlBQSS94MUNlMkMzZHdw +ZWZ5RXBWOThIaEw0aGx0NEU5UEhMVHdoQWVHd01IR25XRVBMdVNGVld6R2hVRXNQWW5vcldGUXoy +RDJwbVY1R041Z3NOZThRMEdQNU5KTDg4d3VKNDcwMzNvMXd1VHVMMnpUcjMzMi9PVHYvM0I3cldi +YU1JZGJYREtyM3ZHRTd6aFJFMHpXd2h1RDJxTSs4emhYbzM4aUcxYUdwMWZEUEgzWnIyNHgxT1o3 +L3JCU20vMjQzemRvTTJDdlArdlArclArMUVNNnNsZGVHS005Yy9Wbi9WbC8xcC8xWi8zNVNQNjhJ +RWRDd0psTWNVZ2tiZ1ZxdkVSK2Y1a1F6Rm1yV04vVFVIRTBEdEhXSzg0YW56VlZVdkVJeTkzNkpY +ZkVBU3doSnhkU2pzaytlVFd1UERHcW4vWjkxeFN4WFNGUWd4UnFFT3pPRFVHd3FtMUowOUM1WW5v +NFlOVTBwaUM0REthQkJRNDErZTd1TzdMTndVVllFWjhHdStCSlBhbmRQT3c0OTlsZFRMWEE2TEtI +MkdDQzM5c0NhM0VyYnNTK25NeG52V0k0K1kwVnkyQXlyeGJQMVVVTTF1c2ZMdGFjMHpQemFwRVRS +amw4ZDdjV2RoeTVZREhNRTZHKytReXorQTdLUFFBbjlyNjFtemxyWU1HYlhPSEJHdlhqa3VEcHlE +T1l2WDJSRjNjd2lLOSt6NW1IcWF5MzV0UngvbUs2ZVhvUk16ejkrTnRmV0ZNeUNWNSsrY0hYVjg4 +MG95WnI2QkZlLzNxWVBBNEEvWFBCb3k0NmhVTTllb05EdVdoYm4rUlhEeDVodEZZdjlGZDllaXNm +ekdMSjUwQmcwSENPRzdyTkgwNzFaLzFwWGkzMVovMHB2M3Y5V1g5YVUzL1duL1ZuL2Ztby9yello +QUJFTUtpZ2hzVitnZ1plWVg1aU40ZGd4UWdLa00rZTIyOVlDemh5L0lTckVSR3Zad295cjBCa1Ja +aCs4bGVjT0FwQ0RCTlp0ODhHRDBKOXRuWWJQQU54Y0NyS0hzUkg4SXpwTXdIYkYwSjMzQXdLdjUv +TzFlNVhGSmovaU13NGJ4M3NnY1BuSEJKcXMwNU8rT1hYU0hPYVk3M25jSzdBQjM4YWhDOFhiUEpx +TUR6V2lHbTlucGhqQ0kyVTIzZmlVSXVHK2l6K0huQlRnM1VadU5yZURCNDgySXMzL2JUZkhONElR +WDlnOFl6UnhUS252Mm9YQTBZeGcxV3RORURNOW9xcGJrTEZHMkhxdVRoaXdLOSs5ZUhRZW9OQWNT +a1dRZHByRDZ6MEVyN3N3YW1EUmg1dll1d1RGMzc3Y09iUXdBMGRxRmtNL3g5Si9sSzI5YmpTWTFy +RGk5ak1xQTY4cjVIbnUxelJqenB3cTllNEVGOXNCNFNjOHFzN2M4RWhObjd0VlovY2VGT0xYTGhJ +Ym4yUml3YkNzZjA0cXovclQxZzhxei9yei9xei9yVGVxRC9yei9xei9ueFVmKzRQb0lBVHIwV1NH +SWdCQ0FnYkVTRUJzV3VrQXMwVGlHSWlmUHNSYmIwbVNLaFp3TWlsNlFpV1hEd0RXRzhYWUVoTWN4 +ckY4T2JOaVdmZTJ3SDR2QmxBanN0YSs4UkJySVpaQzJzRUJHTndFajZCcXc5SkNQM3BkNzY0di80 +QTd4blgzUjh6K0t3K2wrL0lEaythNERNT2laend4TFpPemZMYkIxdjR0bDk5c01PTG40aENMYkRH +UUw0ekRpeGlCUXNCNGxwT2gwUUczdTBuRm5ud3FVK3BXWDY5eEtGNmNTeWU1M0xZQTZjK2lRV2pH +amZIN1BGTWpZUkhuUEFSclhsNDRTWTB6OFhCRVR6cXMxNTh3eHdPYU1KK3NlWHlSc1VlOGRSbkwv +eWVFN1Erd3luK2VVTjAzZHI4bi8xNnJvOWk2Z2xUK3pXRkhFNE9Zam5oU004OHp4eWV6Y09LVndl +VHozQlltNE1DRnZQeTQxSXRzTmxQVDliTFFmc09EUEhWcmMvNkppNnM5cXhPaDFNMXg2QnFxei9y +ei9xei9xdy82MDk0MUZkLzFwLzFaLzM1RFA2OEVJL2lOVVpnVGZJVHNVQStNNXVmYUFVQ1FxTjls +OXhuU1lHMTM1RGNaOC9GQm9JQUVDR2hYSnBxamhBMTB4NmlVYnpQaUxhUElLd0RXTUdNWkwrM0I0 +cXgxeHJ6aTNmRXBIRkg4TWY0aWhkRDRlN1dHR29rTURFamtBL2YrY3ErUGJnZjRjQ2xKdC9oRkVj +OXk4ZVFyVW13T0xod0V4UGdUWFB0TVFjclRENnIvOU1ENWZ3bmR0eUtxV0h3cnpqbWtvT0FHVTVl +T0hCcVRjeEtDQWErSTN5NWNHYXQyRERoM3p6K0RMaUpUZ3dYSEdMQmFLL2NPR1VXV095akJYZ1cv +enlEUTgxclRnZmNDRXhjOGZ6YUFQNXhDSWZZOWpna2NRb1AzbUZpYkwyRXh4NmZsNWZCQUF0VGlM +T0h6ZWpWZnIxMHdIbjI1dTIzbHFPc3d3MXowRTkwNEZkUmZOY0xkVm1EVTMzTFBualU2RE9zTU11 +bG4rTExSWWZxTStqUnM1aU5KMkRHQjN6aXdTK1cvcnJFdEovMjRpZTZ3cHQ5dkFDemZmVm4vU21H +QzQ3NnMvNnNQK3RQbk5hZjlXZjlXWDlHQjQva3p3dFNBU2NnQ3lOdWk0SFJVRUtXRUtFeG9EbEdR +cXBHTGRCcGlqaEpGbkZLNW02djRsYm9OeUJpeWhrTWZxSVdpeGdRYUo5Q0VDVWZFcndoSWpadkJZ +aWNPT1JMWTVCZ21DTXlPQkNvQ1hJYlNOTWtkYWxiVGQ0aStIekcrWXZHOXNERnlITEtBWWYxcmpY +SDFCSzhHZ1EvSVdROVBHTGdpdURzVTRlNnJZL0ExRUhBMWl4SEU4ZGV6OFdTVjVQTmlXZHREaDdQ +NWM4d2p6TzlTRHlmcmRNdnVCMHFodjdBSVk2YzNucmdUajcxRXhxeHFoRm1lSzNGQ2I3TndhbFdk +VENadnU3QlBMenJGek5GN1BEczI1bkpnVHNtdDhhOG5vcm5jSUZadnVWbDFzS2laMnFBRVovaTJ3 +T1RIUDZWTUpvVzB4bzhNNmp2NHRQQ1AvM3dUMWNYc01LaTczREl4M1RSbUhvOW96K1k4SmUreXlY +bTBlRWdtSHJWajNmcjdJY1hGMnJPV25Xdk5vWlR6L0VOaS96OFFjZDhvUjYxMVovMVovMVpmOWFm +OVdmOVdYL1duMmVZcno4ZjM1Lzcvd01xb0lVS1EwU01JWUdOd0dpcWpXa3dNTURiTHdFU3pTTk5M +TjhWR29NR2xBTE0yZWU1WEw3TGcxQ2kyUVpPTEFWRUVHTEI1M0pnaU9kQVVJVDFhc2ljUFpxR0RN +OWhXaU1OTGdRWTlubHpvV0hKalZTaWkzQU4yUEFRRXRNa3pTWlM4MkxMcFJiQ0VnczM1dHpsRkFP +UE9NQ2QvZUxCcXc2TkZBOHVZakNIYXlZVTMxenEwR2gzSEVUd21rc2tHVERBTEQ4K3hKSWYvL0ty +Z3dEbEpWUjE0MGRNZStIQXAzM3FFRnNmNUhMQno5VHdwd2VlV2FldlloTTJYQTVCNjhTbGh3alZu +RDNxd3B2UGh2b1pYNDFpaVk5YldMeE53cVcxNmpuOXZHNCszLzBGN0dEVEoxcWxIZkhVbzM1dmlP +U0RUNzM2am05NXhMZWZvWHpIT3p3NHhJOExYcmxlRFRyOW8xZjVjQXZmeDNQUTR3MHVHT1dUU3gz +dTl1Rk1qZWJjOFpFMTRVZk0rclArckQvcnovcXovcXcvNjgvNnMvNThGbjllRkIyaEFXK1RaTzZD +U0pha0NnTFFtd05yRldDL245b0ZScjduaUpEUU9nSUt5WUNLNWJraXZVSFFkTEhORVUxSUE5SmhB +WmRuYXg3aUh4TGtzNWVSQ0o0SXhiQWZ1WW9qbEJSSmZNUWhON0lOOVJHd2VPNXdFNUtHZm1yUWw2 +MVBmSFdMSVovYUdKdDVJbjU3WUJaTC9UN0RwSVkwMTV6OTZpQWEzK1VrTVBYRW9OYkhUQnBzcnps +NzhCWU1udUZjVGZMQmNBejN5ZUxERXo2c2gxRStIS2dEeGh5ZWNzdTE5VTNQN1JWUGZQdUpWMXhy +WWNNZEE2Z0RmdjF5d1V3NzZqSGdrazllL1ROdnI1NktwVGVFYk0wYWVIb2hqM214NVpXUE5zU1hp +OUhna2g4ZllzZ0hyeGd4YUE0NDhlMWpQRHpxaVJocWhRRkdjZVJJWGVab0JnNDg1eEJNblhMcWc5 +aTRoUm1YbnVHVlh0UXNKbHhaSDZPTFo4NGU4ZmR3SEx5ZTBSUzlxd2xXTmFpMy9xdy90Nzc2Yytm +cXovclQ5L3F6L3F3LzYwOFk1Y09CT3VyUHgvRG54VWFUZ2dyT01CR1Z1ODBSS0FGWXcyQkx3anh6 +S2RoYXBoQkxnNUdqY0hIOEJBMG9BSnFnTUlLUXl4N1BmWWNGT0ovdGM4a1BOSUxsc1lid1ZsaERq +Q0xrSWl6aXR0NWdUdnZOaTRjSVFpQjRBem1hQVd2d3FBdXhHZUlqVjZNaWFsamtVNmQ4ZTJqTlp5 +SlhoNWlFcUFuV2FxelA5c0FtaDN4cTJlK3pOdUtYU3d3eHJaTlRYQ0l3aDAvUGNSM3UxZWV5NzVq +RHVMNktRZzRIbHpYeXFrRVA0SlFuaHhsdVlMTFBmRVFVVXp1c3JNT2Ztb2plbkV1ZE1EbmNqcmpP +a0ZNTmpLTi9PQkRYcGZjMElTZGNMblVhZUJVZkZ2ajBTZzlnRnd0MnRhWHYrTk5yZS96NlFZemxM +cTYxK3F3ZTYvY04wVTBuMXZqOWRjL1ZxUi91dU01QkNBTmp4Z2Z1NGxtamRnUDM4TkdSbUE0WWM5 +RzN2a1VMakNxZXo5YkRqYWYwMlRyNzBzUDZzLzZzUCt0UE9PdlArdE5WZjlhZjlwc1hyLzZzUDlY +emFQNjhoR2dFQTBNUUFzUlF6R0F6UXpLb0praG1MV0VLaW55a3BwQTB5bDFEWTNyRkFhUVFuODBw +VEF5RWJaeFpKeFljWWlIQlBVMzBwc0d2RnRpckNHdGQzaHBvbUdiWXEyR0lSanljNW1DT2dNd2pP +WVp4MXlqUEhDaUdldUMwRjBjNFNEd2lGazhUckxFV0RseHBzalhteGJNR0ZuVVRseHJFczA1czRz +ZXQybkd2TnMvRndvLzRNWng0c0d1aVo4VEFnSExabTZGMy9tbHh0Y0ZLVkhMaTJkMGxqdDZrem9n +U1JyRmhnRkY4K1BBU00raVYyT0xCalJjeFY3RFd6bUFzdVduTU9yM0h2NzVaUTZCK1A1M0d4SkxU +WUd6LytWOE42c2VIT21HQlRVemFjcWNOc2VpVktmTHJCL3BwRDc3RWNoQ29EVDcvWDFyd3F3ZHZm +b2RlRFhxVStBNENlRnd3NDFzL3JNZVZkZXFTUXh3WTlZZEdjSzBlK0hQSVcrc3VUK0xxRjA3a29n +MGFobEV1OFh5dlArdlArclArRkY5ZDlXZjlXWC9Xbi9Wbi9ma00vcnhJYUxGQ3RoRXpFQnV4S0JR +WmtucUxvVmhBckZVSWdBZ1dPRWEwWGhNUUtoRUNOVERpc005K3BHcUFHUFlReUpwKzFzb2xKcEZv +cENJVXc1eldyWW5tV3F3M2s4aG5qVGNrOXF6Z0pwYUd5eWYzNHBtQkJITkkxRkFOOHhkMGoySE9F +QnNlSWxBakxzUnhhYVpjT1NEZ0ZWdE0vS2hIUGpXcjB6Tk55RUVRUFBnM1VpOWN5L3ZVc2dabG5z +R0FJekZjOE9pWjlUN0RyRlo0MVc2WTkwODFFNGI5K21mTkhqS3pEMTl5NlpuY3hHQWRMdUVTVnk0 +WTFFUm9PMjZjNDB4UHhNZWhOZGFHWDRlT3o1N2hJQmQ5MEVZT2M0Y0lUc1RDbzZGZlRBTXpqTmJx +Wnd3S08yeDY0TGxjRGcwbWZQUDJXN3ZYdkRsYVkwaUdVUnVjL2lucTFkQXRsME1DRnYyVVF6M1cr +NDRYL01DQlozMFRSeS9GOE5ud2h3SVBxUVUrdFlxTlgzZlkxTTdzNnRRZitOUkFlM0xCcHMvNGRh +bS8vcXcvNWE0LzYwK1k2OC82cy82c1ArMnhUcXo2cy81OFZIOWVOTXRpalFOU2c0RUJUQ0RQSkNP +YXZCa1FqTENRWloxbmtpRWVTT1RFTkRGb0NOWll1WUJXdUlhTEN5Z0NFS3JJTFdRS1NpSDJ3WUtV +SlhmMklRV3VGZGZOb0FSaHlML2l1b2xZc1FRSW8rRmc4Tnk4Wmxqcllxd01hKzJEUTM3WVZ0dzN3 +eUJVZmdLeUZrN2NpT2ZTVEh6WWp4TWlKSDdQMUxveDRSMStJbWE4aU8wZzhoMUhPTkFUMk1RaElM +RmhFWnRZOUlBWWprR3ZHOTh6QnNJRm5QcUlJNzBUR3djRy90VWluaHJFeThHbDF0WEZZRFFZTlZq +RWxpTWMyMmYvamxsdmJYU2ladzQ1Ni9STnZXcDFXT0ZZUEhFTWQveXBUVHhpaFE5K3NmQVJ2ZENK +QVJNOC8vS1RQMy9Wa2JyQ00yejZwRC8rdjVSb0VMZnE5SC9raXc5WTVhQW5lK1RjMnVjQWdJWEd4 +SUZYZnIyRXhSQXJCNGc0NmhFSHgrTGdDUjRZOUdKMU5mNFN6eDk2OE9MRHBWOE85ZlM0L3F3LzY4 +LzZzLzZzUDYyclArdlArclArZkFaL1hwakJKS0EySWlRTkF3SkFQMEVqZ25BVUVDRUJBalJpRUc2 +TmVjL3MxZHdsY014azNuZU5VcGprd0Fjd0RCRWRFU0pjWXhTQkJJS1Z6NXptR1dMQ1p4ODhZaU0r +aEhwbTNuNk44WDBQaFJraHpqUHg3TnNEWlBDL2poRVpYdUJRVCtLcDMxN04zeVpNRFJ0dnNOdVBE +MnZOeDZnYWxzOXlxVmNjKzNjTVJ6RDZyblpOMDd3OXFLWmh6Q2NHYzZ0SERCZzBVaHlDZ2lNR3Rk +ZWJFbHpqUXN5UFB2ekdDc2g2K2UxbElvY0xYdVhYZjMxZVEwOU1uQlBUdlVIaHNWNmQ0bG12dC9i +VER2N0ZsTk04VHF4blVHdlZDaXVkK0pVQ2M5R1ZPdFczQnAzZXJ3WUcvMnByOE5DQlhIaXczM3FZ +MU9JTjM1dnZmM2wxSUdZTWlnZUhveDc1N20yTnZUQ0lEWmNhWTB6eHJkZHJmTkNNV3VDTGp1UzNS +ai8zRDR2QkVBMlp0MVlkZUhCWG54anlpU3VYK3NSaFh2cWlIemh3cEY3ZjY4LzZzLzZzUCt2UCty +UCtyRC9yei9yem1meDVNU0dZRGNqUWRJc2tRcTZnQ3RTVWlDN0dYU0RUYUkwQ1VrQ2dKUkFqSkNC +S3crM1I4SWdNZUNaRGd1Y3dJRnA4bjVsRGZudVI3cmtMK1laOWNpREoraFFhQWNnSHQvbVFHNkdK +Q1QrU0VHc3ZBYXN2UTBQRnNGYU1tRjV0ZUpMYnZGeHFUMk1jZXVxd0hoN3JOSjNvek1QUGJENkxZ +OGlCZzlTc1hySHhneXNOODh3bGo5N0FsTU5ERGZqSVVNY0hmL2NIR3hNMisvVFJjenpJNjAwTjhj +R3ZiOWFzaUlZekZ3MVlxMTY5V3ZQZkRpM0d6d0VBbjdWYnkvTDdzc2F4bDZIRWRXQ29IM1pyNU5Z +enYwWkJ2UGluSlhqMEpBY3lEdVVUVHc0NGNhbCs4Y1ZoRGp5SXdYd09OL3lxQVlkeU1PL3FaZmJx +czVwU1MvTEl6NnpxZ0ZWUDlGRTllTkZYY2RVYk0vcThoLzVvRGlacjlGdnU3Y2ZrVUk4NXovUlRy +TlhtNUlRSGJwNkl0c3hiQjVmZTFwLzFaLzFaZjlvalovMVpmOWFmOWVkeU1ManJ6L3J6VWYxNTBV +d0xtY3hkTXdFbElnS1N4RjhxdGlFaWp6QUZKQTVrSWlwbWpkRjlSNndpSlhaWnIxa0tZNENBSlJ6 +NW1WbVJ2b3V2T0JnOGR4RWw0SXQ3NXNUenpBVUhmQVp5SFJUeVdHOWR6S2VoeUJMZk0wSlJMK3pp +WkJBbEFyTWZQbXR4dEFSUERNK3NrOWN6YStIWCtIekhnd2FrTHZXN05BMlhCcnh5MktOT2U4ekJL +b2I0aEw3Q203cVhyN25MNDdQZTRESUR6aDkrNC9jMlJ1cGpLUHQ5aHB0NFlNY3ZrK3p6aVdtUGZP +bTNPbzlCL1o4WHZ5eEdvdktmK2lQWUNNdzZtSzJMbGdoWjdTN1Axc1FqYVBzWXd0NGMyblFrbnpj +MWNOT1NucGl6bndGaHdoV05PVndZUk8wT0MzaGdVNDlheFBKckM3NkhZLytVdFZwcEdtYTgwb284 +akFjanpWdS9KcHRhSEpCNkJtY09DR3ZsV0lQTzRRV2ZaOXVibTViTndRbTN1T3JSTS9WNGJqME9j +QVNMT1h2NUJuZUc3L1ZuL2JuUEoyYjlXWC9DVjMvV24vVm4vUmwvaVduVW4rUER3VjEvL3VmMzU0 +VkprSnFtSVNWQ2ljZzBGdWwrT2cvSlFHcWdKTWhNREdzVkhqTktoRVRyRUpER0E4aTRjbGlMWENU +NHppZ0lTeDRGdzZFNGU2MDNRckRMTTI4QnhERVVMbDh1Y1RUT2lPanRVNitjNW1EVGhBemtSZ2p5 +d3BCTFRIZE53Q0dNMXRxUGNITnk0QVEvR2lTR2ZPSEI4eGlVd096SEN6end3NGMvenpUTCtvZ0NQ +OFNESStzOHg0K2hGM0xrZDcvaGwwK04xcm5FUE1MNlpMSEN0N0ZIQStrdGJNUnJQd0ZteU84Tnly +Lys3SzkyRDd6eW5YVk1mUDUvbjJBVGoybkVqM2J3TDdjOW51TklQSEh0RTArdGVucHZVRFhqbGo3 +c3diV2M0dEdZK3J3TjhvYklaL3F5eno5ZHZmRUhpejdSQ1lQRmdPTDVqQ043NWFOMXVYSGpRS0ZO +UGRDajlDN2NuQVBwa3pVWi9WbmpZS0FMYzNBenZOckZkSmNQdnpsVWNCNDkyZXZ1TUtnLzYwOVk2 +OC82VTkxcXN3OXY5V2Y5V1gvV242NzZzLzU4Ukg5ZUpDQUM5MndHMkIwb0JRS05VS0JDaXFSKzZz +NVB6TUJvbG1mMld1TTdraGhKUXV2a0lOeGpzdXNLTnNaQUZwRFdpU0czZFJFWDRIdE5JdzJZeEJm +RFdyaUp3TnNIalVDQVdPNEV0M003cnZ0Y2JIbkVVNk9tcVN1RFdPU3pMcVFIWTU2RlZQR0lSdDN3 +YXFaNHZzdWZ3ME16WEpyckRtQk9aaThBQVAvMFNVUkJWTHNoTHN3eENCN0ZJRHcxTWlIK3hZQlJY +cWFPNlBDSDV6TmVOZ2F4cmtHblJtdnNseE5PKzgrNHZob0ZmMkxJWWEyY2FuRS94dnYwZDkvMTFy +L3dwZi93NElPeDl1MlBNVDNBcS9waEkxQW1nUVd2Nm9KeC81V3dFVFpESEV3dng2QlRQNTBRY3Vy +Rll6aXdCd2JQeGFJRnoveDlEbi94Mnh6OTJxczJmM0NvQXg1WTlDYTFtSVBmNWJPWWVOSVR2WTNP +ekp2TEFhV0hSeStIRzF5NTlGUWV1RHhYZy9yVUpaNDVNZkFPcDdoNGNmZGNUK0hTZjJ2cnovcXov +cXcvcmFzLzY4LzZzLzZzUCt2UFovRG4vaXU0TmhMZkNtMUlRQUJ3ek9TNVlnQkVOQUNJRlZoRGda +VElYcDhCRVZNaG1nZGNSQjNEZWU1N2ZucEdoaHhwYWt3UElCTzR6SXVqZURFTTJIeVhYNXh0M0pn +UThVaUExVjU0R0lRd3p6Z0NObWZ2Q21JYW56YzduNDd6bjlrUkpyNEdhN3E3dkN2K3lhTU9XTVhI +bjVqdTRnVUhJWnJISVp4cXRnWW5CdjdDaXpuWTFDbWZ1M3ppcVUvejdGTS9iUEJZbzBjWll1Ly9a +eENqMzB3S2g0Tk8vdVYvakxKcmh3dlBjQ3d1ek82cFF6NWNxSk5ZOVVnY0J3RE1zSW9ORCs3UEc1 +UHJxd2pOd2U2Q2szQ3RrUU12T05FZmVRMmFvU1ZyNWQ0ZVRDd2N3Mms5SGh3Nlltek9xWVVSbWRQ +dndETWh2ZGlYdzlIaG8xYS9zdUM3ZWJIMzhKOTFjREs0dkxpUTJ4OWVZdk9DNXk1N2NBb0g3djNo +WkIyTnFGZnMxT25Bc2pmYXloK0M4aVVQTDZuZFhqSG9FUWU0dGJiK3JEODl3N0c0TUx1bmp2cXov +cFNqL3F3L2pmcXovcXcvNjg5SDhPY0ZZRTJRVUFFVzJpU3hqZTdtSTBpaytCNVRFWURBeElGVTN5 +V3hYb0tZU2l5SjdVZTA1L0loV0lNL25wKzRFWTBrQmRvREd6enlPUXdVRmZNYUNvMEJ4ZkdXeDFz +RlF5NnhQSGN4NE03TklZQndNVjJ3SWtwOHpZR0x1TTQ0UC9YalFEd05NYTllWkl2bk83R0pKUWZz +ZUJGVDR6VUU1dnpuZlBWYmc0TTB4R0JFeitXS2tabFA0L0Z1SFl4aVJiUXdNWVU5OHF3Z1pzQ0RG +L1hvaVhnNEVFTTlZdnQrRFAyeXo2MkJUYzRZVkE2ZjVjRWJzZUV1ZkRMRThxaFBOOUVkYzc2c2FO +TTdtckRXMzJIdzNad2hGMTV3b1RZeERMWGdRaDZmcll1QjVNV2p0MEUwQW5PTXdPeDBwQmFtSkhK +N0daWkJmTWNMODlxbmZyWEpiMDVjYytxUng1ellEaVU0WU1lZGRmQ216N2pSUDNIRVZiZGE5TTVs +Ync2Z3JXZDZKcmJMUVFNelhHS3BHUlk5bHF2K3JEODlyei9yVDNqc3J6L3JUNlArckQ4OWQ5V2Y5 +ZWNqK25OL0FMVVFJZDVTV01COEJKaGtpSEFoaFNoOUZsQncrd2hOSVlTVWVNaFVVTVNMTk1BQXQx +K1Q4a2JLbmdoUFRIR3NoY1d6aU4zM2pUUE5Nc3dSVjRwRnJ1OElKVm9ZTkNqMXJVR0piRERKNTVK +bk1jeGxQMEl6ckZ2UlRyN1AxdUMrNHBnNkhEUkl4UnREYUpUUDNsWlk1N1A4WXFqZGQ5aXNoZE9C +SUliR3d4K3UxYXY1MXNGdW4rZXBTYlBsMVNQN2ZEZHkwQkVSUHQzMUNIN1l6Tmx2blVzZVhNZ1JI +SExCYnA5NXhqUndDNTlZYXZLWlZxekJpM2pxMFZ1WVBJOUI5UkJuNW5CclBmUEF3MEIwWUtpRFFj +VEZ2ZnJjWVhZSUU3UzY3SWNWWmtQLy9JcUJPVytCWUdjY3RZbUhOMXFTVXd6Y2VhYlc5QVZYZUlC +WlBpYUdWUng3WFhDcTNYcmZ6N2h1SFhoUk4ydzhFWDNBSm9jRFhWNDFpbTg5WHNTMHp4V0QzdnRK +dnZxei9xdy82MC83NjgvNjAvcjZzLzVNZmZWbi9mbG8vcnhFcEJZTGhBaEZBNGxNUmJvREtwQUdF +NXJuaW83STdCY0xNTi9GY0NkNGQ4S3lCa2lFYTdobW12T1RzTDBhQXFpaUFaWEh2TUlSNHJNNzhB +alIvT3l4RnVrSWdkV2NHdUFRWDJQRlFiYTlhdkpjdmZLSnE3a01mTWIxVlVRT0k3SFVMcTc4c0ho +R2JOWVNMZ3dSR25QNkZRRUNVSnQ4OHJzaUxtOFl6QnNhQmc5ZTVkTjgvS2pIZmhmUnVPTlJ6d2hB +clRpQUp3YkZ1VHo2bzhuMmhUdXhmVmEzdmVMRFlyMWZMMUdmSFBvcXJzOXFQTVo3MmI3aEsySldG +eXo2aFJlNXJjRUhuRm1iZzh0NllyZU9GbUJTczk3aHpWNmNNRFFzaVEydmVtR2t3L1JPdkdQUTYr +YndGN0pwYmQ5R1RieHdRUWZ1K0dBVytlQ1Z5ek9jbUEvTzZJeFoxTzR1cDNWcWtpTmF6ZUdsWHJY +aW14YndobVA3NlVLZDZwSFRwWDR4eFRMblVvOWM5cVh2OVdmOWFYMzlXWC9Xbi9Wbi9WbC8xcC8x +cDNpUDdzOUxHbUZTbzVBaUtTS0FCRllncEJLd24yZ0ZzVWJTRkFLUVJER0d4UGFsSU1Bakptc0lB +NUFZQ3c3UHJVOUQwcUExNEZ5S0VnY1dZakduT2RaSGtDRlRQV0xEQVRPc1lqT2R5eDVpaFMxQzha +L0xpU0JEYytXMER3K2FMS2FmOHUxUnAzeTdidkNLU1R4aWlpZW41eHBudi9WeXdva0h6OFEzOElF +LzNPTFR1bU80Ni9aRkRReXRIdkZ3WTUwMVlybUNuV0R3SWI4NDhvb0IvK1ljVExnVHg0RWtscndN +Rjk3c3R3ZmY2bFVqMDhGanZSckV4d01oV2l2bUd2azN2OXExMXBtSFhSelk0WUZQUEhtSTBET3gx +cURURzF6Z0VUNDFPdERGMG12aXh3Tjg1cXc1aCtUNTE4NSs5cjB2N1Y0SHNyckYwZ3Y1MVo4M01q +N1RpZGpxaGwvdDZuS0Flb1ludGNNcmp6Z3dpcHQ2OEtaVzNJdkY3SExvSzF6eTY1R0RRNjB3THY4 +ekw2Y1k4bnBPTitLcmk4YjFxdjZzUDhXcVArdlArclAraEtmK3JEL3J6L3BUM1kvdXp3dFJlR2lC +aHo0anprL1JpQUFXeVFKNjY4RkkxZ2lHTEtBUkI2anZpbFlBb1BaczBvbG5UZ09CV3pFTmNQT0l0 +dGFCQUd6eVpaL2lGQXdzNEFoa2RFSVJCd25pMkdQTk5uaklXZ3p6WFV5eDFXTzlJYmZZSzlqSmpT +Q0VFNG00R1JxelJONFJId01oRnc5aVdlZXp1VnpXcXdVZnJ2MCtYTU9GUXpIVWZnNmJneWtOZEZt +UHIyM3dpQ1Q3WVpiYm9lR0N6OEVnMStIRmVGa00rcVYrZVh3bk9qWENJTCsrV1F0N2pLSS92c3ZI +RkxBdWp1RTBCc1dIUTFVOHZPc1JBVnNubmhFdHlVdEhtL3VYNTUvcVptQTE0WE0xTm5YQnBRL3ky +T3NQQXhxQUV3NDF5Z09uUFdxQTFmUFVUUXYrejRuMTJ0OFAwRlBjaSszd1RWKzhQWUlmQm52OE9v +TjRlTElXWjB4c3p1VVEwd2ZjaTZGbU9PZ0xGNGVYODMvbUs2ZFlMcmp0VTQrLzBHNS9ZcnBnaDBj +ZnhhVEpQZWltcDdENVhuL1duN0RYbi9Wbi9WbC9pbHQvMXArTG9mNnNQeC9jbi91djRQcUFZQU9w +TG9ZQ2xDRTBEam1LUWc2UUxnUmJoMVNmL1NWWlRRTGVjOERFdFYveEdrQjhjbXFTZlF5dUVYSXAy +RjY1ckRlSGZEbDlCdHhQL2t2YURFVWhLRGtVaWlTTmd5L2lkQ2NRYXhGSm1EN0RJVGVTVnVRamxQ +c0JuM1hpaVlOMDY5WG91WG9jQ0dMQzdWb1JUQjI0MG1ReHhNWUhFMlVkbkN2V2FiNmhJZktMTFla +OG5xbk5YUzcxaWEwL1lzSmdEajdDZWpYb3hNUUhnMW9MczNnT0tvWVRBNWZlMHRnZm8xZ25CcjVn +MFN2UDhhVU9QZk1HeUx4WWVrRjArckRtSHV3T0szd1FiWG9nSCtINmk4cTBac0FNUzh5Mi9abCsy +QWNYN1BhSEErdmhzQiszMFpLOTFodlcvdlE3WDF6ZUdaUm1hRWx1LzZmQ2VJZlhIcGdZU1Arc3oy +SGlNNTdwbm41cFRVMTRzZGNjN3RjbmcwWE9VL001WkdFejc1SmJQWGh4Q0poTFRBZUh1bkNmSHJu +RHBGWjM4ZXZQK3JQK3JEL3BvLzZzUCt2UCtsTjkwWVk0N3ZWbi9ZbjNSL1BueFlSR1M1d2tTRlVj +RWdCVmtNWVFHVkZwSHJESTFTaHpZaENKeEFMYmh6anhyUVhFblZBQWtRZFlNVjZOT3pIMk1KaWly +Yk5uaFR4a2FxYkNySmZQbXdva0tONGFlUkRzYzhpRDIxMWpZTFAyWGp6V01ZMDg1dFI5UDhTQ1NX +eXhjS0oyTlZwdlRuUFVDVDlTell2ajkrTmhEZ2Y0U1AzaWVFWTRSQStUbXUxVkl6NDBVdzVObE04 +emw5amhTQXhHSTJ4OXdHa0d6dkpXUXNOaGpXak5FUnorM2VVbFRMSEZ3eGRzTklFZnRUSG9tblI0 +eDUwK3dDZ1dubktneEN6aEdGWnh4V0ZvT0RKdnpqNTFlYXNqSDhPSWs3VnlyamFtUm9MWFR6VkV6 +SXZ0VmpkZC9QemRyKzdiSUxYS3J6LzYrOHNQdnI2WTFSVThSdytmckRiMDE0VUx1ckxXZ1lBZk1m +VEJYbHk2WUYxTWd6WDU4YWtQY01OcEwvejY1STBYM0w2blovWkdGK3BRazczNFZKcysyT3RaL1Zs +L2lsMS8xcC8xWi8xWmY5YWY5V2Y5K2VqK3ZQZ2Z3VHdnRk1rVlppTkNGSU5BWUJXZ0lZaFZoSi9x +L2RTdktVdmVKUERjSHFSRXVKNHJXQjdBeERHbklIbVlYaEgydWlOanhURmtXUytmSXNSQndJcjYx +LysrYnliZzNVTmg1cXl6MXo2Tno2VVptbURPUUlCbllpTWRMbm10dlI4YUtJZkxHclhqUnEzeTRr +enpDRU16NEljUDlqZHZ2N1VpMlJybllNT1Y5WnJrdTgvd252R3lqY01aak12emNMTENuSWFKRy9H +RSs1ald2Rmc0MzRQTEc2Zmh4M2U5RWNjZStNUVhBNWR3d2U2NTcrSTdVTlNNSDdXSzYwMk51QXps +TW5DVm12QVJnN3JMejJSNVMyV3RuSzVQRFhycWpaYnd4ZXpxb2pNYzBJWDY0UXNQYW9iVG5Mend1 +ZFNpWmtaeE1LcmI0YVFlTllyUG9QYklwMGNPQkhGaDhSMVA5RUFYdEFTcmZFeWtGcnlJSjU5bjlu +cm11amNvbmVNaUhLdFRMK2lQMTNESW9ERGxJS0lKZXRvM25vTkJYdkd0cnovcnovcXovcXcvNjgv +NnMvNnNQK3ZQWi9MblJUQ0VCS3hKQzAwS2JoN0pnZ0t2a0JoTllLQVZrTVRXZXc0Z3NvRzAzek01 +dkFGQkZFTE5lYTRBalFCYURHdmtNQ2VQaHNqak8xRjRTMEVFTWFqMUJLRGhZb29IaHowSWc1dFo1 +RERVQ0I5YzZ0RjBUU1c2KzBFd1loR0hwc0ZpbnoyYmU3Q3F3MkVUMFdxOHBua2pZQjE4TU9EVGZu +ZGkxR2d4OXJDWlN6MHd5K1V1SDhHTEJRZk8xRy9PUGoyejFsemVLcTB4cG9kNWkrUHdzUVl2N3A3 +QjcwNjRSc3dBT3dQRG1EcGhGd09uYXN3Qm9FOFJPZTcwRlM3OGl1dXlSeHdjd1NpMnVqMFRBMmJQ +OEJ0OXdDSUdic3pwazNXRWJNN25OZWhneFk5bkxqb3o1UFNYb1puQjcrT0xBWXVjaklzMzJHbUlN +V0JUbDErdGdVTzkrZ01QclBMcmdUVXc2Q09ETTZTMWVvUFhlNFBpaXhieG9VNjk4RmsrZHh6R2dI +aUJWZTJ3NWNDRkt6MndSNDMxWi8xWmY5YWZPSzQvNjArWTY4LzZzLzZzUCtWOFZIOWVGT3lCZTRn +bExJczlGMVJBZ0JSRzdQNXpOckFDKzA0VURDb3BNWGdHNkFGK1hWTEVOQzgrY29rcDRyVWVBUXBH +aU05RXBCSG1rSVh3L1Q3RjJ3dXJSbXFRbU5ZaDJYUG1sb1B3WUxYSEd3UHJEQ0t3Vm8yYUlSY0NU +eTJmRHB5b0VTNXJHZDQ2amRlb2lNMUJCb3YxbW0wZHd5TmJ6ZmFzUWVjN1BuMjJYdHd6WGxZRVlz +SWlQcjd3Z0E5NVBJTWxCaE1yTWJ5eEVsY1AxcUJqZUZqa01VOElZaE9CT083Ym0xa25ScmhiakpQ +TFBBNjNiOU5QZkJGV0RnQXg4VXBjK0ZPam11RTlvanh2NGZRQWJ5N3hyVW5OT1BNTWJ2ajB5cHc4 +ZWdwVHRCQkJwMDQ2ektFREwrTWFqT3BORUw1enFNTnUzWWMvK09QbHpubzgralVHdU9qRmMvWEtq +d040ckpFUFQ3RENBcE84OHVCR1hQdXN3eVdNZU5FNzYvVFVrTk56K1JKTFRYaW1iVDNCbGI3aWt5 +OWdFS1ArckQvRFhmMVpmOWFmOWFkUmY5YWY5V2Y5K2VqK3ZHZ2NJMHFJWEFVeEYvSXNTSkUyZW80 +Z3o5d0JKbjVEVXFLUXhIT0pWd1FUVXlIMmlpZTVvb0JIaUdLc0IvQmVWSERCd01EZVJPeGhNTG5F +andqVFFJUjVicjNQN3VLSmkzekcxckFZRkRuaUlnd21kL2prdkIrcEdUOWl5aUhlQ25scXdsbk1H +YUU0U0t6MWw0SVpodGdJeWg3eE5WRWMrT0RQU0EyNHdhM21pZW1aZFREYWp3TzROSEFOT255S2Fj +OHgwRkF6b3ZNZFZ0ajF6OTRZRkNaWTRjTmZqQ1MyNytKWjY0NURjYTFYcXozNm95YWlnd1ZPZkxv +N2tLMlJVenc5RmQrOFBYRHJIMjNBQXFNWTlBS1hYT3F5ZHVzYi9PNHUyTlNWWHluQXYzanBLM016 +cU1OUjMrU0hRMXdha2s5TmV1Nk5rYnNSM3ZHOG5FMTlhaE5iTGRHcVd2RENrTW1kL0hEVEJOd3dX +eWMzL2VQV0cwbzlzRlllL1ZhVGZQS0xoNmMxL05RSk0zejIxSi8xWi8xWmY0cFhmOWFmOVdmOUtW +NzlXWC9DOGNqK3ZDaEVZSXMxd01RS1lScmp6b3dLajhrRVJKSmlFWkVpQWZKY2t4RERGSVFDaUpp +ZXJmbEdVQzV6bWlTdW1HSXpvWDBLRTUvUTRmQjcyb28wanlURGZ1UXdtMHQ4NjlXUW90M3RRemFC +MkdNZ1FGM3FpM0R0dCtkK0lGSStHTlZHRkhCWko3ZGFOVEJOOTl3L2dXeXRSamxFTkRQN1BNZXpK +c0NLaHd4YzRNQWRackhFMURnTnQwZE9jZUFWQnk3Y3F2L3dNZ2ZYanV1SzNuNFlyVk1uQWRpdmZy +aVlqdkRreFk5NTNNdUh1eGhDRDYybEE0S1RUeS9VQlFjTTRvWVBQRnR2THh6cXRCYm44aGtFVFE5 +N09FOXQrcTVXWm5CMzBNRkIwUGJMQTdNNjlOTWRIM0xiWTdnenFMZDAwVkt3aTZlbTVYL3ExQ2Y3 +RGM4ZDRQTEFHTDBtdjNyVUppYnV6YWR1ZS9Ca21JTXQvWkliWC9MUnd6Mi83dnFNbiszbHhIZFhP +N3cwaFovNnMvNnNQK3ZQK3JQK3JEL3J6L3JUcUQrZnhaK1hnQ1pHWUlBV25FQVFMQ0hSbUFmZUhK +SnQ5bE8zSklRb3VUMXBLZ0VhUU5vcnNiMmFpMGhGSTl4YWJ3ZGNZcTJKcHdBTjl5WUFLWjZKalVT +TlZqUk1Qb3NMTXl6V2lTdE9TSlpUYkkxYjhZd3dnbEZNeEtqSGQ3SHVCOXhJdHlacjVTUU9lOFNS +VHk3MTQ4ZC90cmZXNzhnYmFaYjVORlV1TVh6R25jdkJ3bEM0Q1NhNXR4K3pWd3k5WWlUN3JHRThN +WEVzbnZyT2VObjE4akJ1RGloR3hJV1l3UytmdmJCNzdoa2R3RUlrNmFrTFJqbmtqRWtJREE3N3Jj +ZXZZYjNjZEtBZlB1dWx2UWJlOUFjdWM3Q3BONGUyK0lRcUo4MnMwUWV6ZUhTaHR1VmdMam95NVB6 +d25hL3NyeC9JSllabit1cU5IVTV3cHpiOXdhdmhrSkJmM1Q3RENKdThxUmszMXFTUDh2cU1IN2dN +R0IwSzVueG1RcjFWUDRQQ29UN3J4YlpmVCtIQmdUM0w5OVNEU3pYV24vVm4vVmwvMXAvMVovMVpm +OWFmUnYzNUxQN2Nmd1hYUnBzSTBxSTBDMGhrSU5NYXpUVWZjUGF1UVdjd2xHUmlwREVHa0FpM0wr +dUpRdUZ5S0NqZ2daUERtb2pKNXhUQkhKcDR4c3NXNEx1Y2pHRWRzWXFMVkdTTHlkai8vS00vMnpv +TXhTZVdHSm9JZy8yZmpwZHRoUHdSZ3MvcWhKVjQ1SXBndlNrZ0NoZlJXV2ZnQ1JkdzRNSWVjM0py +MGhublArc1RFSkdJcDRadC9oaWNFZFNDUDdnOXM5OGE4ZVYyRjhOZ0VuWFpEN1BEUzIzNklvYjY5 +ZHVWZzBHdjRTS085SGNQbDduRERMczNSRGpVVDdvUXp3WDNDbk9lSno5KzVJUWQxaGdMSHR4YVkx +NWVmS3BMM1hLNGk2ZXZNTUFyQnJ6aU1KNjg2dC9uVTc4Qm96Yy90SU12SnJSZmZIOTVHejU3NU5J +bmNRM2ZhYzNhSEJUbTVZZkhaYzQ2ZWUwTFJ6RzlOZmkyenpPNWNLQk8vT3FCWi9pMjM1eFk4dW1K +ei9MaVJ3L1VLbzQ1K092UCtyUCtyRCt0cnovcnovcXovcXcvNjAvN3hYOVVmMTRVYkJJQWdwSEVN +K1FCNUs0cGtpU1lJRUJ0VVRkeU5NSjZ5YTBSeHdBSzRYSzRFTVNnN3VaV0hFTW1ZY0lnRG1DS0Yx +OU13cEhiWjNzTXBoSVBEcmdSSXc4eHdTY2VIREhNUng5K1kwbk0zb2duSnZZR1NUemlNYllwUTFL +TWtmcmdFRitkaURSZ3NIYmZZRTA5UkJLY2pKSkdtck5QSEhFMW1UbTlSY0NKdU9KNFRteldpdzB2 +RHN5TGh5Y3hyZk5kM1RnNEI5REVtOHM4OGN1cm4rSFU0V3NmN0FRWUkrRmFiV0xwemVhQlplcUF4 +WnVOOE9jWkEwVlF2c3V4TmYvNi9DVnRjZVdDUWN5WTBlZkZPVFhyQ3g1Z2swdWQ5dE9VT2MvMEUw +Njh1VnZMb0p0djZtQSt2VFp3NVZjUjRCZFhIOFNFeS85UmJucXVuM3BsblNHV21HcGNQTU1UTHVT +SGxjbHdRQjk0Z2NVRkN3N2cwTDgxOGZSTVBuVXlvMkVQZm1IWmRWT2ZlL3BoWGl6N1lOSWJ0ZEFK +YlBMV24vVm4vVmwveWw5LzFwLzFaLzFaZjlhZllzTDFxUDdjWDhHMVFQR0tsVXdpaXdFQ05BYlZG +RDkxUzJhUFFxMUZFRUNhSmNhS1Q3Tm1TQ3lwT1RtQUV3OVJRTWhodmVlSzlsMHVzVDNQZ2VIU05Q +RU14U1pHeEt4Z0RiYlBXaGk5K2ZGWnN3eTU1ZEtRWUJPSFFkVnlQOVFGanlZd0NBeGlxa1c5Nm1F +R25CR2tlVEdKUkE3RFdyeWw2V0pGclBiRm9NUmhqWGs1TmRmM05FMGpmWmVmY0dBMjUySk9XRmI0 +eHNTREk3MjFGLzRjZHJBVGdmcHdoeU1ZelltbkQzQ2JrOU5hc1gwM3pLbFZUcC8xSEdiOEJRTUJ5 +bStkSHVxQjd4Rzl1dlhKTS9QMjR0QmJMaGh3aUorSUZXYmY2U0wvb3B0MTVxd3hmUGQvMUdzZEht +TldkZTFoTmJYNFRpc09iRzh0RGZ6UUF6N3dJQTdjZTJBNHNFZFRzT0VjaDdDb09iaDlkdDllai9G +aEVoc0hobnd3K0k1REhQaHNqZlZ5NWMycldQR0ozcVdIbnBtelJpNFljVjUvMXAvMVovMVpmOWFm +OVdmOUtYZjlXWDgraWovM0IxREZBbUNqUWl5VzNIOVdGVUJRZHdMekY3TVJoQ2lKQmZKVHZCaUN4 +emdwWGpKZ0pOTjQ4OFFuZmdyMjZ3VXhwMExrUXE0NVJDdmNmczlqVUtLMDN6NkVxRU4rYnpIY0hT +Sml3WWgwMk0rNExzRUVJNFo1M3pWTVl6TEVVYmRuNG5qVGxIaXdtOU5RNitEMkhSNFhqazV6UnpS +RHV2ODBIMTdzVnhkK3NzWVFTMjB1M0pyVE9QVVJGbjdsd1FYY2VGR243d1FLVTRSS1ZEbDQ4Qllq +aUNjL3ZzVFFOeGRlNWR3RFlmQ3EyZnJVZEhCZVh3VUhvNWo0Q08vMlduL2VJbDMzd0JFVHJ2U0FF +WmJqRWJ4QmEzaVZnL2JzOFRZRzl2MVArb05GVGI3RHExYnI3Zk9aOXVnMmRUT0ozNDlmYzA4ZU9x +TS9kZnVEUlJ6ZkY4L1U3THQ4OHZzdURwejRnVjA4OWJqa3g2VTZZVFdIQjUvRjhWMWN1UFhHblB5 +NG9EMjE0MDhjZVJ4RTlzcVh3OXBsYnd3cVJ2MVpmK0sxL3F3LzY4LzZzLzZzUCt2UCt2TlovSG1S +VUdMa0NPaHRBZUxYck5NRXhTRmU0UklKYUEyeENFemNDQ2M0Q1FEMWVaL1BJQUFOSmtxeEZDT2V2 +UExZRStFeURXSUJJM0xOa0RNRnVpdkFVS1RtSzBZc0JNdmg3WU9tcWNrZUY4emVBR1FRdmZVcnpw +a1RVKzRjS29ZWTFzRVhjOElGcDN6bXJFRTZMbUFSMDJkbVI3Q0JVd2VRK1lpVnVMYlJ3MVhlbHFq +SDh6WGs1TUVmbnUzQmlYMXFTZ005czJhRk5MMkN6OTR6WGxaazFrZFVUQ1JHeEJBek9SalVSOVRN +Sm9iNGVJTmRIblhDdVZoLzg2dDlGdEdMcnc0OTNIVWplR3R3cTQ0VjgzRGh3QlJULzlVTFkvQ3BT +Y3dqNlBOL0Z1MVFVNE9ZcTZ1Sm9UL3F0UjVPTlZsN0RvVXg2T1Q2eGZ0Zlc2MnV0aWFtR0xDcFQy +NmE5dXNLT2FEbHRJOCs1TUNCUFRoeHg1TWUwekNzY25vT2s4L3dXZVBhL280R3hkT2I0SW9HNVJK +bkQ2TGgwanA0OUNYOUZEY0d6Y0VuYi8xWmY5YWY5V2Y5V1gvV24vV25tdXdKTi9Wbi9mbUkvcnhJ +YXBHTlJLS1pRRnFnTUtidzA2eC9aWWxnckZXc2ZVZ1h6RDROQVJ4SWpmR2NjY1dSTkhIWEJQTWNL +Q1FTcFdmMkVES2dZdnVuaHMwVFFvd2E0Um9JMFN5Tk1nZXJXRWdJVVg0dHdWNkZXcE5oajE5SjBE +RE5kc0cybUc5RFBUQW1ycGptMVFFRHpCcHNuYy8yRXovQisyZTFjVUZzbXFGQmFoRURIbXZGd2Rz +UjZ6a1F4TkVIK05Jc05jUEgrTXZUN0pOWGZNOEpDd1lZY1cwd1NYakIwYzZOaWV5MUhnNmlZeVFZ +UE5OVEJ3eE96Tm1MWXlKU2sxcXNkeGNibG5Eck81N2cyakg5VlkrOCtxa08vWEJacjBkeXlHV05P +T0t0UVdjdlRMUWdycy9XcVUyOWpLVW16OFRDdzhGMzNsd3hnbGg2b1I1cnhaY24rbFN2SHVDTnR1 +R3pKcHFYbDg3MTJ4cVljQUcvSEhqVUQ3WGhGVWExYjcyM251bWYzaHR5d2JWOFRnNTcxR3E5T1hl +NFVrL1dxTEgrckQvcnovb3pjWEZYZjlhZjlXZjlXWC9XbjQvdXovTUQ2QlFEcUlBZUtsQUN3akpQ +Ukg1aVowalAwM2pQa0pNWVFBSU5vQWI1Q1JtcDRubE84TnZFYWZTU09RUWlKZzFDbkdMRjlsTSs4 +QkcxdlFxQnowQ2M1NHFLa1luSlQ5N0l0UTllc1R5M053TlJ6QXVEdldLbzRaanFEUEZqS3RqbDFU +Q1kwZ1Njd1d0dmpJdFl6Y2piQVRGaDExQjc4QVFyUEhDYzhYSU1OUFA0eEoxOXpLcFpjaVp2dU53 +bXppVWVER29nck1TREZRNDFFTlppR3g1d2s4TkJMajJIQjAvNnJWN2lnZGw2T2VCVWEyb0tiK0tz +b0FZN0RrNys2eDRRNHBzWEIzYUhPM1BSQTN4eW13dS9hbU5hbDczZUVIcnVzL2dSTHZ6NittcVFl +UWFiZ1ZPNDZKT1cxSTBmMzlOTEdPRml4TlNmNy9pR1ZVenJyYzFiTVJqZ2xSTlgwUUNlOFpVOWNz +Q21IekdvZmJSS1V6alZYN2x4Z1hQeHdtVXVjZVdQdCtyUCtyUCtyRC9yei9xei9xdy83YXMvNjg5 +SDkrZkZabzFVcEFCSUJKYVJCTFVJQUlGOGx3Q0lKRVdvQkVpekgwbElCTmljL2NTdElIdUFkaUVE +Y1RrRWZQWm1SWHlmeFVhQUlueTN4bHFGRTRFWXlDUThhelFpNXRXY3hGWFA1aDlpL2FSdnFBMlpz +QktDUEFpQ05RTjJSQ1BMM3N6dlFUQzFpV2tOOGVKTFBEeUk2Y3JBQ1d5d1d1TS82NmNoSjkvTHJ2 +TlpIUVJCREdLb1RhT1c3eEdiWjJyQ0pmN2M0ZEh3Q0RiRFBxWXdMNi9ZNHZxZXcwWWRPTUhwUFVa +ejZ0NERZdnJCSkV6bk12RGxkOEZYcUpQVCtoaDB6VEtDamhiRVZxdCt5dUdBT2VPNmUrU0xQdWhN +WCszTjc1ckQ0RkpqVEtDL01QZ09wejRZUG52VHBlZHFrUmUrMU95ek8zM0Nra1BLdW1nZXorTGFJ +NTVEQ1I0MXltbU45Y3ZuY0pQREMwWTEycWR1YThVMzlFOU9PSzNEazFwaHlkODNVYjlMeitXVno3 +cjZzLzZzUCt0UHVldlArclArckQvcnovcnpXZng1c2RtYkhodUFFVWlCaUxUNUVQcXlCVm5uYm8x +OWlnSk9FOHk1S3hBUmhDWmVrbGxyRHRHR2VVWDRUOUh1elBuTEgvL1BGUkhBNG11Z1N6N2c0VEp2 +aUJNeXhHWmkrZFRoc3lZaVRFTmRHa1lVQnVJSU9vY05NOXVqRVR2V3lDLy93U1NhWm4wYTZiUDFH +Z3lqMmh4QXlVMmtCbEdiMHd3NS9RNjNOVGc2d3ByRFpqQnJuQnBoODlrZWNmRXZydTk0eXFFQmo1 +cmdJV3AzV0UvU1gyMHNuSXFCSjNIVUF4c09JeXByeGNlUlBmcHJqeHhiNStSWmcwNi9YTllUampq +MjRRWUg2bkhYQS95SkhRUExMUzc4UG0rY0dYTGd3aDhRNXZTVTRNV2dDM2p4TEllTHZ0UkNEK0l3 +Q2JPbzM3QWVKbHpZcjdldzZpOHNxZEdkWHNYRWd4aGl3Z092dXEyM0Z4YVlQSlBiTTNQeXdJdWIz +UEVtVnY1d0VaK09FbDhjRnd6bWNLdDIyc0FYL1BLcjJaeDE5V2Y5S1g3OVdYK2Fxei9yei9xei9x +dy82ODluOE9kRmtSWWhYaExKRlVnUW5tc21RbjBuQ0VRRGlpemthb1kxUUFFQkRHSThJMkJGdXhS +a240S1JBZ1JCYVRRTTlpTVZzS3duUmxqOEtnTmlQSXRCTlFVV3VaQm5IZnlHSW1IWG1KQW1ON0lN +OFdGVlEwUmovUkhPV2NNODhzT0d2SkNHUkhuZzBSajF5SUZNZTlUa0xVVU1hcTAzTlpvZ243OXJB +SmVZZXlBTWYrTElBemREcTAydGFwTVA3ekdqR09vVlQwMXdCWThyQTAvMnExT2YxQUluanNXQVR4 +OE1jVlowdDBQQlB1dUppWkRrVStjNXdJN2d2TUdCeHh3K3JWK3RESDdETTV6bVVJRkJIckhsZFNp +cFYwNkdzUmNQYWxlUC8yTmRtTVNKU2RWc3ZYNTV2djBiTEFmWGVlTWtqeHowSksrWStNWW5Ec1cy +eHZ3ZVVMUFBuTDNXaXFmUHZ1dUovc0Fxdno4ZzlObisxZUpORTVrWEV6WjU5TTB6KzhWVWl4cmxW +d2Y4N3ZhSXFYZHl1c3o1anAvNnMvNFVwLzZzUCsyclArdlArclArckQvclQza2YzWjhYUlFoNHhz +czJFSkVTK1NsYUFnVFlBSXhFVzhTQWkrZzF3V2R4RklVZ2pWT3c1RVNySU9ScVFFRGZYK2FJeVQ3 +WGZoOER3b2RneFFMdkxZUWhEaXdScHZVd0lRc0ptdTZTVnlPWDdCMUhZSEloQUQ1RStaMThlek0w +VDM1ejhGc3JqeG9qSmxpWUVsY3VjV01LZVF6N1AzcnpONHRWTFlTdEtXS0tZNTBZNG92QkZPYkU4 +UjJ2ZWhLVGJSOW1INDVnOTltejRDUTRBeGY0c2s5dWNSeDBma1hDZWdiRklVT3BVLzN1ZUZTajNI +Z2tGcm5nWkZKM09CeEM0c3N2bDlwZjN4RE53Q1grYVFQM01PaUZPcy9CY0g2UFAzVzU0SWRKblAv +OTA3OVlIUGpJWlI0K01mUVV4dXhSdDJmcVRPME9FRGdZeTl2T0NOOGZDcmpET3l3NTZPMnp4eDFX +Smt0LzFLa0cyTXlsWGpIeFpBK2RwbGIxbUxjLzY5V0FTN2pWTHI3Yzd2QllZMi8wcWs1eDZzLzZz +LzZzUDNGYmY5YWY5V2Y5V1gvV244L2d6NHNQQWl1UzZBUkRqQUw5L3Jobm1na2cwU0xJR3ZzRU5O +d2xVcHprQkppaXhFS0tmYnQrWWhtQXVEeVQ2K09QM2x2aUVFcHMzaTRRZXRZcFNBem1ObUlnT1pB +cmhtY09FWVNKaXp3QzBhQ0lsNERFMWNpWTAxMnRSemhud0tHZU5jL01JdzFoY05rakw4SnpVT0NR +SURVR3JqT3VpMDBjZDN2OTVYQ3gxSE1hZUYyTzhhYUc0R0VtOWNMS0REZ1YxMTY1N1ZkckRncjlz +RGNHM1Y1Tm45VHVMajZNWXRtbjFsUHZkWEVUc0Y3NWJBL3UxR2V0WFBvVzg4bWxGbkd0STA0aUUy +L1h6RnFZeEFySCttRU5YT2ROM0RsazlkMjhTOS9FRU04L0pXM09IdzV3cExmM3BuZVhYNC9VZ1J1 +WXJOT1Q5RS9kdUJYWEdqSG9RVnhZN2NGMURnQXg2Y05hQm9QQm5IemlSVCswS0tZNWZaQkQvNzBS +MU8vb0NWZHFkeGlxVVMvMEVDZlcrMDViMXNEbUxwZTQ5V2Y5Q1hmOVdYL3UrdnB6WTlXZjlTZE82 +cy82MDczK2ZFeC9Ya3o2S1IwUXlRVU9DWnBKcUlwQUVLTlpyM0FnMHhUUEFFYWFvalFiK1pvanRr +UUFtTk1jZTVBZ0h0SFo2L2ZqRVFtRGVBU2FnNEk0WDNHdXdWOFdqNzFJaGtNemtTV3VPYy9rVjRl +M01nalpNYVRHbkJHNXozSkVPSnRqMXVFaVpyY0dYcDg5YzJrY2Z1QjBxVjllM0dTc2tHWlBqQ0kz +TWRwN0REVmpjdUhVSmE0WThLalhQZzNXeUJ4MmVUdmtZb1J3YlE0M1JnNWJjZXpGRGZFNWlOemxK +aGhESHJFOXR3Zis3ZG5FdEUrTmpMZUg5UXljRXFGK2lXTU5RNmhwT1J6KzlFRnV2T05ZNzJOQ2VB +M1l2WVdrRFRIM3dKdGNldTFObHYzMG9FK3cyQnRkaUk5YmU5WGhFc05lejJtUVBxekRvYy82WUkz +UE9JWU5ackczcjZNYmRjQXFoK2UwQ2hNK2xzZXB4UjhrNXZYQmMzWGdCemFjV0NjKzdBWnQrbzRY +SElxeEJoMHMrSmJUSG5yRGZYd2svNnZ1NjgvNnMvNnNQK3ZQK3JQK3JEL24yZkkrZGRTZjllZWor +dk5DRklnNHdqLy9oN0VSbXIva2l6U2tDOG9zZ2dJbWtJQ0lRYndFNWdGbThBV2lPVGZUMkdkT0xC +ZGlmRStEM3J6OTF2Nm5mTVVTSkFHWWg4MTNuMkU5NDJWSmt3Y3V1WW1MUU1XQ0J4YU4yT1lQK1RD +b0VaRkwvankzRndscVJicGFFeDhYNmhOTGJuZWtJWE5GT3lMVEFIWFlDNjg4NWduQ01PYzcwVmlq +R2N4Z25jYmowWURKTS9QeXlNdklNYWNZOEtvUlpuVmFJeTZlOWN5ejVYZE5kMTFSSjEvV3VoT1Iz +dUVBMTRiWWVTN3Yxb2liZVk0M2RjQVl2UG9LQzQ1alFIMHlieDJlY1FTNy9mQzU3OXE1N2cycVJu +R0NSMTloOEdzWmNOQ0tXbkF2RHVQWkk0N2V3YWhld3hvMTAyKzBLQmVETUk1NVBNbWxWaGpGdDU0 +KzlkLytIRGppdzROVHp4STdmNERKdmNZZVhQTGdnQmJoeFRYREcvTEJvdWZtdG1jekp3WmNyNGZI +WEhDSmE3MmUxWi8xcDlqMVovMjUrcWsvZDEzOVdYL1duL1ZuL2ZuWS90d2ZRQVhkSW4vOTd6dmh1 +NkIrU3BjWVFNOEZJa1FKVXJTQmZNQ0pDd0IzVGRJd3lSRHVVaVR4QTJxOUJ1YnRqVndSb0FhRUlI +ZTU1TGRuRDVLNVlJTGRHczFRbk54eTVKazQ5dHlUeFZTd0lFY3VXR0ZFZW9Tek9RYW5PTlpva3Nh +S2pSZlBYT29nU0tRR3Z5WjVzMlpvbkVQSFhyWENRV0F3MlN1K1hJU0J3K1F3NzlCSUhYZ25EbjFa +Z1E4ZTNPNzZtK0RWNERvR1BmOU1OWDRZRkdiOHdPQjN6MlA2TmRQVVNiaWVXYlBpblh6aWhrOEcz +VU42KzNkZDRlQXdZdFlidGNHQlg3WGhXOTlnaHRlNm1DbzhNeVI4OHFtVC9zVDNHVS91aWFWV2VY +Q3JyekRqS1ZxelQvMzRoazN0TU1IRDdQcmlHVjQ5cDEweGpkUWp2anQ5Vyt2N1loMWN1RkN2L1dv +U0E0WmRNL1dwQmFmV3FBZVBPRFBFdEM5OVU0OSswNHZMV212VTY3T1k5QVVYSFBWbi9WbC8xcCtu +di9VblRQVm4vVmwvMXAvMTUyUDc4NEl3bXhXaVVBa1VDbUNFcGtHUzI0d2NqZDAzQ2dNRWdUNFR2 +Q0lBWHlQTkFFUWNlKzJUSndaRmprTC96NXUvM2liNVQrZnllcTQ1OXZnT3RNOExlZ1ZpbkYrblNC +T1lTNkh5N2FFd05Xa0kwYW5Idk9mMjdWdUFHeW1haXdTNHJOdERhZ1ppclJkNzg0NndRaW9zNWp6 +VEZOd2dsd2pFek5zaUExNTFJOXVka1IxSUdpdUhlQ3ZBTWJKbjZ2Vlp6ZkxpUW16OHBJRnlFdzJ1 +emR2bnV4cVczeFhyeWMxd2FzZUZkZnFVZjdvYWQ4Y1FaMjM0eHgxY2VJb3hZSUtUY0EyODZCMGVy +Y2VqbnEvaEp5WWh3c2NFY3NHWDJIUWlIcHhpNzVzelFoMytWNE16clBNOHVsUXpMR3BnZWpXcFRT +eHJZZE03Ni9aWE1DYVdtai82OEJ1TFdSemN3NEJYZU8yMUhtYnorZ3FQSHNxRmI5OGRlSG9sdDNy +VVRUTXhxRnJFVUxQbm50a0hRd3lxZi9DR1E5ellCNE40T05wNjVySXZmd0NFcy9CUWY5YWZ1S3Mv +NjA4MTFKLzFaLzFaZjlhZjllZWordk1DVUlJUlhBcGdQcUtTR0VrS0JrcGpCU2MwZ0F5TkI4cEZu +QWdYVCtNbEFScEJLNG9oY3A4UHNVVGdFTERlUDkrc0FPSVFBMkhXaXlrdjhBckxZQXpQa2F0QUFo +RlR3WWhYdkF2UmNuc210MzJlaXkrWC9XSjdzeEJDRGZFSUlXVFo3NW05WWpDb084TGgxeno0eE5J +UXc3eERBZ2NNL01zUHZyN2YxYVQycldlRWlsL1lZY0dCZXVTR1Q0ME9MSHlMYTUvY09UaGdzazR2 +dGg5cjBQTjJ5OTdFOGgxRytlM1JYLzAyQ0ZLdnMxNDhsL2d1T2VGYUE4N0FIVjdGdzRkK3dwYitX +T3VaV0hxUG8zdHp5RXY0bnVsMTZwQ0h3ZkdnSC9xUFE5d3pPZDdvRlU1eDdYRlAzOFMzajU2dHd4 +c3NjbmtHOHhwamVzVEk1OEE1YjZUTXlZTVhjZlJVRFliOTRRd21uMTNpNEduakRHNDhlcTVHSE9Y +QWdjVnpmVm50ako3czhVeHVhNk5YK3NXSGZvbFRmOWFmOVdmOVdYL1duL1ZuL1ZsLzFwL1A1TStM +NXZuQWlNRDduSTErR2hkVU1NM1FCT1FnbGlramlBZ1pVUW9IM0ZBZ1l1d2hETWsvKzl5L21xVm9q +UklMQnVTSUNRT3lBRWVnSEJrYUJnL2NHaXFXbUxESEtNalFRSWNMOGd6aXNFNXNEVWNFYkp2L2Jx +Z05zWEtLa1d1ZkRZSDJpaVdIZUg3WFB6aGlVQTNTVlBXYS8vQ2RyeXdXVGJWZmJlb1FSeDdmRGZQ +d3k1ZURFa1p4NVROdnJUclZTMWppeWIxamVKUlRiUTRuYjBxQ3l4czV6OVN2dDhRSFE1N3BoWHIw +U3A5akRyMDlnbnRaalZpdmxnaEsvRFh3Q0ZYKzFjaGdWa1BxVlNOYzZRWHMzZ1RabjRQUGZyWHJo +enR1MU9tZVEwZ3NHRHhmM1UwTmhybWZmdWVMeXhjTjRZN3BrMzgxTkxtczB3L1A4d2NHazhBVlAw +UkhhbEU3dm5Halh2eEVEOWJ3aDlydHdZdDU4YmFlR2N2bDlBSjN1SVNCenUyM1ZzN3d2Z2ZEMUdU +K0hrLzlXWC9Xbi9Yblo1L1huL1ZuL1ZsLzFwLzE1eVA2OCtKaG1nQ1lTWWtVSTRuQkJKcnJ1VUNJ +UXo1QkdnQUlpQ2dDMXB6OFpJNVlRTVM3SjBSRG1ONmJFL0g4anJtUlpubUdGSHNpbk4wL2hMbGdn +aDB4aWhjVFBudUlCU1o3RUNZV1BQWVFtZmoySUJjNVBoOXM1MS9ZTW5BQ244YXFiZk1OZWVyUWRN +L0YwaHk4NFVwY3RZWVg2OFF4WUZHck4yS3dybGluVnMzWENLSVJXeC9FVFIvd0xtOXl3eEgrN0hP +SkE0ODZJMWFZOFVDa2N0cnJjckRDaWxPYzdCaFQrSjZZYXZBWmw3aUJKMFkyNUJNcnZWWWp2clBP +aFJQOEpZN3ZQc01FdjZGbU9wTERaM3pwTGY0WUZJNnRkL0xKb1Q0NTlWMGN1ZTFMSFRqemhraGV2 +NHJCL1BRQnE3MHc2cGY0M2g3QnBlZmk3eDh5YzljblhGcERIMktyQzM1ejBaVGM4SGt1cGo3R0gv +YktSL3VHdk9hc1VRK09jeWlyWFMxcWltYkZoc1c2K3JQK3JEL3JUL0hxei9xei9xdy82OC82ODFu +OGVaSEVRa05UZ2ZJVGRveXp6MmVob29GQkVLSzlIUkRZRU5oYVFNMTdEZ3d6Mmd1d3BFQnAzZ3Aw +QUlqaGtnOUJoamdJRlVQRC9KUnRUb0VSdTdpSUU5ZDhCR0FQL01STWlCRzI1MnBFRHJMbDhBd213 +dFJrZUY3SENGWmoxU0t1WEpvbUJyN2dXczVHVEdxeVh6d21SemcrREhQMnExbFQvUVZwWnZFY1Yr +NEU0ZzVMRGdSMStxNW1abkxoUUMzV3lnT0h0V3FIM1RQcjFaamNlSVFGVDJLcngxcy9GeUVjdzEx +M0QzeTRTRzRjeWFHMmpUbWNKTGJheFJKRGZkYkloWmMxNlBRZXBuQVBwM1YwNDRJVnYrYjAzek01 +Vitpem4xajlTb1puK21XOTJOWTdzUENoTHZpc2djMlFnMjcxUVArOUJiSWZqdnlLQit4d1o1MThP +TVFQL3RKSG1HSTgvUmQ3alQ3UHhjT0ZXUEx2SDA0M2c5cVBENWY5K2d1cjczTGhIQjkwaXlPZjdk +a0Rmamgzd2FVUE9TeFhhelBxejl1b1A1ZWorclArckQvclQ4L2tyRC9yei9xei9ud2tmMTRrVTZo +Q0pBdDR4V2tzSUo1NzB5Q1J1d0kwVkREa2FCWUExdm1WQTU4TlJwUVlBQUtMd1pqSXRTYWFadGl6 +Uk04Z2tnV0tpSmxUQUl6eUFVNVFqQUd2UEhKNHJvSFdJeTBIQ3NLUXFqNGtJc1JhKzlRcGh6VUVL +MGVHMk9vU1QyMDRzQTRmOW9rRjd4NUNzdzR2MXU3emlYOXdEc3hiMDJEVzRGKzgvN1d0UStOY2NO +b2p2dStMZnpEaVMxM3c0VTlPeitUeFBNWVVNN1dKcFM5RVlPRGF3U2NmM043RWlLWFc4TDBIeEhE +SkxIcWgxbkRpcmc5eVpGMk1vRDY4dXNNRG45aEVsLzdvQjd6NkNhOHJzZTNEOFpwNGFvWkZQVGtB +N0lGVFRiREo0UTZQdnBwWGs1aDdVTjV3cVhsL1I1NWVScWRxdGs4K256MFByOTVBMlcvQXdieTR4 +TE9hNFdRbTVsUVBMT3EwUjEwNHQ5NWV0YmpnVTQ4K1pJM2hzNXpoV3EwNFhUMU83WERGSjhtUHYv +eGhVMy9Xbi9Wbi9WbC8xcDlpMjRkanRhb1psdnF6L2x3K1ptMzlXWDgraWo4dmZtZGEwUW1pU00x +UzRMLzg1TThQQVJNWStaNEpwSkV4bVdEMmVvWTQ0UHhVTEliLzgxMUVLbFljb0t3aFJzL0VYNVBm +aWtDQS82ek16TzdFcGVIV20vL1huLzNWNXZHbTVaOS85R2ZIOE5NY0JST01uQXIwZTlJSU4yRGV3 +MlJxODUrbGMzZ1FNbkljUkdMaVlFMnEyYmVHSTkwYkhRZVd1TWlIMFlWb3pkTkUrV0dXeDVvSWh6 +RFVLVDVUd0NqMlA3NzNKL3NYdG9rRm4vWjRneUVIWEo0VHFFditOOS8vOHNiSE43N0U4WjNJN0xl +ZVdOSnNlQndPQkErZmRXTHRtc0dFQXpYSlNRaDQ5S2JHNzlJdkQ0UEhkK3Q4Vm9OaG5SanFqMWtj +QXNTclpueFo2OWNEc2tiLzVjaCtjL2l3VHIzaUdYTEZKRFJscU5jNnNkV0NHM1BXcWNzeitkWGt1 +eGo1N3ZNZWdtTVNYTmtIanhwcENUL1cwVVQ2Wlg0UGlvbGxIOXg0b3lFODBKQzQ2dEIvZDc5YVk0 +MjF0UDI5Ly9sZlAvbmhOMzd2MVhUMjZwZWFZY09aNzdSaXZYd09GZnFnVzlxMlR4N3o5V2Y5aWNm +NnMvNnNQK3ZQK3JQK3JEL3JUNStmd1o4WGYzRlljcUF0MW13RnVXejBVeXpBakNjd3dxMVZNTk1T +aFlEbVhKNER6MkJpdTJ1Vy9RajFHVEVBZi9pRFAxNGcxc3VIZUFRcWpqQVZZK3poTUFYOTB3Ly9k +REVxZWdYdThKakRRRHg3ckJkSFhHOEVES1FqVUxIcWtjK0FSZlB0a1JkNWE5QTVqUFl0eHd5Q0lG +Uk5sTk9hYlpaMVREeHJ4WWxvNUJBZkp5N3hjU1dHQnFqZCtQbTdYMTM4NXNTQzBZR0RSME1QOEdZ +ZmMvMzQyMS9ZdWdoV0RuTnBkSnFaM2xnbkhnR3UyYWQvRGtJMUVMOFlCRWtJL2tJOS9Bd21uc05L +ZldMaUg2OTR4N25oZ0xWV1hlYlViSjQrWUpaUGJnZHo2azkvSFdaRW4wT2JIdFNSZnNDbFZuRjlO +dVR5bmZqbFZndVRXQ2VuQThFZk1ORWlmdkVaalpyM0dYNzcxSzlHR0dNdytQSEJ6RmtEcDNreDlj +MkZkN0ZjY3FzVkpyekNZNzg1R29WSi9Yb2h2cGc4Rm8rbzI1dzgxdEc4WHRqcmJnM3U0S2svNjgv +NnMvNnNQK3ZQK3JQK0ZLZityRCtmeFo4WFlySEJUK3N4S1hFREJKZ0FLY1lHSUNRVDNGb0UyMnU5 +SmlPZWVBRVQyMXB4M0FtYUFCQ3FDTUlSSDVGaWE4akdINUs5RFNKUVEyTVErN1B2ZmVtVmJHOVpF +RXVJKzlQOVBKUERPajlweTIvQXBqNHhHVlhoaG5yc0kwYVh1VFF1dzJma3IzQW5Ya3dJZjk0T0Vi +aDkzazZwWHowRWhoZDc3Q1ZLL3d3M2ZoMG96S0VaRGltNVlaUkhYRU85OE1Bczd2dmYvTnlLRCtj +YXV3ZkZ4RmF2bUJHbVorYlBRWEQrdjZUVUtUOE9ySVVMQnd5b3o0U0cweHlJOElnTGo5ejJ4REJF +R0hNU25WeldlMzdXWEJlRGd4QVBQdU5Zai9DRU01clFWNFkyTDQ2aFp2bHdFb1BZSTQ0WTZsQ1Bm +ZGJKcWY5d3dzZ3M4Rmd2cG1mV3F5MzYxQ2QxMGhpdGlhdG1jY1MxQmc2MXlNMmczcVM1N0luK1lW +T0x1dFNpeC9iSm5kN2d5WjNaOU5WZXVQVkpqZTdxaEZXdjRhQnZCdFVYZTlSUWY5YWY5V2Y5NlY1 +LzFwLzFaLzFaZjlhZm5qMkRQL2RYY0JWSmZCNHdtQ1NLc1FsWWdBRFRYR3NRWVowTEFBUjQ1ak5n +d0l2bnpRWkRNcE01ejhVR3lrL1dMZ1FpQTJDeEFSTUxMZ1V5T3d6bUNKdDR4UENmdW1FaUNqaUNp +emcrK0xzL1dCSU1wQ3NZZWVZMXcwQStIT2FJRE5Fd2kzZkd1U1BWUVNJK2pHcUZNYUxERFY2UXpB +VGl1K0QwS3dCaWk0SGppRmdkWXNLVHc5QWFjUXp4aVFJZWQvL0pHMVp2U3ZURUh0ekFoRmZQY01D +WStIUXhIbXoyYWJyOHNNc1J2TWZJNTNmZS9hZDJ2WUlGNXJ6NVVMTTZqYTFydnNla3ljMGNEQ09u +Nzk1MnBSL1d3WUV2OVpzVFd6L2tPVDBjZzQ1QllGSWI0ZW83QStGQ2JXcG5PSDNiZms1Y2Rjb25s +M25QWVlzcDZVME1PWHlIWFgydUhEYjJNSmM5T0pVTEZybXNnVlZ2NU9NSjNPVmd0c1ljTGVKWlBH +dkU4QmsySEd6TXdZRmZuS3ZiYzRlT3VBNE4ydm1INy83UnZpM0NnM2pXMXAvMUo0M1VuL1ZuL1Zs +LzFwLzFaLzFaZno2TFB5K0VxVUFMQlkxb0l6RGZ2ZGx4UjVibkNZNEFSUXNVOEd1QVdXK2ROeE9h +K09idHQzYWRndElVYjRDQWtYUEpuVUlScDNIaUlVYUJob1loVDh5STJYL2lqOEFRNExsY01TZ3lE +REc4b1RLbnJoaVVhUHpVemdCcHNucGhQOE0vV2YyeUlpVmU5YW5CT2x4RUxKb2xqa01Ec2JEQWE2 +MzZDVU5lcGt4dUdCMWFuak9QL09iRk1NU1hVMjdYQjkvNi9OWks0RXlyTm5jNVhEak5nZXBPYkRB +d0RsNEl3QnJZWUxCR3JlY1FlOWsxY09ncnZPSE1tdzhZMVdqb2xUM3ExV3RjN1B5SUZSZE1KWWEz +WVhwc0hoWmN5K096dU9wWEQrenltYU10ZlZVdmd6QzdPWGhwUWd3NDFDVEcxanc0WUxXWGh2Ulli +ZkxDQ3BmUDBTVU5pZWt1SGcyditTWSt6c1YwOTkxZ1VGaHBVVHg0emRHOUEwZE94b0tIZjlSc0hZ +d01TUS84cFcrZTRRN1g4b2dGQXgzaFRBNi8wc01UWXFxTjN1dlArdE9hK3JQK3JEL3J6L3F6L3F3 +LzYwOVluOEdmRjhHUmJDTUNYWVFpQ1BGR2pJb3g1N3VOa2lMTU9va0I5OXlhTmU1OFZyeEdTSXdv +enhHbE1JMHdMdzR4eFNTRUY4QmlHM0lqM2pvRUVDZ1JJTjVBQnZ6MkU1WC8xQ3VlNFU1OGlESkhw +TnZzYVo3OU1HaVkvYmdnc2d4NDRjQ1AyQkdVR3ExVHYwdnRtZ092Tzd4cThKWUxiano2VC9PRVpJ +ajUzdC84N21MU1JQTXc0b3hZY1FLTE9Yejg1Ty8vY0d0T2I4VDIzUDZJRWk1OWNObFBYSzZZVDQy +cHdSN3hmVGJzWldKYzZWZHdXU2ZlY2pJSEYrNnRWU09zUkFRSFBnblEyeS9ZSEM3V1dBdEw5dU5G +RHIyRHl3VzdPWDNCbnl0N3hOZHptdkVXeC9Ob2h3N2h3UjFlNUhmWEMzMkdJekZjOEtnL2Y4aUlL +eDR6ZUdZZnZEUUJDMHpXZUpOSEl6Q0lLUy90T0VEa3BBMHh6TnNyanNQS0lRTW4zdldCaC9aUW10 +eDRWYnRucm1qdkYrOS83ZldOcmJXNHFEL3JUM3Zyei9xei9xdy82OC82cy82c1ArRklqRWYyNS80 +S3JnU0lBNHpZQWJISmN6LzVFaXBpMGtCSnJaVllVYjRqUUdPUlFiUUVSVXllZTVzakhzTEVSd3lS +RW9YNG5pbE9MT0RrOVpQeUVqVkRualhTeExTT2FLeFQ2TVljTW1DR3o1c1BndmJac01kYTJKQmdM +Uk9vQ2RtRWFLMmN5TjVCVURNMFFRMEVMcGNjQ05aUWJ3ZzBRcTB3RWFvNFJBSXY0ZmhQMTBqVzVI +dURpdW10bVRYaWlVKzBSR2dRQnR6Mnl1dkF3YnZuZUxEZnZKcUlqamprOVJsR0FtSWF2d2N1aGhx +dFpVaHJDQnYvOEtwUmo3YitpVzJOOVhvRG03NkpoVE5td3BtZXdhUFhjT0RoR1BTVDNhdDM0dEFG +elBiaWxJWjIvczM1SFhveGNNZll1TUdoblBwT3VPTHJsKzl5eEZSNnJBWjF5NTgvUU5RdW5yd3VK +cE1IaC9oeDBSZ3VjRTEzYXNhSlBQZUhTalR2TFI5Y25ybUxMNSsrcTFrdjlCRWVjOWJSaGx3NFRU +enJ4Y2RuK3JDL3B6OTU5VWhOY3RGS0RFcm45V2Y5V1gvV24vVm4vVmwvMXAvMVovMzVUUDY4ZUtB +WmlsQ2t3SXBUTE9FY0FrYzBFOHhHelpFRW9ZSWp3Q1doZ082YWdUakFyUk5mSEkxZWNVMTh4QkFJ +OFBLR1JER0FjM0FRZzZFNHY5S2dDRTFUb05pTDdjNmc4R20rdHkvaUdISXducGoybjVqbkx6QWpO +V1JFb0dmNDlZVHI2MEVEaTNnYTYvc2VRbU1DRGRRa2ZOaWJPYytzaGNVemYybmNyeVFjc1o3ZnU5 +KzNQamNoTWE5LzJjemV2SmxRMzJLZnRRNDQ1c0NUWEdKN0MwTHNudU9OMkhLSXlFL1F4T0Z0RGM2 +SlEzejl5Y0ZrTGY0OGt3ZFA0WlpZNVBmOTRMNXVmREhTYzd6QkFoYytESEZoTTZkMmg1V2E1QkdI +M3VpS1FkU1NIdE9CV0o1WlIrUzB3NXg0amtIdGM5REtMM1o2b1orSkIvZldOb09KMVM0K0RhYytt +c0VQak9ibGNEamdRZzc1N2ZHckk5R3BtRFJESDNzNFQxNTc2TnR6Rnk3b1hGN0dFNCtPWE9iMUt4 +ektLUzZ1SEpnT05uK0hRQytzOGJ6K3JEODlrNmYrckQvcnovcXovcXcvNjgvNjAzaDBmKzUvQVNV +QVNaYUFhZlNhYUlDN0t3UzV3QkFUd2hUa2VZcENqTHNHeHFCK21pWlFTVFNlaVB6VUhTRXpoQXRB +alFEUUdxWlRISUZvbUVGd0RJb29heEhsL3p0SUxtUWd4Yk1ZL3dkLytkdUwwUkRETTJ2bHlJRmph +SVJueUxCRzR3anhqT3ZHRlpQbzdJZGJYUnFyZ1RoWmtRenAxbmdqSks5bnVNQ3BOei8ra2pXT1Bi +ZUhpSmpTR3ZsLzlMZS92MnR3YUJBYnp1eTN6OXNrZFd1d25qZ3NjT0d6ZXZEbTRJUkxUSDF4bWNj +ancxaWpkeEdKdytxSStQeU9QQnhpaXFFV2ZJc2xybjR5R2NGdHIwZmtPUkRzSVh4OEdIQjdwa2E5 +ajlsaVVBZVYzT0tLUTZnR3JuR3JOanpwcXh4d201UERjOXFBbFc2SVh1ODh0MDV0ZUNKMHRSdm00 +WW5SeGRkVE1jMzVyQzd4N0lmWlowTU5mcjNHZnMvZ2k2N1dkRGVET2pEczFRczQxUnFEbWxlM3Rl +YmxWRE84VEE0VGJkR3BmdU9IRnVYeHZQNnNQK3ZQK2hPZStyUCtyRC9yei9xei9sUzc4ZWorM0w4 +RHFvbnVCR3VqSWdBbHlJQVZWTkdhYTZPQ2dkUk13QUZSTEFESTlZeGdOY3huYTllZzgxMThjeHBG +akRHZDJEa0EvRFBPQ2pRVVo2MjNPZGJLcFFna0dNaUczM09pK2U3LytDOUxtQUhmem8wWWZJWnZU +VGdrMnFlWjV0d0plUWRCM3Q0Q21JT0RjTldCQTl5b3hieUdtSFBZRUxYMVNGZUR3OC9iRUxVeW9O +b000dlF2UWtVZ1RPeTd2UWIrOEtRV2RYcHJJS2JlV0VNNDR1TnNEVGR4OHRtZFlKbUNzTlFFbXoz +NHdyRzRjT21qWVIyTzlFVmMzL0d0TGlaaFRwalVqV004aWhXeE8wQ3RjU1VmVEhMUkZLNXdTZ1Bl +RUtVZmVOMERZUG9oQmp5dzRSVitOZE1mREo3Qm9FYllJbTU0MGd0NDFPa091d0dETmZTTEY3VlpZ +NzAvWk16QjRyUG5EcUN0MmQ2cHdRR1plVDFKWERuRGlaclZBUnN6cXNkNnNjU0EwM3Jjd21PdE9M +UURCd3g0ZFBBNGpNVVRGeis0cWovclR4enBTLzFaZjRwZmY5YWY5V2Y5Q1YvMDRETjg5V2Y5YWYw +aitQUDFCMUEvblVZRUZpUExISUlOd1FWS1V3Q1IxRnNWUDkwaVZuSUdJbTdtSmt4QzJDWk1nelNC +d0QzM2U5K0VwMm5paXBHY3dCTzdBZzE1R0VnQmZzcDJtVU9vb1VnNEZNUkU3L3pGYjcyYVZ6ejc0 +TmFjRUl4RXRUSzkydHcxUG5QRTV2ZkhrYVZlY1FsUEhBMkxnRFFkbVpxSHczT292Q3o1M3BEQkxi +WmZVOUFzYy9BenBMejQ4Si9CL1V0Z09EUEV0QWR2T0RWUERJVERnQXdDRC9PS3hmaFpxdy80V3ZF +TlRuR3NGZHRhT1lrVnR1M0wxSWtYaHdtQjZaODYvV3FEV3ZWL3hUeDM4ZlZJVE56aDBuZllESGNj +Nm5seTJZZkhjR0pPYkh2eHRoaG1xRmsrKzQ3SXozb0dWUisreGNLdnVoMnU4c1VBRG5FYVV0dDlY +RHJBZzN5ZXVlTlFYTFhLYWIzOGFzTEJPVWl2KzV3M3JNL2hlSFIxM1pyd0xGNytzSUFkTHRnMzcr +aGFEcnEzOXZYUW10N0FvazUvUU9GUUQvU1ZMK2hJYnMvcXovcXovcXcvNjgvNnMvNnNQK3ZQK3ZN +KzdxUDc4MEs4RE9NT3RDUkVvOUZFN3hseUVNQVFDaktITE1RSUd1RVNNaERBU0NDUkt3VXdhVVRE +Z042TWlLdVJnSXVQWkd1OElaTEhJQkMva3NBRWlpTTRlODloY0g2WFBjSWxrSGYvK25kMm5TR1cv +TENwUzU0TWdoY1RXZWJoK25TY3R4WU1iaCtqcWpGMXlta2VmbzJBQ1hZTkN5YmZyWlZmdlRqUlBD +THdPL0x5d21QZVlaVjY4UXFMWG9qTlBPSGZHbWJ6WEd3eGlGSk04VFViei9iaVF4eHJyYkhmdk1O +TlB2VWJNYWgxTHQremp3bjBFeWJta0FNWGVvYmJIS0p5V2lmbjRwcDg1bkMwWXc0Q0ptQlFsN3JO +VzRjdk9sT1g3MFJPdUV5aERtdDk5bHd2NUJDTExxeW5MZmkyenVIREhVOU1aRjVjZS9SR1RscG5E +dkY4RjUrZTFTUGY0ZVZsdWVJTnZWVzMzc1BCRDNpQlVVeVltSHIzM2c2a0dGcGRjT0xHdkI3UUp2 +N3NFU08rb3QzOFNvUzQrSzQvNjgvNnMvNnNQK3ZQK3JQK3JEL3JUM2M4UFlNL0w0QWpURVA4Uks3 +WUVDK3h3dGVnSXppRm1HTU1CQ21Td08zWEZPc0ZCbDV4M2tJbzBQOXZFVEJHeE1DNDNwNGdRMDZG +dUNPWTJCaVVjSGRNYy8ya0hwTTRPTXl2NllkSTRoVVg4ZWJlLytiblh2TWhSVzBhWm8wRDVJenI1 +b0lqNUt1RjBNUTBpRXRPejVHbkpxUTVET1RFbDd0bjZzY05NYWhmayt5eFZ3eS9FKzgvZDR1cGVm +dC9Zang3SEc1cWVmUDJXeXNFQTJZSDBNYWRlT3JTU0UwTno1NnJKNXhGSFBqVUcvMnpYOTNxczBa +dllrSkNJQXhETEgxS2JIaTl4VkduT3ZSZUhySGtzWTd3eGRFci9TRkF2Tm1uWnRxZ0Z5YkE5Y1la +cm5DQzh4d1dFYnk3QTAzZjdGV3ZXdFRzd2hzdHlnMXJOQWdEd2FzZFB2MjFYcTN3V0djUFhZUUR2 +YlplLzJJK0pyTFBtbk80bjdlYzNzNjVlNjVtbUdDQno2VkduSG11SnRqdDlSMGUzMk5RKytIQnBi +dGE2ZDVhWFBBUkhkQXJ6TERWbi9XbldQVm4vV25Vbi9Wbi9WbC8xcC8xSnp5UDdzL0xxMkdtQUdS +YXJHalBQUk5BY0tBazFIZ0pGT2VLQUswSDFCcEVNaGNERWkxQ2dSY0hPWXIyaHNKYkV6R3NCeFpv +bjkzdElSQkRnZko0VzhQMGNMa1RpMkdQL0M2aTlJWUlvUWJTOXdBWmtoV3V0Z3hyQ0FZbUpsQUhN +UjJUem0zd2JzNnBBMW1hcng0eE5RSDVPWXdpRG5WN1puZ3VKdEs5NFZKejZ2V2Zvc1dFMmJ4ZldW +Q2pnVi8xRWF3N0ROdjBFWlg4Y2pDMk8yRzcxRVhvWXNPbndkYnExeDZzTTQ5UHRSTEtMOTcvMnVJ +emNBT2IvUERnUkkrc1pTeEMxTDhjWkdyVkwrdGhaVkRyWExSa1A5MHdMejNoTS9QTWlXdjc5TTg2 +OHpFb0RkS2F0UXdJcXo3QmtJTW4rL0VSRHZBS2wwc05zQnIyMElROU1JY1R1T3pCQ1I3a3hCUE1p +Mm40Rmd0ZWUyZ1NCKzVpNnJHTDRmWENjM3Z4YjhDbkI4d3VEeTNwaXpWd3FCMXVzZHpoZ05HL0Zx +ZmZlSGFwcGY2c1ArdlArclArckQvcnovcXovcXcvbjhXZkZ5UVRBWE1KbnNBVysybGZNSU5KUEFj +SVlZcndqRm1JVUpNVnBIaHZGRFNLQWEzLytLUHpsNk9CVkt3OUJPSE5DUERJRkJzQmlyWVdwcGdK +QnZNTUJxOTQ0aXZBaUhBMXhoNS9TVnRlSTdIc1k5U1kzbEFyTWp4TFhJTTQxcWdqUEh2d2dGeXhO +UnZKMXFnL2pkVmtjMHpuVFVnTURKZS9tQncrTk1NOGcrSmNEUXpxalUzeTR4Zi94T2ZOa3JvTi9I +aG1uN1Z5aWFkV3RjT2poeEd4MnZCRzVHcjAyVDdpZ09tSStMcHI1UkZYbmU1cWhsMDhmUk5EL1ho +U3ErZjZEdE9PTVpsbk9UekV4b3YreHFENEpIajl4NzI5RE9tNUhxckhIVzl5Mmd1UEs4YkZGM3pa +cnk3OU1KL0R5T0YrdEhQK29yMzZIRGoybU1jVi9US1BubG5yODJwNzZsU0h2WjdEYXIyYTRSQkQv +K2pVb1lFakI1SzQ5bTg5dzZrREFMWTE2TlJqMkcrZE9icUVRUzdQSEU0dWZhRTVjZmZBcnovcnov +cXovcXcvNjgvNmM5ZlZuL1ZuL2ZrYy9yd0E3aitkbXhRY0VFMGdDZ3NsUlM3VElXWUxtczJFQVNE +Q05VcmprWWNrNnpVWVVVeExLSjRiTVRPd2Z1MkFHSkdDWkVCOUZvZXBZREdRSWo2Uks5cm5mZnUw +VFRpSEIxeGl3K3RYSXBKUGtScGt6bi8yUjNhR3VCR3g1OWJzR0VHZGNWME9DSTZvNGJJV2Rqbng1 +Vklqckd1Q3FXMGJQS0l6QjZQOU9GYVRHc1gzbDdiRkkwcS9yc0NrWXVOT0ErM1JCMjhNeFBYMkIx +N2MreTRPRTNnem9YZnExU080NE1HL0hscm5rZ3R1ZDF6cisrSG91clhyQlhQaEh3NTF1K3N6TWFw +UFhKZm5NS2FQc0RHVXR4Mnc0RnZkOGxxM2ZONU1pbS9ZNUxGMy93Q1lPY2EwTjRlL25KN2hYUnhh +VTUvKzRnWWV0ZXV2R013TWw3ajBnU3Y1UFBlZDhNVlR1enlNSXc4K1AydFFlV0R5SFZaOHd5V1hl +bUNCVDMvTjRkbmFuWjg0NlQydThPY3pMRDdiRDY5NDFzSmszMnBuOE5NNGJlUFc4L3F6L3F3LzY4 +LzZzLzVVVy8xWmY5YWY5ZWV6K0hOL0FBWENKSUtCOVJrQmFYd0lWQ2dqQU9PN3BFUk5lTUFvMXB5 +Zm9LM1ZLUHNWRVBGcmtFdHliMFVJZElIZnlBc1J6QVMwZ1d3aXNvOW94WDQ5UEVZY0NvYlpHbnU5 +ZmRKOHczZHpmb3IzbkFnenhGMHhEVzQxT0ZET3VON3VMMHNZanBoQjdXa1dFMnF3UzE1MXlPTVF5 +eHVpR0VydGZpVUF6NHRyK0pSTFhIWDRGOElZV0J5RFNOVEtUUDUvYzlSTE5ONkl5SUZUY1h5R3hi +NElIUzRZTkJtbTNUUE5oc1BkZ1NvdUxQakdPMjVnOFRaRS84V0RTNzNlYmhDajV4R3plbUFRWC82 +OE1YR0hHNWVNQmFOYTZJUm9tVmgrMkY3blo3ODV3b1hiTTV6cXF6c1Q3bHVvV2VNN1R0VkJuN0Ja +VHovcVpseDkwbVByOEdHUHRlcGhDUGpsTWM4NDFxdEZmbmY1R0RCOVVLczEwU2VlMVdrTnYyd2ZC +bzgxdSs1MklJa2pqeGl3NVpDRDFhR2hmbldKNFprWS9PWXRJcXk0RnJ2K3JEL3J6L3F6L3F3LzY4 +LzZzLzZzUDUvSm54Yy9PUk9oQlFyV2RNUUw0Qm5qRWF6L1BCeHpDcXhZU1FYVkVNbXRaUWhKQ1Zs +eENORVVJbENVbU1Sb1RnSGlLQXdZbndrUkNRUWpwaUVIak13cERsTVIrRFozUnBxbUtERzlJZkpX +d1lESk0wMGpEUFZseEFqcVFrck11NEl5aG1qUG1Xc05PRG1Jd0Q0MXdxc2g4QkMzMk9yU0VKekJw +VGJ6WWtTWU1hanY4UGwxQlo4L0hpTXl0b2FwazFFWUNROTRVWk5uRGp4eDhFRUlPQ1lnL2JCWFBu +enBpOXJ4Rit3KzQ4MWRIOFZSR3p6cUoxSUdWb2Rld2twUVBzZWdQcXZiZXIxaUpwekJpSzhjd0N2 +ODRVaE5MdmpFbFl2WTkwQWVMZzFHZ29NTzZGQmVlOVZqbmIxeTRjSzFHaHdNTGpIVW9XNThyc0FI +cDJHUFdyMEJzMVk4R056bHRNL2E4T2l1dHdZcy9LRXY4cWtIRnZqVXpPQ3c2QU51ZUVQUDdjY3h2 +dlhNT3Z4NEJvTTFNS3N2YnhwcFIyLzlJYUp2K0xPKy9xdy82OC82cy82c1ArdlArclArckQrZnla +OFg1bkdabE5pRXhRSVRuV1loQy9tRUFaQTE1Z0V4bndLQVJCQWppb2tvODhSR2FHSWdRQnpDY1Fk +dVNSNHdZaXRTd1FUcnM2RlFnb3ZZRWE0SSt3eUZ3cng3UnpUK1QzRVJhY0RKZ01nWEU4a1oxb2dy +TDl6cS9lendFN3RmRTRBM2h3T0N2ZTFnQ3A5amJyemd3ak1EUC9MWlF5QXVndEFzTWYxYWdyZ01h +azJ3V2JPSHl2Q0FOM1U1SU5WQXhITDRyR2Y0ZEZtRGQ0S0VXUzFpaGxOclhlckVuM25QMDIvY3FD +RmM1UkNBRlU5Nkc1N2dWS040UkE0Yndib25yL1Y2anlOdmQ4VEJtYmdNcW1kd3cydUlBOWNhWUE0 +RW1xTU4zMkVVbTZuRWRmakRTYThPUTlxeEo0ZStIR293YUFSZWZ5aFl4eGg0WGJNTU52b3d2enpN +SWJiR21UM3FVYitlcVVudE9VRFU0Y0NSSDU3d3ZERm5MMHpyaDhFZ1J3NVA4MnFRSTdWNzIyVGVm +bjF3SUtoRGJtdnF6L3F6L3F3LzY4LzZzLzZzUCt2UCtsTU9OUmlQN3MrTFFqMXdGMXhnQkNBM0Jq +TTAzM09BWE15Q0ZJUUFEb3dpN2RjUXo1blVmb1M0QUFIZXVqWFppRUxCU0hJQkNSZ1JNSU1jQnRC +K2N0YlVDQWcyY1JBbExoTVRMa0cvL2IvKzI2N3psa1k4dVpIdjF4N0V6a0FZTWF0TFBEa0o2WFZN +SGVLb1JYd0VFakhSTUI4OGZzSW5pT1ZnZUZFWHNjR0ZmQ0tHU1h3MTRFZnptZk1IZi9uYmk4MSs4 +OEdtTVpwdWpwaHhZOFE0QkNPdW5JeWlEeG9QRTJ4aWVVdUQzejFzaHlmelB1dUh2U3ZHcVY5TXor +RjIwVUlPN1hNQVhyY3ZSTXpRdUJKUEx2dHdqd1A2ME52a3hudEU2MkRHQjVQQnBBLy9QNFBxbnp5 +TXlhQnFVenNkaU9HNXorcFdoKzlNQW9QODhLc0hkMm9VQTA1NTVKV0xPYXpUQnpuaGkwbng3YnQ0 +ZWlRL2ZYanVzOWpxa3N1bEhuaHdKWVk4TG1iMDNhVk9PVDN6ZVRGTWpqMElKbys2OUFZZWRlR0d4 +bUxRK3JQK3JEL3JUeGM4OVdmOVdYL1duL1ZuL2ZrTS9yd1FGZkJBZTJDejVpR0FFRFRCa0VEemdi +Ukhzd2dEdURYTlBOTVFheENqS0tKV2dOaUVoVkFYQUVRdkQzRDJ1YXdWMzNwbWtjUHduL2VKMnhw +NUZPNVpCQjJEYWdxQk1haGlrYXhRTlpoRHR2Z1o2dldHU0syRUJjOS9HRk1IUVhwTG9NWTlURWFj +OG9WNFRTSkkrWUlQTDR4T0hEaVVRNzNlYXVFM0J2M1IzLzcrbWhlSEdxTitod0xNNmhmWGMzRVpS +YjI0d1MxdThHaE9ualJkZnJYNEZRK2NxMWZ0aExHY0R4ZjJ5YVVPdU1PUDJoS1h5R05RejNFa1A1 +NTlad3p4Y2pBWVJLdFdmRVdnOUpCRHo0R2xWb2NPY3hEOUduUjR4aGtzOHVOVTNnaFhITEVkQkQ3 +RENyOTFEZ1VpZCtGRlQrRlAvejBYZzhieFEzL3FFRnNOZW9vak5la3hYSjZMdS9xZS9va0ZLMzNn +V3gzaVdpY1d2dVhBbDk3QnFXNXI5Y1djdnU1aE1yb1JDeGJmeFlxZTlEWUhBbHpydCtsbi9WbC8x +cC8xWi8xWmY5YWY5V2Y5V1grbS80L3V6d3ZSZUVnUXlBSTBQNmxyOElwMmdqTW84T1p0bGhSQndF +VzhBdnNNdkVZUU45RGl1WUFCU2x4RzBtenpBT1luZE1MUVlHK0ZOTU9Beno5cFRTVGlhNklZU0Ri +Z0VoL1I3Z3pxczZacXBvdHcxUWgvaGhyMkRjdzhFdzlCOTBPRDVZTk5EczNRTE9UaVNuN05ZREwx +aUJjeDRVd2p4RFV2anRyc0pVaTg0ZDZjdTh0ZS8va2JkdHpBNWordDIydVBoc09pSjVxMzhVWUF4 +T1E3WHZSQnZReUtML01FS3paRHVlUFpuU2p3SlQ1dVZxanozRE14OU5Id1hFN3g0Rm94M3ZhcVVY +NzE2dS9XTldaWUxtNWFZZEExNmRRZ2JyVG1ZaGg3NlFDM2pNZncxcXNKWDJMQTRqa094UEJNUE05 +alhwcGpFQWNibkptekJ5YjRhVTB0dUdKQXRZV241RlpQTGxqeHBVN3J4SkpyRDVXYlFlV040ZUUx +ci83a29CMWFza1ljdGNybk9ZeldtNk1oMm5kWG42dityRC9GcnovcnovcXovaFMzL3F3L2ZhNC82 +ODlIOStkRmt6WFRUOExBQTB5b3dFWllDQ1FjWkNJQUNKY0M3TE5IUXFRb0FpbUVLWUhQaUNjUzY4 +WHdQWTFVZ0wzZVVoQ3AvZGJhYjg3d2xvUmhHUjVaYStaWm80R0c0dlp0MWhEdi9zNmYvL2VOQ2JO +bUJRc2lDVERETXpuRlFZcjRaNXgvSlF4eHNDSWFQcHpJTFFieEVaaERTaDB1bkRnSVBETjhWNC84 +bmhQMHdYemRaLzdQZXpXRUNOUkllSVFwdGh5YTc0Mkw1d2E4UHF1UkFNUmJvMDNmeFBVWkpqalZ4 +WkFFSlk3MVBqTVdudVN3Qnc3N3hJUlZMK0d4Qm4rR3ovTFNnanUreGJQWDV4WGc0TmJmOE8yNXVQ +UkNBeTZIdlA3QnRqcWFIb1VyTmFnSHovcStQTXgrR0NONjJ0RUxHTXd4aWVkaXVNZWc4cXRkVHRx +MWxwYlZoeDkxaU1PTU1PaXBmZUxpaDJtWWlPSFdvS08xenhwVUxURGptb2Z3cVdaWW1WRk04ZGFR +czg0emRkbnJ1OHQzdVZ6eG16K0kzTVhCY2YxWmY5b25KcXoxWi8xWmY5YWY5V2Y5V1gvV240L3V6 +d3NBUklBNDRFT3FEY1JEV0lKckZ0RG1nYkVHWWNBRDdUdUJwd0JrQWVpN2hPSXJTZzZmR1VKdTVH +cXN2Y0FoenJ4RHcxcUQrUDF6elo0enRyam1OY0ZRakJ3T0NNMTUvNXVmVzV4aWE3b2FpTnNiRjU4 +elBCTUhSbkczNmNZMDNsQ0g5UVJ1RGZINERwZTloS25KZm4xQ0xaNloxMWpEWi93d3VkaXc0QkNm +YXZIUGRPTllnNzBKTW0rSUU3UGcwWDZEbU5SbkhlRmFBNXNHMitQQ2hUWHFZU3dZNUNZVTg1b09r +Lzd1Z1RlZi9XZDlYQktWSEhxRFk3VTVUTlNkMnUwaFJQMjJ4OTErdzNwdjgzS0FXQytlZWhsT1Av +UUlWM0NxSVlmd2FtYjIrY09CSGd3eHJDRm0yckhHUG5YcGgyZjJ4NkE0Tlk4VGVMMnhNbUFRbTI3 +eGp3ZHg1ZEZMYTVrV05qVWtqem1IaWJqMjZnTmVHRi9zK3o5c3hGdURUaTVlZ1NFSFpEaUFVMHox +MkVzL2Nqa1VjSy9YL2lDU1U3L3Nxei9yei9xei9oUy8vcXcvNjgvNnMvNnNQNS9GbnhlaU5RSEVK +aGhnSmpRUTJlNklpTGswUk1PdGtTaHZrd0FoS0lVb25FZ1VBNmpDTlphUTdBTUdLTVRJUjlBeG5k +akkvUG03WDEwOEJzRzg5emUvdTdtSTB6eFN4Q0VneFhvelFXeWE1NStwRmxzemNvQlk2eUNDSjBO +dU5acnpyM2FKcWM0TUpPRkZYVVFvdHJWd2FhaThzQ00wcGlFcXpVQThybkREU05iajJSNDVOTmFo +d3BqcVVTUGhHR0xnU3czbTVEWGcxWFRQSTFKNzRKVEhCWU80NG0wL2hrL1l4WVFIZmpqTTRSTTI4 +L2ozWFV3SEJwN0NoVGw1eEpJMzRrMStQT3NETGh3NE9EZXZCbXYzVGMzdGJRNzg5QUJ6ek9jdmNE +TUdYUGJFb0hpMkJtZHI4cmxnaFJPVy9LRWhMc1BnQlNaY3EvL2dPci9DUW8vbVBET3ZQaHFVSTd6 +b29aaU1aSTljL0NIdS9zRXhkY0d2VGdlT3V6ZzBJdTc2NTJiR0hKNzR3b1dZYW94QllWYW5mQTRa +dmNPTGZIU2FudFdmOWFmNStyUCtyRC9yei9xei9xdy82ODluOGVlRlFDTXdteVVRT0VVeUhIS1JI +QkEyQ3FvSWdLM1hpSWpFdW9qRVBPRnJPa0pjR2s2Z2Nvb0h1TGNHY01qcnArUjdnNHJoTHpVVEsy +R0o1ei9wbithZU54T0lnTU5iaVgvNDdoOXRNNUFTREFnVEg3NE04Y1ZTby93T2lCWFRiVVJRbW9j +OHVOV3VXWVJFT0RqekJvdTR6TU9BRzN2TmlhMVdPTVNIeVg4MjkweXQ0VjU4M0J2dXlXR09JTzNC +bmZyaHNUY0hna3Q5WXZoTU1QTGh5enA5aXBIZ01aZjR2a2VnZXhnTmYzcUZKeUkwUENjWWUreTFC +aGE1OEN5K3RRVEhvR3JEalR1TWpNNUFPRkVMWEo2NzlNNkFqWW5nM0dlM2V1VVZ5MzV4WU0wNk9Y +SEZLT1p4RDdlYVlwcU5QVHJSWDdueHIyNm0weWY0dzZFOWFwQmZUZlNCZjNVbnI1cjRnYkdza1ZO +YysrVmVYR05RZXZUWmZ2akZkSW1wTGx5NEhFN3FpTi80aHJiVDIvcXovcXcvNjgvNnMvNnNQK3ZQ +K3JQK1ZOT3orSFAvQ3loU0JkL05VMHhFUUR4K0VpZGFKQ0JETW9rQlJnQmhhQmlEdXJ1czhjWW1i +MjNjaVlkZ3ZSbUlZUlVSSXlISFQ4YWUvZlE3WDl4ZlNVanp4UGpKMy8vaGt1cGlYbTkwTkVJanJk +TjA1SW5qcllzNXp4WHVXWjdMbDRFRU9LeEJDbks4eFRyait0cDB0Y0t2eVQ3alJKTnhnak1HVmFQ +OGV3QU1KZzNFaWYxcWtzZS9iRVlBR21pZG1ERGgyOTFhQXk2aWdHdHJudjFpNWtEMDNid0c0bE0v +OUlGUTFuU0RUM3pyek9rbHJQcmxzM244d0NzbkU4QmdyN3Q5T1N6VWdUdDE1eEN3Vjgva3dDZk5F +Qmw4RGlFSEZRMklwNWJsY25pbEkva2RaaEhnOW5nRUQ1dDZ4V1BFUEtNWmNZblpjM3pqQUI3REdq +empGU2V3eTJ1TnZiaVdnN2IwWURWeWU5TWpsc3QrbkVTamN0bGpUcy9GbEVjdmNId084ZXZXN1R0 +ajJhODJkWm1IQVVaNUhBUzBCQ05lR2RUblk5QlBsa3U0eE9FNU9YTncxSi8xWi8xWmY5YWY5V2Y5 +V1gvV24vV252TS9pend0aCtzdkNHcVBaSGpJWlVINzNHMmpFSUR5aVVJUWtQZ1B0dVNaSEZBQnBx +cjBFd0ZBZnowL0tSS0lRQkFFak53SEZTRVR1bWYralhZTDFYR0h5aUNFV2t2M3p6dGJBYkNCRVBv +MjN6OXVrRUErRDJMQnBrajNFWkNDTG1HRFkzOEVmWEpuems3czZOVUI5OERzRU5NRSsrZFNzSVl4 +dFRoN2NFUVhjRWJ1N1BNU3JKdnlJaDRNZmYvc0xtOWNoS1krQm93ak5YWk1OVFlXSFVlUWxFSDFR +SjF6eXVQQ2t3ZmFyVFQ3clBDTWFmTW1sQnJsaURpSVIyN3FJeDFyUHJNZWhQZkRJTGErMURHVG9r +OS83ajBIRms5OWcwSENpei9DNzhFSHN1RjcrWmo2SEdKNWRCQnhPMHhNODI3ZC9jQXdPK2VTSFZm +MTQ4OXdCWTI4T1VYWGd4UjBYMGJIYTdCTXZ1ak9uUi9MNXZMNFl2Ui9NY3pqTU0zMFhHNC9pd1F0 +TERpOHgrWXF1MXFDRHlUN3h0NjU1amh1Y3llTVBUSHFDRDNmMVovMVpmOWFmOUZoLzFwL3cxWi8x +Wi8xWmYzcis2UDY4ZUZ2REZJalhTS0xUYklHSk82SVZIS0UyQWFHcEVnY0lZaEJzTDRBSzg1OTA3 +ZkZyRU14am4vM0lNL2VMMi8vWEVhRGVPbGdIRDVBTXFCbUdmWDd0d0JvRXdldjdhZEswYVE0UStI +MTNkN0NJQ1I5TXpBcVRPYzNJZ0kzWTRIY0FxRVhUei9YSjhoSFJ3S3dtelYwelRDNHhneHRtSEZp +N0p2LzF2KzhhOGVWVnY1Lyt6Y0VyRHBPcWc4QTFUdjRWOGx6MmlLZHgrZ0NUZmRZUUFwSElyd2I0 +cmZmWlBOSDRMajUrQ1FLWFB1UENrRjg5NmFjY0RPV0NtNEFNQXM4aGhUdTVjS0gvT1pSOU5zUnlR +TnJQd0REWVo0Z1QvUExLS2Q2cjJHZTl1Yng5OGtiTUdqa1lERWZXcUpGMjZNQndrRmlIQy9QUm9E +NVlpMHZjNFVRUDl2Q2J2ZVpkOXRrUHAvdzBJNllMVi9pd0hnN3I0Vk1IalBicXJ6YzY5c3FyajJy +U0l6emhDdzcxV0wrSHd2Q0ZYNTV5dHg3SExocWhKYkhzclQvclQvelduL1duZmZWbi9WbC8xcC8x +Wi8xcDdhUDdjLzhWWEtaQTVvS2RCSW9FR0RBQUJGRTg0alhaUEhLdEI4eTZnSkVVZUdSb3VxWWgx +RS9Sd0NuYWV2bmtGUk9Cek9BTmpuWFcrelVGWkhoVFl4OGhpd1dQTnkwZmZPdnptOU5Rb0QxSXRN +YzhRUkFtZ2duQ1dzYU9ZQXp4SW1Kdm5UUmd4NWpMRUJjKzlWb0hHK3lNZzB5aVFhU2Y2TDI1d2dX +c0JJVUhOVEtZM0xpd1JtejhpT3U3UThwYWhsR2JlU0t4WGw3NTVNSUQwVVI4M21UZ1h4OElEQ2Ex +aDF1WHQwN3BxL1dFb285aVpVME9CdnpCSkQ0Unk3VUgxUWhMVEhzOWp3SEVZM3I3YzVDS0k2ZGE5 +Rms4dFlqallOSVQvRHYwUFJkVDM4MkxJYTU5bTN0NDJOb212dTk3NkExdWVmRjV0SEdNblVOSUxQ +MkdrUmJoMVNQOXNFZmU1Vzl3NGRUZGZybXR0MitmallIMFJEMzhvVTh4TWMzQXcvamVudG5EL09M +cUFRM2lqQTZTVHgxcWxBc21zV2dyTVdDMzFzVURmSkhEdnY2c1ArdlArclArckQvcnovcXovcXcv +WVh3V2YxNEl3UVF3eUhGSE1BSWtOcTlBd1JBQm9DUUlsb1FBTlJyNE5Nc2xDY0hGVk40Y0FBK2Nt +QWh5RjhzUTM3b0l6RnNYV0RSRkkvMk92TDNXdytzdEVEd0dNZ25NT29Rb1V1SHdLRlRUeElJSm5n +eWY1VU9tMzdrL1JwaHhNeWdqMmE5R2N4b2x2ejBPSU1MWFVPYU5pTlV1TnlINVQrS3dwaVlZY2Fu +QnNDZXZXRXl1MllZK1dLc1A5c3F2d2VMS29SYmN5NkZlc1FqUWZwemlFVmEvcW1FdjN1VWlGUGtK +U0Q0SGpuaHEyZ041WXVBS3o0ZUQ2NXBLUE0vZ2tzdjZQYkJIUVBZVHRMVkU3dTFHK01hZFBYSjZx +d0svUE9MaENTWUd5cUVrSGs1cHdSNzlpODcwd3ZNY3V1SVk0VW9jZjVEb0IwN2dFbDg4OGVrWk4v +Ylpvd2ExTURVZTVkWmp1TlVtcHpWNlNEZjJXQ08rUElZN252MmpBcDZMaHlmWTdaWFBzOFBQT1V6 +a2h5VUhoNE1IUnB6b2xaN0lLUmMreGFnLzYwLzlxei9yei9xei9oU3YvcXcvNjgvNjg5SDllVEho +QXN4Q290Vm9rMzQ2QmhxeFRLQnd5V3lXU0dIMklrZ3pFR29mSXBINGF2UVJJRU9KTFllaUNFOGNk +eUlBRkJFUnNsOFpZR3FOUVk3ZkpiZFcwZjcvbEloUERrTWp2UDFab2M0YThRbENQbXZFZEY4aUoy +Y0diSjZyM1g4YWxwZlFNdFNxSHNRaTNMeFk2b0VETnB4NGpqUDFhcERjR3UrQTIveE1PdUwzOW1S +cm5jYkVNQVJ0SGNQQWJ6Q1U5ZmpYTEhIdGs0OWdJMXFYdkdwMm9PbUZYTERoTXNJeHI0WjdnMXBq +RHo3c3dRV0J3S3VQT2FUMFBnY1RQdWpCSG9lTUdtQVZHMjh3ZXp2bmpnZHg3SUZiSEpjNGNIb09E +dzNaeTNpd3Fta05Pbm9RbTVEbFlnWnI2Y2U2OUJIUCtJL1I1ZFYzbC9oMGE5Q1FnNXR1NFJWTEw5 +MWQrb2duY1dIeHpGcTkwUmR4MUk3djVSQS9jNGxCcDNoVG16endPaUQwYnoweitXSkU4YTJSUTAw +dXNmWEFuSHoyMlkrLytyUCtyRC9yei9xei9xdy82MDlyNnMvNjgxbjhlZkVBY0ExVnJJQUVBUlJB +a25wTEk2QW1Jc0ljVVNOY0lJVFpROHpJbDFDOC9kV0FLWnJJQ0ZCczVIdWpZZzh5RlVoOGh2MzJN +SisvT003VU1JbjNzKzk5YVVrZ0l2TitaY0ZlZzJBWnpKeTdmQVpDRUtyNEVPbDdCZ3p3SWlZR1cv +SnZ3M001TlZwc2duWVFpWWRFcE9OSHJYa3VoaG8xeFdkaXRRWmZ4TXU4NnNVYkFUaHN4SVVacjJM +S0I1dG4rdU16cnRXRE0vTUVUa1F3NGcwRy9SSURGbmx4NmJNMXhHNmYrRERvaFo3QVpyODY5WXF3 +NVZDRFFVQ2V3NDluUFBtc0ovUWdKbHhNSnJmREM5N2tjMmM2T1YxcWRqaDRqZ054REcrTWNwQ29p +L2hYcElQRm1oalVaL25UUjJaUUszM3FuYnQ2eEZMYndYWTA0b0RFeFJyd1ptenhyTkVybktqQlBE +emk2SUhlKys2dU50eW9SVTg4czhaK3NabEtUSGY4dzZwWE1hajRhcElEQityQ0V5enc0azlmN0dm +aytyUCt0TC8rckQvcnovcXovcXcvNVRQd1VYL1duOGFqK3ZQQ0xNU21rUWFRZ2txSVNNVVRGSklG +RUZSamtDZWhJcEFsS1lDTUFDQ2pBS1BoM2lRb3dqd3crNFpvNHNwQkdBcDFDS3pnaGdCdk5od2Fo +QXVrSWhpVW1lQmtVUDhhRmV5RzNHTEs3YzJTWXZNY1NaN0h2TEJueEZUQkRlL0Jjb2I1TkVVY3VP +Qm5CUG1JRWxjeHZuWDRNM0Rnc3pweEJKUDlleGlOK01LbFpqQ3VIb2l4SXJ1Wkd6WVk0QktQcU9R +WHorY1l6UnBHdGs1YzJPVEx3ZVM1SGh5eG5uLzlUTTBPUVBYWkk4WnlPSHZVUkZCR3hHdS9YRGtF +aU00K0hIc0RoQXN4NFJBVFJyMW5EUDJMb0hHa1ZudVo5V0Q2WkFVcmo5cHB6WkJURFBHdHc0Rkxq +R1BRODMvd0M2L24zbnpSa056N2g4djBHay9XMFpHM2NXb1FidytFNlExTTlqQzBmSHRJREJaL0VG +bnJWd1p3UXUrcmxjbXRmL3FvTGhwME9NSmpyNW9ZV0cvMUFDYys2K3RpbisvcGhkcmt0eGYzNXZo +RVBHdGdyai9yVDN2RVdBNW5ULzFaZjlhZjlXZjlXWC9Xbi9YbkkvdnpvaEEvbFJLL0I0QnJBSktK +MG5NRitVbGN3WXEzeG1kRktKS1JKVXVEa0V4NG1yNXZIbWFOQzJBQ2M1ZURlTXh2ZzRkY29NVVFq +K2pYVU5OSWhQaEwyNG5uMXhXWUZDR0VSQ3oyd09wM3pqWGFIcytYdU1tTkpHc1FuV0UvMGpVQ1hq +WERrYUUrKzlTR0p3Mkd4eDQ0NUVBazQ5bExLR0xGVE5iZ3lScTVjVU9vUktWT3dyQ1BVUWdCSnpH +b1BmaTN6MXA3Q0dkNUhwSG9nYlgyd0FRL1B2WEhaeHhydHZwaEVvdHdYSEFTbGdOUVhYaHdsOHZ6 +aU14UUMwTXhJdTV3dVFZZExOWjVUcWg0ODExdS9IdXVkajBnUW5YSDdMQlp1L01UWncvbjRVdGNo +bzZwN1BWWjdmU25INHlCQTN2eDc3bDludE9DWE9iVXErNFQveHcwOUtRK3ZPc3JVK3dCTXJXbVY3 +aVZ4ek54L2VvTERua0RQN2d5MUtQbTlGVS8xSVJiZklpN3BwdDR1SUFQTnRoZGVGV2YydUNrYlpq +cFFFeDFpMWwvMXAvMVovMVpmOWFmOVdmOVdYL1duK2FleFo4WEQ3eU44WllDYU9JZ2RNM2U1Zzlv +WU14cGtDdEFCSEZYZ01zKzVDUEYvKzlRQk9hNU96RXlxSUo4ZHNtenhVNURGS1VnTWZ6cldacXRH +STFnRGhqOTlPeGZDR05nK3czL09UZGlsRmNUa09FNTRoSGdzMXA5emlBMis5UnBEbDVFWmlDV2NJ +bktISjd3SWU4ZUF0TWdmT1VORDVLWmlXZ2NPdmFHSDZaMkVJcXYrWXlFUC9qeUY3V1pVcTNoR2ph +WG1KcHVQZTd0c1VhTnlRbFRSQ2tXODJXdittQ0ppTldBWXh6Q3RmaW1EL2l3bnhqVjRLMFBBZUVU +Tm55b3g0RkFQR0piZS9wMytnQ0ROWGhSZzNXd3d5cVdmc3BqemE0YmtlSlJIRHFSQTNlTGMvTEVR +SGl4UHlJUFY5WmE1N2tEUzQxN2tJeVIxR21kV0V4SGQ1NkxBNThlTXE0WVcvL01xWWwrNFpJSFR6 +RXZ2UHBteitwazh1T2FUdFZ1empwNFkxRDl1dWRBblhxSFh6aDhGcGZ1OVlpTzlNU2VhTGIrckQv +cnovcVRodXJQK3JQK3JEOXg0WG45V1g4K3NqOHYzazRRUC9BZTJxRFptZ3FzU3dNQ1d2SE1KcUNm +Y2dWSExDSVFMd2t5Q0VRYzN6WGRaM2ZyQ1lONEVLSUlCQkdEb3N4ck1vTXlUUm9Cbys5dytYVUZu +d21Ic1JXdE1FUWlDMUhJMERqWVVodHpxeWVEdU5XaUVSb2szcjFCNWRZZ3VPMkZRZjNXSVZFT3pm +TG1RUjZIajV5RWdud0NVTGM2ZllaWm8xeWVyWkNHTTN6QUVUR3BCeVo3NVdJZ01UVVZQN0RxQmF6 +Ym8rbUZXbkRwRHRzMmV0YjVydEY3b0F5UE1JdUhmMisycklVQjUzb2xsdlY2YTd6bW1JdjR6Qk9k +TlQ3cldkYkNMQ2JNdU1DYkdyMlJnUjlYZWdOWDRtUS8zZUZBRGdlUnZuckdTQnQvc0x2VG9qMzZh +WTIxT0ljVGR5NDV4YlpmcmZiSW5mcWlZN1g3dlAwWURtakxjem10eHpVOW1ZdEJyYUZ2K2RTbHA5 +NGk2ZjhlTU1NTFBUdG8xT083OVhybmd0MTNhL1JDVFhpaUIzbHlpS2dQaC9Wbi9RbEQvVmwvMXAv +MVovMVpmOWFmOWVleitQTWl1TCs4S2hpd0lWNENDeFZoS0lMb3JFR3dRanpUT00xRlBJTVFsZUw4 +NUd3dlVXKzhpV3VQZGQ3aW1OTlF6UVRhRUI5WVFvUEpGZEY2aStMWEVwREFvTlpZYTFnVGdoQ3FP +Y2pZd205Rkl4aE96Y3hRbnpqdUJNdUVhOURmbkY5VENNbmlXV2NOL0VSUDhPckhoME5PYkRYR29E +R1RodHBQSEdLSXJ6SGVLdUVMTjk0ODRjdG5ZcEtYdU95VjMzUDc4Q01tenN6TEx4OU1oSWhyYzNv +QWF3NGQvSXBCME1SaHpnV1B0ZGJCcnlieHJOKzNNanV1S3hRMW04TXBESExhQzZ2RFc0LzBRYy93 +SFQzbzhYTEtUQ05vT09RMXYzdUh2eFgwY0NjMmZDNGNya0ZuM25lY3dTU1A3M0xDNWc2UDliaHp4 +N2Y0akpiRHpad2VxSkUrR1VOTkRrUDUxV0tQNytMRFNoY09UK3RvbDlicHlkejJmbkk3Q0hIbk9S +N1ZKSDRPQUp3c243T2VKdFRwVUlsUmNhTnVPUEdsaDJKWmg3djZzLzZzUCt0UGNldlAraE1PZWMz +djN2cHo2NjQvNjgvNjgvSDhlU0hvOTcvNXVaMUFnT1NFSWJEbW12ZlR1YVRBQTRWa0NSU3BDTVlB +SHFBdFlBQUFUblRlc0ZnSEFKTUFLV2JFaVRSQU5WQVJpR0lVRitFVEI0S1o4d2QvK2R0TGdwam00 +VFEwVXRQaFVFZE1DQk94d0FLblBmQmxhSlo4c0lsTHRNZWNMenVQYkJ3ZzBIMU5OREVKd2g1NE5k +Y2g1MDBCWWJqRU1LYytQTUtqMFhLb0JSLytGVFRmY1lJUHNSZjM3STBoNU1XcG5JUkpaRGxveEky +Wk5STUg2c09uOWR2b3FkbWwwY1JuNkp1M2QyS0lyWDY1NE1lTnVFeWhUMmRjdCs5cWNZZE5QTi8x +UVg3OWd5K3gxZVV6SE9JeGx3dFh1RXV0TUx1SVZGMmVNWldZZVBMWkpUNDhhL1RoaHdiaDhObWJG +RHlacHp0M3ZkRmJlMC9kNTErNXN3NTJmUGt1TjgwNytQQ1VRME1jZXRkemYrbGZUMkV3VDJQbWNT +KyszdUdCRnZISm1QYktZeitlMUFZN1RYaCs5UDdKTWZya3R6ZGVFTTkzMlBEaGUvMVpmOWFmOVdm +OVdYL1duL1huNm53R1RQVm4vZm5JL3R3ZlFQMlR6d1N1T09BbFJLUU5uaXRRTUJzOFp5N3JQTk1N +alRWSDRQYUlvd2xFSUJrQ0ZFWUl4T21OaUJnS0IwekJHbXdkUStVLytidW5XUXo2bzcvOS9WY3hN +d1VzbWtRRVRDaS81eEZqU1BPV1JYUEVneW5EZm12c0p5WUNoeDBXd2xTamVZU3JpNGcxMzJlMUlC +MTJab0RiSEJGNzdsSWJMQnFrMFhDclJYd0dqYUhoaHQ4NjgyTHNBVGFZOEtDcDRoRTVQdFN6QXAy +REUvYjBCUWJyOVVOTSszMFdTd3hjaWExT01mWVFuVDB3V204dlljT2tyZ3hpc2NaZWQvTnlFalZ1 +OUkzNTdCRlBQZ2FWMjNyQ3ZELzg1WWZmUVFBL3puMjNWaTY4NDU5V3hIZlppeHREbnExbmRDbFBl +Z0dIV0w0ekJ6NWg4SXdwOU1jejhSbHBUVHhHdGRjNkdsRWJYSExpeno4ampsdllyTUdSUE9weE9O +QWJUVm5yc01PUnZmRGhHQmExd1k4amNSamJkM2xqV2ptczFRYzRZWVNsL3F3L1liVGUzdnF6L3F3 +LzY4LzZzLzZzUCt0UEdEeDdWSDllaUpib0xaRFVZZ1kxYVFNRCttbFlNT1FwaXVnRkVOeGF6NUFR +TVNCV0luT0sxZ1I3ZkFiU2Y1NVhsQmdJRTU5NGtLMG9lSUIwT0d5elJvaWFHcXp1TGlLQkN6bjJN +WW8zTWZacXNCclVCWmM4OXNDWFlaNmdFQ3VHL2NpTEVKREpqSm9JaTl3TUlwYWE4YVVaM21UWnEr +bUVnQXR6NnBWZlRyOWlRVFNHZVg4SGdGamw5ZG52bHNkRWNPRkxidmVJV0ZQMWdFamM4YVpXY2ZS +S0hVd1RJVGtnZkpjRHQ3Z2lJSDBSMTRFVHJ1MnhUaXlZMVpVaGwyZHFWN2U0UkdlOVh1dXB5MmVt +cDVrWUI0ZXc2MkVNNnFDQ3czZjlOUjlCZXJhNVp6MEQ0Tk16c2VFM3JEOEdQWCt2UW8zeTY1UDFN +S3BSTG5PTWJML1Bjc0F1bnpyVUpwODllRjR6elFIQWhMNzdsUXQ5aE1XZHJ1VjNXTUtGUTF5ck0z +V0xxWFlheHh1czFucU9NL0VkQXZEWVIwOHVhL1ZGUDJncG1xMC82OC82cy82c1ArdlArclArckQv +cnoyZng1LzRkVUlRUkExSFpiSUdnTGdFUW8zaEJnU1E0elFKU2d5VkNPRkFhTFlhWVJJMGtNWHlX +RUZIRXlDeGlJR2tiT0FOcFlpRFhjNElHVkNNODgxWUZZVVFBTjBGNjI3SUZqbG50OGZiSmM4Tnpk +U0VLQnYvNUhFR0dmWm9obG5yZzlGa2oxR1dJeDhBd3dLOG1lWHlQYVRSU2t4d1E5bW8ydnBoYzAz +RURqNE5ETEFZVTM5OFRFRk1PYnlGd0FpOWhlclo3aDB0Tmx5K0MwQWNjdWN0aExTd3VlZlRPdkFP +Tm1IeldSempoSXFDWVB6WEpZVDBPNU5JWFFvY0Y5L3FKTjRkWHVIWW5UTThjMUxRQjN4cDA0bi9X +b0E0VGE0a3dXcUFaUFZlSDUvREhsSGxqQ0RQTitPNHltSkhSR0E4UE9EQm5IZjV4c0lmUTFIVU94 +ZXZteHFjOGFuR3BWeTd4YUNUR0ZjTjY5ZEVaWS9tT1I4K3NsMC9kZUl2eHpZa1hIY3R0ajk3WXd6 +dXc0VE1EWi9oU1F6amR3M0srdzFoLzFwLzFaLzJabnRTZjlXZTBVSC9Xbi9Wbi9mbkkvcndRUEdJ +VUpMaUNrYnVGM3dvQlJvSTBSK0hXSU4xbnpWYUVnZ25WYytCY0FBRWNBU1BkMnhLRkFROElrSWJQ +d0RLTVBQWnJ2QWJLNC8rY0YxNE5Kbmp6aGpnSWdaWGdGR2dRR2xPSWh4eGtJeURETTdqVTdoSjNC +WE1UQWd5ZWFaeGNZbWtrdnVTS1FlWDB6RG84RUpYRHpoNzFFS25jYXZiY0hyK3k0TTBTM016cG9D +TWUzREhKQ21uNGdGZDluaE9PSE1TQWZ6akZWaWNPWWNPM2ZxUXZqTTRJOGhJd2NlQkhMZUxuMElJ +TkZySGxXOTV2UE5nWE1kdXJyc1F3SnpZTSt1dmdsVnNNdk9QRjRVTE04SHNtbHhyWG9MTVBYNmxW +Zmh5dUFTWXVZK0RMWVdHZElkNnVHVjNRcVpvOXN5NkhnTjZLQ2FjNHNIa21IMDZzY2RkdisrREVx +WGt4MUtVbjN2elppMmZ4NU9JRkIyM1dxQmVIY3VtRE9OYXJSeDUxMG9yOHZ1c0JQbkpvUlZONkM1 +T0QxUjFIOVdmOXFaYjZzLzZzUCt2UCtyUCt4Sk9CNi9xei9oVG5VZjE1WVJZLzRTSU5FRWtsQXdK +Wm1tWUlpQXpFYTR4Z0NOY3N6WXRvQ1Y3QmdBQ01TSUNKMkg0RkVLdDlBSWlqR1Fyd2s3UjljaUxD +SGprVW9ySEl5SnNXUmNGaUlGbEI2akFucjRFWXhoT1AwWWpSbWd6NVBKZkxPbTgzWVBMbXdXQlE4 +V0pVODBUdkdXeHdxY2RiS2Myd0Y0ZkVvaDdtdEE4dmVMYUh5RFRJSGdlT2VXYkZ2NmFheHhQTUxq +SGtGOU0rUGRGb2EvQ0dBL3pxRnd4NGtVYzk5bG1yUC9ickxZenF6Y0drRjU0UldRVGtMdVo1UTNU +ZVl1eEJNRHlyMzJjeDVVbnY5RW12Q1JySGNzTU9rLzNXdWRzbmx3TWdCbVUwdFpxejE1eVk4RVl6 +ZW1LZHV6bDdtWVRBR1ljSnpmbU9FM2xoY01rTm4vaGk0NUJlWEo3akRpZHlxZHN6aDVGKzV6Q2hG +WDJrOC90YzZzV0QvZWJ0OWR3ZUdPaFczOEtoUCtUNFEwNzV4TTFCQ3FzOTlLVW0zcWcvNjAvUDZz +LzZNMXpYbi9Wbi9WbC8xcC8xcCt1Ui9Ya2hGTUlIeEFha1NHWWhzaXhFVEF6a0dlS3RBMGFqR1dM +WHoxMGN4U0hjMndJRmk3ZnhCemdCeTZrb3hTSU5lUEhOS1VCUmhPWjM2UldpRVo2Skl5Wnp5NnU1 +Qm1Fb1RwR3dpZnY2bjZVSEwyRjU3azBNa2pMVUs2NW44SHc2ZjkxTHZYSnBxRjlEVUFNQjJ5TS9B +WXZCK0o3QkFCT1RhUUlEdU9RWEh3WmNxdE56QnBYenA5LzU0c2JYVkdKeXh5WHM0bW9VZm9qTGQz +WGkyRE01aVZVLzlFQ3pZWlNQVU9WMkNJUmo5ZWxOK2tVTWNER052R0twVzkvVWdRZXh4WkVmSC9D +b1Uxd3hvd1Y3dzVkWWFvUVhUMkxCck44d2lnRW5qRGlCeFdmN0NWcE1GNjdFeG90MU1UcWQrSTUv +eHZCNSt6RVlZVk5UdURBSFAvM1FpZ01JaDdDSjc2Q05lVjNKRHdzRDRndlA5b2xCajRaMWVNYURQ +RERSSEh6eTBDRmVZMFk1Y2VPelBEUUNsL2g2YmM1Nk1UMlRyLzZzUCtHcVArdlArclArckQvcnov +cXovbFRUTS9qejRndmhNWVMzUUlvREJtQ1h4TWdGQnVtQ1NPUTdNQXk4cHIwMVdHQk5BNDV3QWJG +ZWZFUUR6bVQrTXl5Q2ppRmVOZ2V3Y21xV292M2xaVVFoR0E2bThJWklROXl0TllJTGVYTEJZOGhI +Sk5iRHd4Q0szcUdaZzU5QTFlK2V2emg5eHZrL0FDWmtHRFhLUWFiZW1FUmoxYWRPOGNWMmVlNHRB +azZJeEg3emNoQVlYSjc3M1hpL2N1RU5rVGMxNm9mTGV2RzNsdUVDWjVyc0xnWk1tc3hFOGpHVWZz +Q0ZmM0h3YmI4MStEQXZ0NXJFbEE4M2VIWmd3c28wNnBISFhSM0VTSEFyckhrbVIvakdHNEhUalQ2 +SjdhMksrUERKcTA1dnAzSmcrZzViQkMrV2VWaDlOaStmWjJvV2N3MDZHdGdEWS9ESUZaM0pJdzZ4 +aSs4UVlCVHplQkpUM1hSTkIvakFsejdMNWJQWTZyZEdQSEgxVDgveTZ3TDY3azVqY3FuSE92NmhT +NGVMbW1DMU53Wmxjdlc0NUFpUDY1L0o1YnUxOE1LTk16RnB3Rno5V1gvV24vVm4vVmwvMXAvMXAx +ejFaLzM1TFA3Yy93SnEwaGRCRUdLRFpCWXlob0VzQVh3SEZrbUswd3pBZ1ZFZ1VqUldET0FraXNB +Snh4NmZDVTJ4WWlGUWt4RW9YZ3psRFJFeUZDT1d4djc0MjEvWW4vNEpHMllEMXVDSUtBeEUyUWV6 +dTcwRWVjWjU4eUVtZ3BISGFKcWRlZUtUeXpNOHdhTSsrT0dMaUx6bElYWml3SkhudUNRQ2V6VTVS +bUI4RFlKVERaNjVIRGpxTUMrTytOYklqek41OE9lUWdKVVlpVUlPUE5pTFQrdnQ5VmxkZXV1ejlU +Q2xaM0l5azVyZGNZY25zYXl4M21IQkVFUzlCL2ROUVBESkt3NWNlcmVIMVFnT24vaTNQNGYzRWZS +NUk2ZnYzb2JCSzQrNFJDMDNuZGlEZDdtMzV0dTg3MnZRNll0OGFuUVgyejU3NVBSTXpkRzA1K21U +SHFsVFhubjBCaWE4ZUw0R25kN0UrQTRqaHhBZXphdkxYUTVZclBPSGpaclVJeWF0NGdKWE9RREZw +SEdZOVJCbWU4WHd6RnBZZVU4dE9WajBWNS9xei9vVGQvVm4vV2xQL1ZsL3dpdFAvVmwvMXAvMXAx +aVA2TThMUWdtSVdBbmFUOWpNWlZGQWE1TG5nZ3RFN05aTHJGSFdNUlV5TkVRY1NTVzNYcUhpN1Uv +dlU0aTNFMFMrQnAxOUs0UzVGR2dkQVRvY0ZLOHdCY3Noci85UHAyRFJWR1M1N05FVU9SR3pZMkxD +QTR2TEd5QmlOTVNOaVZ4d0lOTm54SWxKbUFUdnpRa2hNeEY4NHN0RE9MRGgwUE1JekJzSGN6Q3BY +YzNCUlN6aWFnQ0RFcXZmbjA5dVF6NjRHUUJIdmhNWVlSR01YQVFFYzdpWDJ6cWlzODkzdU9TMVJx +OElHR2YyQnpNTVB0dmpNcStQUklRakZ3M2s0SlpyQlQ1M2VGZDhVeSsrckZFdjdENWJnM2Njd1k4 +VE9mVkFIbGdqV0RIbHB4ZmNPS2lJM1dlNDVjQWRQUFRGTEQ2TEt3NU51R0NQM3JZZmd3RTJ1ZWtI +bjlHbWZHb1NUMzU1ZkdjZWNmUmVYOVFUVTZ1WmJ2WkFtdHFaVnozbTFTU1BPSExZSjQ5Kzg1Qm44 +S2dyZklpcFp2VXdwUHh3K1p3ZTFwLzFwejJ1K3JQK3JEL3J6L3F6L3F3LzYwOFlIdG1mRjRBMWlY +Z1VMSmhFZnVvTmlaNExhalBpRlovdnhFY3Mxa21rQUFVVHAzK0syWE5GT0FRUUNDQnpLaHdBRFJD +ZjRlMEpjZlpZdzJTS1VZanZUR1l0MGNHaGNHUmFyeGFrN1BNWmpCYVNZZk9HeHh1V002NUxOUHp5 +YVo2YTRjczhvc1dGa1RIc1hRSE9lbmtpWXZsZ2wvL1Y0TU9CdFRpRWZkL3NEQmIxUkl4aWV1WWdj +bWpCYUdpT3cwYlQ3RmNmTExna0J2bjBDUWZpeUdtZGZUN2prT253TEliOHNPSk5EblBlYXUzYm1z +bXRSakZwUUJ4OHU5UkFpT3BuMGoyd3BrNHg1RklmbkJHY3ZmRGhtemFzVXl2Qmk4VmdPSkpYTGZa +YXA0Zlc0VUlNcGpYMlFCOVIwNXdZZUlWSExlcXlUMjZ4OUJJKzlhc24zTGdiOXRPYmZvc1o3UExD +TG9iOUxtdmx0amVjd0prOWVJU0ZRZFhya0xWR2JOalVHVjNad3gvV3l3R0QySFRBb0hLa2x3NVlu +M09JNEtIK3JEL1ZLR2I5V1gvV24vVm4vVmwvR3ZWbi9lbHVQS28vTHdnbk5rQUEwd3lCa0tWUWpa +Q1FRR3lTVEZKSkFOUUVwR3p4TXkrUk9YRTB3cjRJSWo4Uk00T2NBSW1CZEFRenNiem00TnBENCtY +OEJYRTVrSmwvYll2Z0ZXSm9vTFZ5Mjd2Tk02YUJDQ1VZeHBQMy9KckFHY2dVUXozbXhEZ0dQZjlL +bURwZ2w5YzlqY2RQRElwNDRvWkpERGpYb1A5Mi9qTTZIdVJ3Q0dxWVliODkxak1Jby9wOWVjOWhW +bzhhck1jckRsMHI3c2xqTGsyRkE2ZnVSTEg4elQ3ZjFRVURjK0ZYTEhXNDRNR0x6M2kzbnFIaGtp +dW1ZeXo3MUJFVG1mZlpmdldLVGJUeUU2eDY0TU9sQ3hlR3V6MjBBTE85c09teHZmajFqTVlNL0hz +dUY2NGRtam5NbDhzUnViWFcwQ2k4TkFpRE93M0JicWhIYmVMVHRNK0pTNWU0WlFoRzlFd08yTVh3 +VEU5d1pFOTRjWERLUlRmeXFkbSsxSU43Y2EyRm0xZlVhNDBoRG4ycTA5MmN6OHlPYnpucnovcXov +cXcvNjgvNnMvNnNQKzJ0UCt2UFovSG5SWEJGV0tCWVlBVFNmTThVUkxBV0E4RlVnQXFtVWRZQkxh +aUNFS2RBeFNKY0RQOTVYOU1BSWtJNXhUVW4zeHAwQ0dSb29vcklpSWpZRlN5SDNFUmtIWU1lYk9k +ZkNiTWVXUkhiam1rZ1RMQ1lsOWYrak9BbkdrVG1zSXBCOFNHbTNQWnFxanJsOWQxK3d2QmNmY1J1 +UHZXSUs3NUdlVnVHRDRNNDRWQ24vL1BoNEZZZkVSSlFPSUJyM3hETlFTYXZtSjZyU1QrQ1JaM1cr +QXdEVE51bldhZlphaUdnSERTNHhhSGM0dG4vOFJ3WTFsdm5Zank1eFNNNHVOUkRSTERtd0NKd29x +TWpCd2hNZXUwZzhWbi85TUtGWTcvcW9jOWkwbFgrWUlDRlBzUzNGb2Urd3lBSGtUTzU5VGpkZzNE +RVQwYytHMktLRFJjdXJET0NWeHgxMEpEY3RBUDc2bm40aXpId0JhdDY5QitYNHVJRlJ6RFk0ODJl +V1BLSkJUdWMrUFFjZm5xSWh1MjNOM1hFMkxEQnRYK0l6Vjc3b3J2NnMvNjB2LzZzUCt2UCtyUCty +RDkzMUorN3J2NThYSDllTkJ3eEdvY0lTUlRwc29tWWtDV0lCdGhFTkFKTFloMkNQTk1Rd3RCWXhQ +aVZBTTBsWG9WNCsyRzk3L1lRb2NLUUhMTVFpelgyMmE4NDVDUE93ZUZOQ3NNVHRPYkRobnc0UFZQ +UFlwNEJIeHlJZDVkUDdsY0RUazc1Q0ZNek5GU05PMjV2YXNUVUVJMFNHd1pjZWRPRUU4U0xZVjd0 +eTh2VTQ1SlBUTTMxSm9naGxvTlpJeTVPL0JQVkdvazczTU8yQjg3czhReXZjTzZ6eVdNZm9SQ0hL +d2NlZnZSUGMvR25wNTRuQnA3TTR4Z0g4TU9zYmhkK1hQTG9INkhnelhmQ2NlV3pPVFhnV1d3SHNy +V3d5Q3RuT0lkSEgvVFJZZWFaR3VBd2g5czF5Tnh6d0RGSERDcUhQc2dKZjdTQVc0Y1pidWdVQm5H +aUliRWNRdklaNHNNT2kvMXFsNHVlbVFvV3NXSmF6L0NjdnVpSCtqelhkLzBWaDBibHlqb3hHQlEv +K2lvbnpPclhMMnR5Z01OTXU5YSthdWZtS2Juc3JUL3J6L3F6L3F3LzY4LzZzLzZzUCt2UFovTG5S +UUdLQXNha0FDWWw5N3UvK3hPK2hMTkprUkVFSVJNbm85bHJuWURtRVd5dlM3UC9IM3YzdDZ2Ymxs +V0gvWHZsM0NTSzRrUVlBMUloRE5na2tpUEZKRFpHVUFWVUlZcC9weERZNUlvSHFPdThRS1FvVjVH +OHQ5QkovN1c1MnE1bFB3RnJxUTFwMXZ5K09jYm92ZlhXV3h0YmE1NjFkd0VhY2Q1QWlqY0UxaVBG +ZHdPNTlpSlhiRG44cDJ6RmFaYjhzSW1ERkVJZ0R0aGl0Q05RMCtDcFFjMnBEeTc1WEJxRElLUDd4 +TEdPVUsxOURQejhLMkhFakZnaTBBZ2MvUHdNelZ6dzRBQzVjTWtQT3lFeW9ubzBUa3djeVcwOWJ0 +V3VCaGNoT1hnOFY2c2M5c0ZtSFhHYTg4eWJKYzl4REw4Y0RnNUNjM2lvSmVLNFhLbnBNTUZ1cUFQ +ZjZwRFhYQXo2dGtaOFBCT09OenlFS0MvODdiK2F6Y3ZsT3l4NkI2UGNqSThmd2hNM3ZUZ3U5Skcr +ek9IVVdudkZJWFp4OVZ5OVRLSjNmYU1JQXgycEVYL1d3S3BIc09pcnV6blA1WkFicno2SDA2dGRM +cno1VEpONGtxTjFNQjhjdnV1OVovVG9zSkhUWmM0NlBjWUpqZU5aZitHR0UzYWZZWkdyaDVNNjdE +ZnY3UkRNbmtYM2wwdHZjR0VOclBETm4vTm4xNGcvZjg2ZjgrZjhPWC9PbitibXovbnpJL3N6Zndl +MGJ4MXNRb0lOQUdxYTRnVzBXS01FQVJvbzYvdmR2T0lZeEUrL0NDQW1oSWdmb2s3NDRqT2UvZmJL +cGNIMndDQ09OWEtMb1RpRklFZng5djJuUC92WC84WGhnVEI3Q0U4ZXhjclZBd1RCRGlIbTBUaUNN +Y1JUUC95RUpEWmhpV21vUng1Y1dNY0lqVUdJV1hzNWZQY0d5THpjbnNGZGJ0elZCcHZuYW9RTEx6 +aGlkcHhvSnR5NE1DK1h2ZkRMNVptOGNLclpoUU54MnpjNDFjS0ExdVBOTTBQVFBhdlIzV0h3akdF +Y05QWXltOTY2eSsrWlBPME5FZEVDQTZzTGp3eW9WcVlRejV3ZVByMDRLemtVai9mMkZhZndpTSs0 +MXVOUExyeVhRL2xjOE5BU2JoakRQdk8rUnh0M1oxZ2NxRVBORG5LNTlCTi9NZWdkYXVMSUpZN2Nh +cEdYV2F0eEdOU01vMjhHZmNPR2Q3bk4wNGYrTzF6RWc0TVo1Vks3dTdqdzR3V0g1bnMxSHg3Rmcw +MjlubHVyanZsei9wdy81OC81Yy82Y1ArZlArWFArVlBObjhPZExvN3hOMENCR0VkQkNpendMd2Zj +VHRwOWt1OUVhY3pXMDc0QVRHTEFLRXRldkV2aEpHVWtLSlQ0RTlRMFB3QW9YUnhIV0FDdTJPSERs +emNJSmdsZ1ZyNUQrUlcyNU5WME9EUkNUNEFoWUxrTTgyRFdOU1dGQmpJRU1ld2hjWXhtY1VESXVy +bG8xd0RONHhCWURQcVpLL3N1VHVJZEgwNjBMNW11ODNQZ2pGdk93VzQ4ck1kU0VJMXpaaXpjam9q +c3gyRzhOWGdtNmVOUlNvWW5adzZjaTh0bGJHSjl4L1BEeC9HNit6emlvc056VnJvZDRDTTluT0lk +aVJlV0N0eUxYRDV6Q0pDWWpFNjA1ZGNnQnExaUpkL1hnaEk3dzRHMmF0eURtaUZDOEdsUU1QT2dS +RTNpdXY3N1hRT3BrQmx6aVdTeDd6T0ViMXZCKzllSEgzdWF5MXQxaG9oN2NxcC9teFRkbnY3eXc0 +Tm9hSE9pSE5YSzVpMDBUY2xrWDdQcHlsL2ppaUl0SEdPZ2JSdlhpMThGQlEycXA5dVcxeHVYNy9E +bC96cC96NS93NWY4NmY4K2Y4T1gvQytsbjgrVktFNEJiYUNIU2JZb0VrZnZMMjJScUZTaXFBeEVC +NVpwM1B3SXBESUlTbk9DS29NVFRUMjVRVS9rYVd0d0VBRTVybWl1R3pmUnJnOG93b0ZPNVhITHhw +aUVHdTZZb1NoeEU4ZjR6d0RBSWlDbmNZbVRCaXVRR0wrdFdqU2Y0SmJEa001S21SYU9SQm9MYzR4 +QUZqYSt0YktQT2FCUVBoRUtWWWNObWo1dUE2NCtPSkdieFYrT3MvL0xVOEY1Tng1YlczRGJiZmVu +UGlPVkRVNGpBbEZIRndvejg0VlFmdVlDSHM4bVo0THFaRFFXNTd4Wk1iQmoxU0svRXhITjdWSnIr +NCtCS0xnZDNsMTBONDRjR2pub21GdTJyRlBwemJaOTdoQm1zMUlCOHU5YzUzNjJuSTVSREk5OE9D +YXpIVUx5NHU2Y1lhejJFbmRySHcwYmRSNXRUanViVzROQyt1ejNxbnoycVNYeHlha2tjdHZ1TWh0 +Vit0NGpnOHpPbXJudnVNcCtiSVlYbVgrTmJDaXU4ZUxQcXNudktrMy9iRGhHczVYZlBuL0RsL3pw +L3o1L3c1Zjg2ZjgrZjhLZFpuOGVkTEFUV1hJalRWZDhFdFNIUFBCSUlBVEREbVhjaXhuc2g5UjVw +bUE2VHgvVjE0QlNBQnlacnFWd3dZUlRFQUE4cnNTTE1lbHBLbXNZenRyUklTWVBDZjZXczY4MGdR +aXhncnVJekRiWTljNXVEdzJYTURrWnBvVGd4eDNaRW5KMno0MFRTRzlHYkRNOWhxS0c4REVBK3pP +U1lnT09MU1ZOOFJMcmQxR29WRDRoU0RXTlVCMTlPOHI5bVBoelpLSEhPK001NURGSmZ3dzRkL2Ra +clROenphSTQ0Y01Cbys2eE9PcklmQi9oNXFSRkxEMktNbnlYMTQ5YitDTmdlWDNydXNnMGVOZnRk +ZnpURENLeWFkcFBkdmIrVFNqM3R1dmI2SlMyZnFFRmRkK01laGVRYWpHK3YwQmhheDZjYkJZSDJO +VTg1Z2xrT045bnVPVTJ0ZG5xdkZuSDdxcnpYcWFXMzJlNllXTmNIUVEwbk4rdWtQSXZyQnJaem03 +T25oeElEcU1NUVFHeGQwSzRaKzBRNDg5dUZJLzZ4enpaL3o1L3c1Zjg2ZjgrZjhPWC9Pbi9PbkdK +L0ZuNitJL2hZcHJnWkZuRVphMkFZSW9pQS9PUXRtajBiNmpsZ2k4eGtvUkNxWStNUXhwd2pBN1BI +Y213SXhYVVJyenJxUWUySmtiamphQlArY3MrOUlZQlFHZlFUOUpRUWlzK0x6OXFORDBmQ3FSU05p +a2pPQkMxN1AxT0tDbWZqa1pHS2NFTDNtdTFzTFh3VmVQb2hjSSt5VngxNzE5RkN3RDBmRmhTTllO +Y3pCWTUxNnhEWnFVUFBpYWI2N25IZ2pBcUtYM3lHbUptdmtnUU1tQW9HZitZalFnT1U5SG5mWTVi +ZVcrSWhYdjlVTWg1elZnalZaZHlMQ09ieU01VUMyRmkvNnBLYzBVT0hKYncxKzlBNFA2b1BCZm12 +aFZnY04rS3dIVENxSGVVTWN2TUxUTlhsK1dPa2lPbnN6bGJ3KzB5K0RWRnU0d1ZzTWQzV28xVnI4 +cTBsOU9GSXZYdUNzV2VXRXdSb3gxWUUvZUdCWEN3L0piNys5T01DTmcwVk9NZWpaaFE5eDRjR3p0 +WjdSdFhyYTgvbHovclIyL3B3Lzdaay81MC8xelovejUvdzVmMzVrZitiL2hpVUdPNEVLYnJGaUpI +VVJnNTltYmZMWk9tSlZLSkFWdWVCRTRhNW93dk1tU0pIQXV5TVVtWXpRd3V3WDN5V3ZHTUQ5elov +OFpraXpSM0Z0dkV0K1lyRE9ITXhJUllDZjJIMW1YRU5OdmlNTERvTE4zSm1JeUJHREpIVWhuRURW +WlI0Mi9DRFRQcC94NExONkNGcnppQTR1OGVCbi9vcENiam5zZVhKL0gwRjRTNEozYjFUY3ZaMnlY +ajBhcWptYUtaKzNOTVNGWndiMVdYeUNrTSs4dS9qbDFCcENrb3ZRRFRudHh4M01lR1JzZTVwUGZ4 +aEN6ZmlGWDN6OHlVZkU2b1FGVjU3SE9JY0gvei83bzk5SUxIaHdKTDlZNXExM09NT0JiN254VjRQ +Q0wxZHo5QThHUGZTOXBvWExjNXJwUVlzbi9jV2hTeHpmNFdNWWVPVHduWTRaUWR6MmlTWTloMG5z +SHJacWdrKzlZcW8xcHIrRGdsNlM4MnFwTm1vd2VhejNXUjN3V3crTG5PcndyUEZ4WmEzWTdaMDY1 +OC81Yy82Y1ArZlArWFArbkQvbnovblRKWTd2OEgxa2YrWlhjQWtFa1FCYkhHSGRNNXVRNnFkN2kx +MFNBQ01JNGdHeVZ6RU1CUmh4KzMxeXY0c3RvY1JNQUpqNGpJc1FqZkJkQWViRUZvZWcvVVZzeFJ2 +bS9mNjYyTWp3V1h5RU14dnlZVklMQWNvRmc0RUlSQkdoK2Naa0JOaGdiMU9KeHpQNXhQVUdBYW0r +VzFjamlvOVlSbEkvb2NQbE00eUVaSTk2TkZhZDdqazREaGRlN2NHWE4ySDJPRmlzSnpoODIxT2pW +YlQyd2FCZXZPbUh2YkE1cE15YnN4WStkL000Y09HSU9lWHRRV1lkdmduVFhnSmtVdUxSQzJZeWp4 +KzF1L2RBcEpjS01CcTV6dzZjY0hHSGwvM3FyQ0RGMWg5NDVSSkRqZUtwVVM1NzlGWCsvR0Z4c2V6 +VGkrQzQ3L1l5VTN0YzQra0xET1p3WXowY2pHOGZmcXpCaXp6aXdtNHZQZEdJT1h0YW8zVSswdzJN +RGlsOXBuM3o5c3JYdFRIV1lTaXZuakV6cnZBUGgrL3lpbUdkZlBhSHQ0dU5FemlxMmZsei9wdy81 +OC81Yy82Y1ArZlArWFArL0N6K2ZCR2dJSW9BaENFVlRDdzJFNGlmeEFGcmd3amRjeVJhTDdnNWpV +Y0FnZnJNaE9KWUsyWkJhWko4Z0JHYStQYkloMFJpSVZ4NS9QcUM1djd0ajMvcktmRHlFd0ZDTmNH +OFF1eFJDd0dMNDdsaEx4S1JaYzdCWThnSk0xSEFMTFpEQVRHd01MQ2F4VlNuL2NVc0w1R3BoM0E4 +VjZmUFNMYWZ5QW1wSEtvRlJzTWFSdkVXVFYzNGYyOVFITW10SDIyMno3anhKb2xROEZZc2F0SUhj +ZFRVL2hFTGJ1Q1BvRzVkUlM2V3UrOTY1UzRYOFZoTFVMaTJSd3d4Y1NPdXU1N0RBR2YzNE1vL1V3 +MHJvYW03UE9IVE9uWEtEYWM1ZVFoY1RVd2dqMzMyeUNVM0kzWEl3L1RseW5DbzRsWi9hMUJ4M0l0 +TFRHdndaVTd0Y3V1VDUzS0g3NXUzdHJ6aVJSL04wNGs1QnNJM3pjU2d4N01EdUFlSHkxcjcxZVh3 +Z2tNc1BmTFo0VTZidnVNS0YrWnhxeDdQNEp3LzU4LzVjLzRVYy82Y1ArZlArWFArbkQvRitReitm +Q21Ld0lqQVJzRUJSZ0NRUUJPelRUWUFCQmlBMXJjSjVvaTlEUkdUbUFoUk0rMnJRUWxDRHJrYTMx +c0VKQ2hLb2Q3V2FLYm5CT1pYRnJ4WlVqQnp3cTA0ZTJFZ0pxUTRGTVRwMndNaWxndXViM00zWUpI +SGMrUmF3MFFFVnp4cXNsNStkMis4NUtyaDFZSWpBaUE0Nit3aFJPTEZZVVhtVUpMSDhJeklYR29o +RnJXcXg5QTRjV0N4bHFEbFZiczk3dmpYUkJ5S3E3R2UyK09nVUpzTEwzQ3FWNDA0Z2xVdU9jVFdl +L1hBQVR2eGlPdVp1dDJKVU53ZUhITGkxbmQ0WFE0Y1BZY3R1UytlMnVWMllNS1JRL0wwWUE0VzVp +UDQ5Z0l1V252L3h0QWE4ZlVGcnpTbFZzOE04MktLRFRkZTZNOGFzZFhyOGt3ZGVOQTNuT21admVv +UkR4ZE1yaTU3MUk0bjJIQ2xkam5zOXkrRWlRVWpUMWdQdTNucnhWVS8zUExvaFZqd3FzTXpXc0tq +dTl3NGhybUh4Znc1Zjg2ZjgrZjhPWC9DTVgvT24rTE1uL09uTlIvZG4va3ZvSUFEcERrTnJqZ0pr +ZTJOaDRBRkRWUVR0TW1LSWpZTlJTNHhNeG1RQ0NWQVFJSFRSRGtRSkQ1aU5KRTVGYXA1M3RZd2N2 +YmNUL0gyK1Axcjg4d3FucWJDcGloRUlobHAzZWNOZ2h6aXd1d0FRWkxoallVOGF1K0JVUjdzTlkr +b0hoS0k5dFpLVHB4WkQzZmlYNDM0U3VPT0IvdUozQnJyWGRiWTUrQWdQUFhEKzkwUGY1Q2FZQ01l +bVBWQmczQ3RhUzR4Q01GbnVNMjVQQk9ITU9TUW4wRFZhZDd6OXNwZW1OVGlNcS9tSGpnTTQyMkcy +dFVsTHB4NmloZENjbEQ0YkE1ZTY5UXJ2aDQ1aU14YnAwNzUxSFBIVG5BNUpQRXR2enBnODl3ZXVl +RFFiL0hVVEYrTXJRZjZUTFRxTkJmajMzQlhBNXc0aGxQUDVSZmZXclU3Z09uTldyajF5MTdHMW1k +dmpQUVJaM2pFSVp6cDM4V1RXODE1cTNWM2U4U0NNZWEvZU9aOHIwYnN4UTlNWXNuYkE5UGErcUFI +Rm56eTY3Kzk4K2Y4T1gvT24vUG4vRGwvenAvejUvejVtZno1UXFqa0hpREdBdUFVaEN4Sk5ZemdK +QVlNS09KVnZIMklFTXdiQWdtUjZmZllOVXdzelNSQ2J3bVF6THpBdE9BMldGRXVPVFVSTGdhRXlY +TUdkUWo4NWUvL3lqY2hFSHlicTNHYUlCL004b21GS0dTcmg2QU4rY1RYVkFlTVE4U3ZQb2pGZVBJ +aVRoUFVicCsxeUJQSGM3V29YMjJwNVhLNW1NeCtOV3E0eHNDbmlXSmJBNlBEUkQxeU84eXk5K3JS +dkFwY0grekhmVVJ6allRRjN5NXJ4S3FZN0JPbjgvREt5UVQ2SnA0TEhudlY0Yk9ZOXN0dnFBc3Zu +bG12M3pISVlXaFBjU1dYV2hrYjkzcHJ2VFhmWXA0NURmclNWM3lad3hsY0RtMzU5RWtQOGUyNU9B +NFJuK1hXTXpuRnhJTjFCbXdPT0R6YXp3RFZLTzNvbDMzNlIyOXlWL1BxN1FFcFpnOFAzTFZXbDE3 +Qks1NTkrcUZXK2NURUJ5N1V5QmZpaVF0L0QzdDU3VE9QTDBOT1BhTVRtQmxaclQwazVzLzVjLzZj +UCtmUCtYUCtuRC9uei9uek0vbno1WGVhTlVrZ3dyRFpwb3BMWUVVZ1IzQ2kxVkNiL1lRcm9HSWta +eUprSUk3Z1hHSUJMMTUvVWxhWVpoQ0lDMWltVnF5MTRwblhjTVhCNURrRHdlc04wVGRCbndubE5n +K1hQVEFRaGJqaUU1cm40b3R0eEtEMzNaeG55UHlyUC9qVk5JQ2dYUnFpWWVwRW5yWHFkNWVMRUdE +d2F3TUVnRmp6M29pNGNHU2ZodmRnSXpia2k4dWNPSU5EUFo0VE8vNDBEWGR0bkR6aWVkWkdleVlm +YytKWEhtdk5pd1VUTVltcGp6anRPbm10YzJqZ1NEeDNhM0hhUGxzdlBnNzBBUVphd1hFUEpmcXdU +eXh2V3hqTG5oNU9lRExvUzkzV3lTZVdtSHBmZ3pJUnc3WS9NZWliT2QxYm0velI1ZzNyNVpLN1hO +bXJmbXR3SmhlZDY0TTY0ZEFMdGNKamo0dXh4S0tKSHBKeWV1NnpQYmp4dWJYaVFXelA0b25EU1cv +Mm1HZGFXTXpURngvNHJtNDhxWkYrWVlZQkoyTEFNSC9PbnppYVArZlArWFArbkQvbnovbHovclQz +TS9qejVTZmxKbEtvNGlWa0pCdGR4QzZ4b01ENmFWa3k2eEVna084S2xhZ0daU2hOOXFiSW5XSE1W +WmppeWNVTVlpRlhISUpuR1B2a2hvVXBtTk16bDkrWGY3QjlEWkhpeVcrTk53VkVneGpQaU13YkpU +V3F6MkFnTWMwUmhsOC8rTFAvNDM5S25ZUUVFN0kwVWh5Q3N0WXp0V2lJdFlRRnExcmdkQ2RJSkZz +bko1d2FneWZQTlZnejFDa3Zyc1NUeDRFa1AxN3N3NG5MZHhqRVY2ODVvb3V3M3N3UHExck1XNjlY +aEUyRTFzQUpqOC82UkxpRWxQZ1hDejc1clZjWHN4QVpubnhYUS9QaXdtZXhESFZacnpjNGwwY09k +MXd5c0xwb1IwNHhpRlBOZEFHdkhzS0lSLzFUaS9qNDFDOHg4R2F2Mm9pWVBnelAvUHFEM0Q3VGta +anFVU2ZlNUlYRm5KcG9UbHpQeFpQTFh2bVkxRDRIakRrKzhkMGNEZUhLUG5GNk9PaXIrSGlDRTYv +MnFOR2M3L3dpQmg5WTY1bFljc2hOVTliTEFlZjhPWDhtL3NXYVArZlArWFArbkQvbnovbHovaFR6 +by92ekpUaFMzSkVzdUdZZ1g3RnAyaEdEREFKUkJGRXBYb0VDYXFMbUFPd2lDbXY4U2dHZ25tbVcr +SDQ2dHA1NHhkWWs4YzFabXpjUEIxUnpORXdSaWtJdzh6RmFmbi8rWWlMUE1BZXJPZ2pkbkFFSHdT +REJjL0hFTXNTVkE4RXdNSXRmZlNoQlNDUjBXTzN4MlhxWWlRNE9YQkVUM09aeDVKbWYvTVd2SWRT +akFVUXRmbVBnM0dHbHVTNHhjQUczUFM3WVhKcWxKM3FRdWVQZlhlM2ltbytaN3JQK2VaTmhyUjZK +NlRPUnlVc3NxZnZFUTlUMmVCWmpuRGtkZXZwc3ZScjBSQnc5Z2hFZS9aUWJCa05kRGtaMWlOZDF1 +RmRQM3c3cWszWG1jU0ltck9xQkNjZFBqNzRFczdya1ptNXI5VnhPK2RWcE1DRnUvRm9Nek9icFVY +eDl3RE9zNXNWUnErLzZMQ2FONENVYXZQM2wySDQ0cmNXRForcVExL05xdGZxeVR6N2NxVkg5OE5a +dytKYkRvWUFUT3JHLytkU2cxdlQvcmIvejUvdzVmODZmOCtmOE9YL09uL1BuL0dtZUhqK0RQL05m +UVAxdU8zS0FSNWFGa2dJZ21RYmJLQkFCSWtmUmlBU1dRQkZDNUVRaWdjWXJIT0V4d2pYTmN6LzVN +NmM5d011amFHRGxRNHJDTkJvMjY1RmhQeEk4ODVZQlpqZ05oWWhudm9JeTFDSU84czNEWkszaHJZ +UTZ4QldIY0RRUURoZXM4TUVPVXkreENNMmRLSmxTUGsxRVBwNDBpV2pFN2dFUUFkNTNlL0NHSndj +R3ZIakZuZmpNMFI0UUo1N2xzdDg2ZVhEdXM3ZzFRamg2TTUyZWVYTmt2MXJ3YXg4T3hNdmVXK2Na +SWVsQmE4RzNRY1R3dVNLeXEwdmQ4cVNmbDh0KzZ3eDhpUytlQTRnZzhTS2Z0eUU1NUcrL2VZZWJX +dlhDUG56NWpCOTc0TERlR2p6ZzFjRkI2TmJRWVBwd1BIVlVNM2p0T2xqVjdyUDQva253SHBBNGto +YzJlUEJsSHczSmFhLzZjS2hHY2VHaEcrdjBRNzE0eEk5NXZQS1ErSGlUMDF6L2NPdmhJNmNlaWlN +dnJ1VHEydmp3N3RiT24vUG4vRGwvNG1qK25EL256L2xUSEdQK25EOC91ajlmQ2lWVXhOalFDY0Vs +cVFrOFI0TEM3UEVaNFM1clBhK2dOWlBndktuSjJpc0NTWnFCZUFiMW45Ti9mcWIrWnREN3FkbmVB +dlUyeUI0RFNmWll6NXhJUkxhNEJDMjNlUElSdkppR1puaFRaYS9MZ1lGWWc0SGVHMEJ6eFlUUjJ5 +RTFsSHlOOGJucmtRb2JZaXNXRGZEY2QvVWcyUnJyTmRoenNSZ0daMzR0d2RzaGgwWVBDSFhVb0px +TVk1ekFyaVk1eFBEWm5EVmlxOW05aHZDWndPQlNKM0hEcnM5eUVZZDFZakdNeituOTRZSWRCakZn +VW9OWTFRYU04T0NqNG1KcWZaV1BVRDF6MWNTNDBGOGN5QmZ4WGgvb0ROZjIwcEJheXhIY3hVclFo +amh5TUt5MThLck4ybUxEaFg3SkRaODNUWGhSaDBOZFBqdzQ2R0VRcTMvZ1dGLzh2c05rbjdoaXFC +bTJtbHd0VEdwTjE1blhHMXFGdjRjZkhuTDRIR1lIQTUycEs3cTVmWG91SDkyWmM2KzI1cy81Yy82 +Y1ArZlArWFArbkQvbnovbnpzL2p6NWFFUFFBRXJzVTJhSW9udmhtQVNLb3JRRllNd3hGVEVHc2Nv +QUNIS2QvSE4rdzZFbjdBOVp6SmsyTmVmdXEzUkFPUXpDNEVZQ2laa3BINzNvMThQTVM0aU11QmlN +QmRqSTgzUURELzl3NkFPMkt3MU5GVXRDSVNCYURRUWp0UjFXTlBvSTk1ZU5mUXpYR0tKQzRNNURi +QmVmTEZqMEJPSjczaVZTMzBhNkJtRHl1a3ZyT1BESE42SkZYL0VBSnM1K1lpSW9Ca05Ccm5FMUZ6 +MXlvOVA2L1ZTVCtXUGdDK21QUVFpdHpocXdUTk83VkUzVVRBby9Qb1BrOWoybUNjY2U0blhNMkx6 +bmVEVWhSTjlzbGQ4K2VDb2NOM1YzWU9iK2RRcW4zbzlWeDg5d0N5TytQWTJCODdWOS81QVkzNzFx +eGRQN1FYZEdHcnczQjhhRGhPNTJoY2FFVXN1bXFaNW1OMzFvNGVkdldJYXZDQTJmbnBZcXRVOGp0 +VGd1N3RjdEtBT2EvbEZmWEs3d3lvUExzVHBIdzc0VnVmOE9YL2FNMy9Pbi9Qbi9EbC96cC96NS95 +Sm44L2d6eGRRQk9hT2hKQjdoU0pRNDN4WGxHYTA4WVFzT2FBQ1Y0Z2E3ZzBPRUFvaUdNazFUbU9B +MUF6TjgweDhJa084aTFpSVZqeHhHSTdBckJGUERML0hycEgyeW0zQUxwZUR3eHNoMkRVT1dlSnBL +aUtJeDc2TWkydWRlVFVpVXFPUWliaWFwSWNQekhEZ1FmTTAzRDdDaDlkejgvYlVvQWpIQzk2czl4 +ZkxQU2NzOVJFNkx2RUdCNjdneGw5em01TlBEdnkxVjJMMjRORjRjeml3Vms3MXl2bU1ydy9ueDV0 +YVhQS1pKekoxLzlkR0lFenhyTk5yd29HOVhNbmhXZmN4bEZqV2V3YXZQSVJJTXpXUmVzV1ZXLzhj +aFBqQ0hYdzRjRWpvQVo3VTZUdlJ5Z2VYM1BxSUJ3Tm1YSGlPRDNjNDVUT1lSV3lhRXd1bjVSTjJu +KzJyUnR4cEEvN3FuamJ6QjhMcEJqYWMwNlM5ZXVJT0E0N3c1YkxmM1RNeHJFdk80MEpjZzg3dzBI +bTF4S1JuVURxWVArZlArWFArbkQvblQ1am16L2x6L3B3LzRaVFArTWorZkFsQVNBQUJhN0VDTkUx +QmltVlE2d0JIcURsQ3NGNWdEUU5lREc4OE5BVWhHbUZlMC96bmVMRTFBN25NcDJqRUFLVndPVFJP +azBMcTVkZDRlZVhvbXlFbTlFOVY1L0Q0L2t2dWlCVlRmakVOeE1BZ25qcGc2dHNqTlRHSU90cFVj +dzRkVFVRZ1htcUkzdFZnblN0ckRwZkdWQ0MrTXdTUzRSWmJYWExCU0dnNDlPWktqZDVvcVJNTzYz +TVkzcnc0OWpHV09mbHphQndtT0RUYUphOW5CQ0ltYnUwVnkxNVlDRXBQZTdEcUUxeHFiYS9GdEk1 +NFBHY3F2WERoRjVjT1BIWEFvbGI5dDg1NlBRd3YxOVBnUDB4eWlVc1BZc01LbzV4cXB3MTgwSVRQ +Y2pHbHRlS3B3WGVjeUZWT3ExZW1Oc3d4REoyWnd4TWVudnEvNUJsczVxc0pHUFJQdmRiaVFFMjBC +Yk41ZGVLNFBVOS9idENrZk5ZeUlTNnRWMThPbTd2YnEwWjdQQk1ISDdoU2g3dTYxSS9mSGp5MHAz +WTFxTjJlMWp0L3pwL3o1L3c1Zjg2ZjgrZjhPWC9PbngvZG55OEpDTmdYR3dVbUNNVWhXektCYWtU +QkVBa1FJaFdETE1FVjZzMEh3bnhHaFAzaU1RNFF3SXByblRjS2hLVmdzV0NSMTExeDlxV3gxd2pD +OEJNNWtSTmFpVFFVSXhaTTV1MDMxTU8wTU1pdFVlSVlHbFh6V08rQ3kwL204Q1BVZXMvRmdjdDNn +bFNibXVYRm5iWDRreU44M1dHRGVFMVhoMlo2RHFONi9PVHZYem9UeHgyZnhJTmpnb0lCTHBlODVo +d3U4dUM1L0dpK2ZsZ0hPMDV3WnM1enZheEI4WXdMbkRVdTRjbU5UNmJTRzhKaFJQWEFMNjZjTUtu +ZFBySEU5d3dtL09BTUozcGVnN3JrWUZEQ3d3T0RpcWttbkloQmtIS0k2dzZEL3VpVjNzUGZxL1Zh +cTMrZXlWMlRxRjFQWUxGZlhtdDlaekpyMWFVTzllaVRONHRpaXUxWFo5UU9ILzV3TFo3MXNEQzAv +dW9WVGVzaER0V2tYdkh0aDBQKytrcXZQTU9GZXQvSGlUNHVOZ3ptZWppMTEvUG4vRGwvenAvejUv +dzVmODZmOWh2ejUvejUwZjM1N2IrQUlsV3hBTFVCaXBiQTh6WUg0UkpWck5iRjROZDRRSWtBd2No +Z0tDQmRSR0F0d1NETFd4R0NWUmdTQVc1dWU4M0xRN0J5SWdzZXBHZ3l3Zm9KMzRDZE1Gd01XdU9L +YVoyWUpSbEdBMmx0b21jK3c4TThTQlliSGlJU1IvUEZ3WWNjWXNYMGQyaTV5K21PV0VLSEtibHZu +VnJzaDArdGVQQjNBTVR6aGtodXRjcUphd2FSTzN6Y2Z2dHdvb21hV1RFemh4N0FwQi9ld3YzOHph +QTlVR0dSVTM2ODlWQnlRRnBucjg5eXl5dW16M0xvSmFIYUk0LzY1SWRYVCtSd3R3OW41cnpCaTFZ +T0gveU1obE4zK1dqQ0hHSExnU2VYT3ZWWURHdUpWRi8wRGYvNjVRK0tDbGM5NWh6dWhDNlh2dUFh +RjliQmFmaHVmWHAvTWRMTHk2ZDJoNnhmZmFHRGVPRTRVejlPUElOSm5iRExJUjlPN2RVWGRlZ3pI +ZE1BM09KNHJzN3FDWGZXcU5XaFlKMkJLeHJEdTN2anc0K0wrWFArbkQvbnovbHovcFJ2L3B3LzU4 +LzU4N1A0OHlVeDRBSWdSdkVBdVFES3B0dmdKM21GRXBmR0FRR2tCZ25tMGt3aXR4ZEFieXlzcXpp +c0Y2dW0wQ3pDNjFzUk1ZZ1FTUlVROG1FaVBQSC8rZzkvTGMvdEY4ODhRdjJVVDZCKytoZkgwRmhG +d2cySFBXSVpOU2hzTUxock1BS1pSR3oxRXdHUzFkUFBHZ09qTldLRWkyc09MbHdPSVc4QzdNR2Yr +TVh2OE1LaFg3RWdaZ2NYWERXY1p1cUROZVZPWStVVXkzTTRQR01NWEh0bXYzanVPVmdQRDNIaFIw +NjgrQjE5R096MzNUNWMyVWVvTU1QdnJnOFI4c1dqalpyV3BYYnp4Q1UvekxqV0Y3SEU2RDU5OWQw +aFlWNWY4ZUd6V0RnVVIweVh0ZmlIRzJkcXhpZGowUTdNNm1Nd2RSanRjK0tmenV6Ujg5Ui9vNGNT +RGhrRDlxeTd1Kyt3eXEyWDFzQ0FYekhrd1JITlJtL0dZWkdiWHVoZC8rVEhCYTd0RXdjWE5hMFkx +dUZLMytqTVVMdWE5TjQrQnJYZWM3aXI5Zmx6L3B3LzU4LzVjLzZjUCtkUFkvNmNQNDJQN004WEVt +eFVER0ZJTEJraENtN2VKa1FyQUtuK015N3hLaVRpdUNKYy9VblpUOERBTXpJU0NGV3pyU2NFTWNW +QnBKaUtBaHdKRlh4RnJWRXdOWWFmNWhVaWx4Z0diSWp5VEpQYU9QVlUzSVFwZG9tcG9PeURVWlBr +RTB1OUNJTkhMRHdRdEdmd3E5VXphL0dXNzljODlUMU5mUDZpTmJ4SUZwYzVyQ015K1J4ZUdxaWU5 +OEt3MStHaG9YS0xMemY4WXRrYjAxOWRqR1FkY2VIQ0d0emdyNEpRcjNpNGtVT016dG1EQTVjMWpL +RjNQdXVyNXppQVMrMzIyUU12ODhtUEI0SXo5TWhGWEdyRkFXUGpSSC94UUR1NHhvMWE3SWNYTHJw +VHM3V0dQSEFRcko1NDNscmxhZitaMzJmN2NWcGo0WWdHeEhIaEJ4NmN3aUZudVJFWEZyZ1lEZWZ3 +aVNrV3ZlczlMRFRwdXo4UWV1RGdVNTJ3aUF1ZnVIcUZVeldxdjFwL2RQZzEzSXFsVGhqMWdEWnBT +eHg3NXMvNWMvNmNQNjJmUCtmUCtYUCtOT2JQK2ZPaisvT2xhWnJnZ1VsSmdCUVFZTUFsVlNnUWdo +SDFOL0hlZW8wRFJESG14UEgyUnh4enlOQnNZRFFQY0h1QkZNOVAzRUJwWEl0RkxrSU5oQ05WQWQ2 +b1dLY1JZaGlFb2c1aTl4d1dBeG5pK082d2dBTWVROE0xVlE3WVlKVmJMSGhnc3hkSnhBbFRCSG4x +aWlVM3ptRFRERnoxc2w4ai9lZnlpZ3d1bkdxS3ZONW9pZVV1aDdYeWVGc21qejJ3NGhtbm1tdWR2 +QzdQTmRxZW1QWXU2L0NFVTJ2VWs3YzBKeEIxcWdjR2EyQzJ4bk94bUF4bWF3bFJMODJwR3hhaWhk +ZisxSHVmMWE4V2MvaTBYaDMyNDlNK2NmVmRmek4vdlpkZlBlcXpWa3lZNVJSUDN4eGt1SVBSZDFj +UFJ6V2JDOWZHbTJIOG9VQWZzT0ZEdm5La1JuMFd3MmY2bE11OG1Qcm9POE9KVzE1aTBOTWJEVEV6 +SFB3QXMxeldxNFgrbU44UXgzNzU3Tk5QUHZLOWgxbU1mb01mZWxDcFFXdzU1TUxoL0RsL2lqVi96 +cC96NS93NWY4NmY4K2Y4K1ZuOCtVS29nQ2FSckRpTlJJQ2dTQ1lhQVFrQU1NVVFpM25mZmE2Z21P +QmJzNjRaa3Z0cFc1RUswRXdDbHhOWWU0a1dVZUlTbFVMc2NSbEVnZ0M0L0c0NVFwQ0RRTTBSeHpN +NTNaRnFPQ2prbFZPeDRtbDQ1OVFzdGxxSlRuMHh5Y1gwZGdNV0JNTXRGMEhpeDMvZVZyZG1xODkr +SWpTWEp0MSs4VlAzTlo4Z2ljZGRyZTUrMTE5VDhJVUx1VFZMZ3pTVXNlU0dXKzFpVlVRNEV3Tm44 +dUcrTWRScnIrZjZJNVpteTJOZVRIMVN0emppNGd4ZThheGxValhoMHgxL0RpejE0Ull2c0tzZkpo +dzRsTVRSYzRlNU5mYUtoVlA2YVgvZ0toWngzY1hGc2MvMjY2bjFlc1RjOE1FbXBqbnI0VEhFOWht +bjN0cm9qVHo2aEU5WThVSS84dm5Nb0hnU1YwelAxQ0ZHRHJkYiswM25iM2Q1eEtOdmE3emxrOCs4 +ZnVoZkQvN1V4M2lIaStIZ2Rja3ZwMWpGTFhiN2JiK2U0VmJNK1hQK3hCbTg4K2Y4T1gvT24vUG4v +R25nUWQ3NWMvN0V5MGYwNTB1RFhJcEtNYmNCT0p1QUlEeWJtSlNaRlNPUWRScG5EY0tCYTJGK3Vp +ZDRvclNXU08wREZFRGsyT016ME9KckNneUVBNnk3Sm11OHZmYkk3ei8xQTQ1OGU5c2d6UlVUYmdR +WkdndVBlSVFPazNzSFhJaVVWeXp6TWRqbFZBOXhxSkhBN05Ob2RjTm1EeUx4bzBGRXIzNzc0Q0hN +bXJQbUYwT3RNUHBPZ0E0ak9YeXZ1UjBPNHNrdlJybnd2WHlMUzZpdzRSaUc1bEcvL0FUaVVCRWZY +MHhxSHo2c2hVUGQ0aHZpNlJHajRydzZFSU01NU1hek9zU0FFdytlR3c1ZU5lRUZEakhVUVd6aXFr +RStQTFllZk1KdkxTNXFTSHZFVUNzK1lSTERPblBXdHM4NGE4NGVubXFpUDdyRmEzdU5FeldvTFlm +eDlkcGJIbS93OEdTZDJ0UWhqdlh5d0FFRG5kcmptVGVTTHR5N1lJQVZQN1NxTHJGd2gyKzFXVk0r +NlIrL2N0b3ZYdzhpdk1Bd2Y4NmY0aHZ6NS93NWY4NmY4K2Y4YWN5ZjgrZEg5K2ZMLzJnYUlJZ2hN +SVg0aVJtWnlHYzRRMUVJY2drbUVTRW9GQkR6OWhFaEF2MlVyb2xpQTZnNDMzc2crS3h3NjhVQ1Zt +eHZQTHhwcUptc1JhQzNRdjV5cys5SXJVRVZiYTJjNGhHRm9hR003YnU2dkdueEJxQkRMcmlRWFNN +Z0JrSFdheVFzc01rdEgwT0duMXN2citaWGRPcTBuMkFxY2pGd2d3czhxMVhqMUdlZWFlU1hXendE +bnpEWGVQYmgxbmNpd3BObTQ1TllyYlVIQmpWR3VDY2lBbUE2K1prSGJ0emppWUQweW1lMTlHQm9U +L0ZDRy9qMFhCNVlyTFVIRmpubGtwOUcxS0l2T0NoSDViNjZDbGMzcjE1MTVOQzd5MW8xNHFzaWxj +Y2FmVlNyNStxRzBaeUxJYUtmd3l3Mlh2WE1wZGYyd2lsK2UwMC9lRkNENGRDSFd5L2x3eGZkNEVj +K2wvMzhJUmNjZW9kVEI0MDVGd3pXMEtMOTlRc001VnBkdEtFLy9rQlVyNzB3eWkyK212Q2xSL1Bu +L0ttVytYUCtuRC9uejNBMWY4NmZOK2JQK2ZPait6Ti9COVJQMGtEN2FicmtFcUlOTFVKaUJiWkpR +Rmp6ZmgzU0FkRnM0UHowckVHSUt6Z2kxbmhFRVk4RFFHemloTUZGa0g3U0Y4OUFrSUpkL2xsbkdO +NGJWUE1Zd0hyUGlNb1FWOFBFaEFNZXBIYW9RZjMySXRKK1loR3ZoQkVub2Z0UDBiQ3ExenIxd3F3 +cFRHT054bW95NHNXQVF3UHN3ekVPREJ3UnM3VnRJdHcxcU9hSWlTdTF1dG9qYzNEaEJKL3dXVWUw +TUltYlJwOWhDS09jdzY5LzhzanZ3Tk5uQjQvNkNVTS9hbnByUFljZkYrcldGM3Z4Sm9mbjVjUitk +Y0NKUTN1dFZRZE53YUJuOXNJdU53MWsvdGJMUit4eTRBOFdkVE9heitwVFMzbXl0d1lsZG5QVzJH +K1BuSHFOQnh6cEdVeWVpMUg5bVBlWnJ0UUNoOW9ZdUxqcEhSYkd3bytjZVBIL2NWVWp1L0JXYzFW +WDhucU9HM3JRRTMzM3pOQXplOTN4QUk4REFHWjU1OC81VS8zejUvdzVmODZmOCtmOE9YL09uNS9G +bnkvL0E0ek5TQ1l5d1pCSTNBaVVGQUdTSXNWbERTRElrTUJuQlQ0RytKcmkvZVNOU0liVUdISEUx +d2dGQWdTWWhnRGV4cnVzc1kvWllVR2NZdjB6MVg1eUpqQ21NK3dsWG9SWVY0UENqRUI1MUtnSmF1 +cVEyL29LejV4NjRFRWdITWhFbHB4cXRBZDI2NWpTbkVZU3Fid1I2NUhzY0JCWC9CNCs2c0NOMmdn +WlIrcHdMeDhHb2RrVGNSd3VjOTUyaUFFWFRzWHkyUjU1ckZlanYvUnRuL3o2NllMWjRXUmVMajEz +TVBqc0lKU0RLS3dsZG5IMUdIWnIxU1NQbkRCNEpxYjZQY2NSek5iVENON2M0Y0JOY043ZDRTRW5u +Y1FBdDllYzc5V0l2ZmdYVTEzVzZDOXpxRkVPZVBCUEQrcUVXWDc5VVl1OVB2L0hQLzFYTVpUYzZx +Rk5NV2hYM2JSdFhnL2dnUmNPejhXaFZYalVxN2N3bENlSGswUFBldk5pbWpkbnY1anEwYXRxQVhm +V3dLcm1ZTHY2eEZCUGEvZGNqZEhseFladC9wdy9yWjAvNTgvNWMvNkV4VjV6OCtmOHFjYjVjLzc4 +YVA1OFNld0RzYWVaQjFMei9FU01BTVhWUUFva0ZpUVJHU0NBK1E2d09KcEFvSkl4ak9mSThaeVlK +ZloySU0wN2NDNmlsVnVoNG9zakp0SVFBZ01zeE9jdnhzTHhkei81N2NUd1Zvc1I3WUhiL2hvVUdk +WVRrMHNNVndkU3JGZTd6K0lpTGdJNXJQYkFpMUN4TlV6c2lQUk5DRVJxamM5aXdhVCsxc09JZUJM +UHBWYnJ5a0hGNFhEb2dhTWV1UmhDSDhTSjRBK1BXUERnMWQwejg0U09Bd2VKTldwd0lPSkFYQWJY +RCt2VnFHOXdlRHNpQjd4NlRCemlxczhhc1lsZGZXcTNWai9GQ0IvM25RanhGNnkzVDEwT1EzZjc4 +R005REhUVGZ1cTlYRXlqdnI0MXdwLzYxQyt1TmVveFlHZ2Y0SU9aQnJ5cDg1eHU3TGZYWCtnM2lo +TTI4Y3poSENaMTQ4VStiNkwrNmc5K05iMkdqWVp6b04wKzgzTFVDL0xUbHIxNHpQemhOTlNqNzNp +U20wR3RZMWo5Y0tEZzJUcDQ4b2ZJY1lKclBaUERQbm5sbVQvbnovbHovcHcvNTgvNWMvNVVrekYv +enA5R2NjSW0za2Z5NXd0d2hTTEdSSTBXd2c5RVJRVzhRREhDa1l4TSt4Q3Q2UVFDRUtHMUFBa1Uy +Z0lKejNkeGlFYnNGdUFTQnlGeUFPeXp1REFSbnFacnBHSVpOSWZLTlVQUjhDUGRIc1FiekEyUHdv +bldQbkU2Q0FndStYeDJBRVFnMXpUWWZCWUw4V0piNDd0NDZvNXdieDBCK2h6Y1Z4OGVrUytmUnVG +WXM5V0NTL3RkaEVJTTV1QlNaOFIyK2NTeFR6NEMwRndIQXd4cU1XOGREdkJldm1LQW04TW5FYmhi +NHcyUkdQb3FocGd1Ynpuc3dTRUJ1dHVuRmxkRnBzN1dEYk1ZOE9wek5TQy9ISGh6OEpoVE16eXdP +d0JweGw0SHRtZm14ZFludlRYZnVzd3p1SDVVSjJMYUR4Y2M5cHFIR1k4NUdBNkwvZjQraGI3QTZa +bDZjQzRXYm5HR2IzbGQrc2gwTU5DRkdHcXhGMWQwWGIycDFSc291T0d4eDM3eHhLY1h1bjk2K3Z6 +TGNMQS9CODNYYU5QaHJrYjg2WXNZK0pBRE5sam56L2x6L3B3LzU4LzVjLzZjUCsyWlArZFAreitE +UDEvK3A4RDlkS29nQ3hYck9USDRpVlpRZ2N4cEtNREUwNzJJQXByNUZLQTREWlZJc1JvaXVTS1ky +enp5M1JtWG9LMFRIeWx3d0ZDeHlxbnBmdTBCaWQ0MktOQ3ZNQ2lvSk1KZ3Y2RnBjakVLakFpTFFW +UFA4LzluZzBqTlFoNngrcHlENUhEN2JCOVN4WERCWkI5aWExQk5LS0VPTXVScnB0d2FLRFlCbUlk +WERPSXlKeVplNEJCTGc5VWx0dnhpMjRjYmNYQmhYbDhZeXVkeUNRTWVJcFRMQTRjTFhtK0M5SU40 +clBIWmM3aDhoaGNPbkxsd3BSY3VPWEdDVzVpWUF5WjQ1Zks4TmVtVHVQNnp1N1hpd21vK2grWEYx +U2RtaGFXYXN5L3hyMFk5TitlNytzV0FTUzFpd2s5NzFuaW1uMHdxajd4aXlhZWY4TUdQWDRkSER3 +dThtN09QWm5NWUhKZjJXRVByNGZpd3dDQnZEekI3eE1lcGZQV0JIc0RpbVh4NHFrR3JUWFhRazd0 +OGF0QmIrYTBSdndjOEh2RThmODZmOCtmOE9YL09uL1BuL0dtL2dZUDVjLzZFLzZQNjh5V1JxOFQ0 +YWJkTlEyYWFmYUlSVUxQTUM5ZzNBUUpiNXlkY2dCaXhvdWthKy94T2NnUjlSU0RRZWczMkRDaU5J +dWprdUFZUUFBd0ZDcnlMT1JIampZZmNGV0xyc0RlSHlnMkNnMGR1Y3dUa0x5VWoySkNqSm9HVjZk +Vlg4dVJUcS9YVzJxOFI4SWdIUDN6RWl6czEyZHUzTTVxa1ZqVnJPSjRjSm5ERHBPbHlxOWxhWEto +SFhQbjF4RE5jNm9rR2E2aW11OFJWSzBIcFU0MGdMbEhBRFpjYTFBWUxNY2t2WHQ5c3FMMW1yeGs4 +MDA4NXhWWm5hNjgyNUlOQlB1dGRZbnFPZDcyQXdmZnF3ek14emNPaTd3Nld2Rkc2T0hMQWJJLzY1 +YlVHeDNtTGVQSHg0YkxmTTBNZWErM1JLL1V3SHV6MkJOdmx4by9QNWQvSVFYb0hOeDd0a1Z0OWFz +VXp6dTF6K05JcWJxM3poZ2dYY09BWWY0Wm41YzR6UFRldnRod29id05HL2RYNzd1bGhrMzRjdC9L +bXAvUG4vSGs1NTgvNWMvNmNQK2ZQK1hQK25EOC91ajlmZ2dBT25PU1MyYUJ4TmdFckFkRjRKcUhu +MWlBRFVlNzJBUTJJd3BEa3lwNUw1TTR3NGdCaUgwSUIwUUE1eEVVTTRwRHF1eldLSUNnWFE4bkZw +T1pUcUlQbGFtQUNwcExQc0ZkOW5zUEkxUDI5YVFNK1dPUzBydzJGQlM1N2lKcG81U1JzSkJlaldq +UVFQcnlZVDBPT1lJS3locW1Kc3cweTdDY1EzOFczRHorRVlNQXZObnllRWJENUNzeHphMkQxSGQ4 +VkxrNlkzcUdxRHZqMHlxOWZOSWU2OEdnZnNSSm9jektuZnVERkhQN2tkM2ttWC91a2J2cFFnL3pt +Y08wNXc4SHBiUWN1OFdlZDNPcldTOC9rczg5YkZmV1pnOE9jL1BCYkk0ODRjSWl2ZHZ6cXZVTk5y +ZFl4R203dDA4K3VyZVpvSEE3bTBoZjRZUFV2ZnVGSFRqblN5OE9oTGp6aExZY0JBOUhxMVM5R2RX +Q3ZPeXllcVU5TytidGVmV0xRRTMyb1c3MDR0bC9PZU9IMFEwZjJ6cC96NS93NWY3clBuL09uM09x +ZVArZlArWFArN05wcTdxUDU4MldqUmNEYXJBaUxnZkk4Qk54QWtPSUJKVGFGVkJUV1N3NlVQUkpy +aElTRXoveWFLb2FrMW11QTU3NGpDYm5XV0MrSDU0RERCQ3h6K2YxbnhoUWZxVEFhTlNoTXNHbUk0 +VUFnR0pjOTh0cVBSSU9BWUlURm5KaHloNkFUYkF4MnpVU3d6L0JaNjdNNnZUR3dUdjRJNW1wR1BN +enVHa1FrMXNNbW4yR2ROeVJ5TTQrYVhYcGd0RG5tN2NHeFd1WDRKdUxMaXo4OXFGbnhpek94TXE1 +T3o5VlB0QldhT3RUYStoaGFUblVRRHhIRGpzc2ExRDdyeFpOZkR2bGNOQ1FHYk9LS1phODZDYzVC +Mlo3SkY0UGZKYTVjNHRvUG4zejZwbVlZMU9hdENqNXBCRWR5TXFzMUZUVGpXT2Znd1MzZS9CcUVQ +VERqMG9VdjJHaFpmbnV0VncrTS9Yc1M5RkFjZXE3K2NITzl0VWNNOGUzVEI3Z2RDbkRnZ1Y3VlNp +UFdPNmpWbWYybko3RnhvUC80eENGODZ1UUgvY2ZmL0RsL3pwL3pKeXhpekovenAveXUrWFArbkQv +blQ1aHg2ZnBvL253aHdTSWdCQUpJSUlzWnprSkozUlVGaEdBSVFKVGlLM0NBM0NzaUFCUXJ2cjJl +SzlTY1BHS1lCemdHdmJpK2F5Z2k1VmVRendyMnZmKy9Qb2lNRUUrRWlsS003OTQwK0d3VU0vSVFL +cTg0WWhwdEhQTEVKQ0FZTlYwek5LeE5KanB4MVFjM1ROWVFFTExsc2hjV21EUmZUSUt4RHdaMzVN +dXBEbkdzMFlNMjJsN2lhNE5neG9uUHhDbXZXT1o5eHlNUjZJdlBZc3Z0UDZQTEJaTTNRSDVGUVk3 +bTlLc2VEbzBlYW1LbzA5MEZFMlBMMzRNQWp2YS9CN1RlaSt0M3kzMld6M3I1WURIRXBTazR3c045 +Rmg4V3VhenozVjJkMXN0anJiY3U2bkI0dU5PUE9zMWJUN011Yy9yaEdmMklxNitld1VtYmN1QVdm +bHFLNGU2QWNJalNBazU5aHIvYXhMRmNhck9lTnVpSHpoejI0bHFMQS92aFVKLzQ5SjhjdHg3bnRD +TWZ2REJhYnorZTdZZFA3ZWJwcXMvbXovbFRESFc2dTJDYVArZFA4L1BuL09tN3V6cXRsMmYrbkQv +Vk9YL09uLzlVL2ZreVNZVEF0S0VDV1V6TWFmYUpoZ2dsQmRZZENBa1U1aElNNkJZR25LSVFESnc4 +aWlWNkJBR0FPQTJwUWNXMGwvR3RKeUlGK1k0TXVEeERQQUhBYURBUElWVndHbWpZeHlEeXFRcytO +Umx5MW13T0VHdmtWRytib0ZGTXEzYU5oYzBhYTlWQ05PcHo3NkVpcHpjVjZzU0pIT3BuQXJua0pT +TC9lVnNjbU9TSEc3ZmxBcmNFNmhKTGJwZlA5c01qRjhHNjF5Z3VjMzVGUVIzNHhWM0ZKQVpNL2dV +dG1NU1huNkRWN0U1VW51TlhIK1ZVbTd6eWlDTVBubXM4ODJvb0htOWRjQlN1VDNEaUViK2E5YW85 +VWF1NEx2eTRleWFHZFRoUkI1NE5QSnZEajl6OVZRemE5UWNKRE9xQ3gzNzZnVWtQWVBaZGJwcW5R +YjJEMVQ3eittR3QyQzZZWU1BQlhuaEJManBRdHh3NE1ZOTNXS3BGZWRUaTBJYkpIdC9WZzJmMTZ3 +K2V5eW50eEtDbmFWellNMy9Pbi9Qbi9EbC96cDlxMXF2MlJLM2l1dWJQK1hQK25EL2wvaWorZkJH +a2YwclpGNGtSU1pnUzlqL1hldHNnY0lVTW1IbkJKUkVEZ0w0TnFXRTlSNmJrTG9SMGowdHMzOFZY +aElhTEM0djlTRlNJcHZoUDdBb2tiSGg5aDhYUVBLVFkxOFliak9XN1BHcENtamRHQmpHSzEwT0Nh +SDNYRERsZHNLalZXaGlzRTBNdE5hUGM1dXpUVUhzMEVoN2lWb2REZ2RGeFMxRDJFcXM1T1IwNExx +SXpMemRSeVdlUDlXcFFuN3VjY250dTNtZG1nTXMrTVFpSllOU0ZSNWYrbUlmUjN4WFFNL055eTBt +c3NCTUpiUENycVhvZ1JMM0ZnWDN0RjQ3RWNIQ0xyODl5d1pkeHhvSFQ0VXJ3OW9paFR3VHRqaTlD +RmRNemZLb1hIaGY5cVFsT3RkSldEd0Q2a1FOK2RkS3BlWG5zZ1IvUGFySmZYUTU0ejVuQjRRV2Yv +RGpIaTN2MGRHdnhEUmNjMXRPRE5XcFdvMXpXMGdFUDFYQXVlMmliRHUzeDNYNzEyR010VGZZd2FB +Njl3NG5ZOCtmOEtlZjhPWDkyajJ2K25EL256L2x6L3B3L1A2by9YNHJwLyttdHhpQkxZUnJ3bm1S +SktpWWthQVRRZ3RoVGd5SU5nQlI3U1RXclFtQVlUVWErb3NTSW1kN0VCSXQ5RWVVOTEzQUYyK2N2 +MGNyOTEzLzRhL2tKM3VHUkJ0eEFEQUlVN0ZsaTNsQXNmSElTTnNMc05ZaEF3OHozdVh6V3FWVU5Z +aUxQV25oOWhzRSsySWxWODlSb0g5SEpqK0FhcGdacHc4U0Z4ek84cU5rbHJqakVCbmRqNGtMakNN +WjNsNTZvMVdmNUdCc2V6NGdPWndRYllaekE1SEtvZWZ0aEhXd09qYjROZ1pGZ0NGd2Z2SjB3QnhN +T3hJY0JscjZONndGREE5YjQ3aEFTcTRhQUwxaXVKajFuQ2pWWFIvVGhqUTF1OVYwOG4zdFllNmEz +ZE9tdGlvT25CNkVheFBjTTN3NHBlZjB6NXZqMFhlMTZCN08zUU9MMTBLNUI5Y09jMm5Dc3grcW9a +dXlSMDJXdGV0elZxZC80bE10ZTNNSGpPUXc0ckNGaFZqZWU0VldUR3ExeHllK2VnK2g2VjkrSU0z +L09uL1BuL0RsL3pwL3o1L3c1Zjg2Zm44V2ZMdys5TGREY2JnSU1BQ1FDS29IbVNheWhpZ0FLSUky +elJpRkFFYTBFdm92aE8zRWdTekVLY1VlT3Uzd2xrY0d0RTE4dTJCUmpIWXptSENibXZKWFFKSTNy +NGFIQnhDdTJnVXc0K2h4MkdEc25ubGdhRnRHY2dlQ0JYODJleStPdGdob1oyTFBXQUROZTNNV1hD +eVpESERHSk9mWGZQcm5oeGE4M1hXSVRoenBoSkVhSFZReDZ4cGNUdnpWSFJIZ0N0dDkzOC9MMjZq +elJxMDkvMUVSdzNxakpMNVplTTYyOHVCWXJQYjcxN25LcnRXWmlVTFc1Q0JWV2M4MnJObTlabU41 +ejNNZ3JsM3J3U1FlTnFjKzBwVS80Z01kemNlU0dQekVjV3NjdC9oK0RmaC9oeXVtdS9vekxrUjdj +d2F4VzNEdTRIT0xtY09JQUVSOUhkQWtyVEdMYm82ZHd3STBQUm9ZRkRyem82M3R6NFFLZittc2ZU +dlROb0N2MTBKdTk5b2pwdWRvY1dIRGh4enA0N085NmZKWHorWFArbkQvblQ5alVPbi9Pbi9Qbi9H +bk1uL1BuUi9mblM3RVNXU1NRNWlNdXhGOUE0cTlnYmJDT3FKR2hPTUR0RVlQZzdDVmFpYzBoVG1N +UWdDajdGV1FkZ21Mb0EweWM5b2lMSkdERlJBcVMvRTQzVWZVTmdKanlhaHpjZmdMWEFERThOMkFY +UjZNSmk5Z1l3ay94QkN5R1p4VTZrdURTQUFKUXYzbkVJMHhjTlZubnMrYzRpRWx2dlNiQWp5OTF3 +b0l6bURVU0RvMlJEMWZXTUlrOE9JbEJiNis0Qk5JK3BMWmJJNGM2NUpZTG55NDVyVEVYZzE2djhH +SzlQQVNvVnJ4YUp5WmpFcW0vWEUzTStKQWZsM0lIMS9GcEQ4SERwNGRFcXhaeGZEWkhEOTY0eUFG +WG44SHlqQy9oelVFRkQwNnRZeGExaW1Vdm5HcUd2UWNjUWVzalRJYWF4TktQSEFER2Nhb0g2bENU +NStaaGQvakRJVGRNM2VkZm5KTS9CL1hWS1ovbnRFVm5lcXkzMXN1dForNzA2SUlCSi9iZ3hkMXoz +SXNoRiszQjJ4eTBFQU1lWHBjWTFzQXB2dS9xaDlrNjYrZlArWFArbkQvbnovbHovcHcvNTgvNTAv +UFA0cytYeGhPUzRreElackdFU1hBR1F6QWlDUXh3NnpVSktRSUtMQWtTR0k1UU5GaHhtaTZIZGRi +YmwrOUhtclZQRTcrbTJZcUNRMTZpVmhUQml1LzNxeEhuYnAxR0lDajlPVkVSSGFFaXA4Smd0QjRv +NzRYVVFSU3dxSmxnRUtKMis1QmRyRDYzc1hKWUp6ZmlFUW03dXpndVRSSW4zMDk4aEZkT0NNNGNI +dVZVcTg5d1dvTUwrUlAzbXV0d2tvOHA0WkJMVFB2c1Y3YzV6L0NFVTdodzRyTUR5ZHNiR0NwRU1U +MzNMNVdaczArUDVZT3YrMkxRNjdtNjVWQXpERGpBcFo2S3B5L2VlTUdqRm5QdzZEZE5xVWxmSFFi +eTBRcno2Uk9zNW1qS215T3gxUzZYR3VrT256VW9VMWt2cjF4dzB3anM4dXN2M0xqRWYrWk93dzRr +ZVQxWEx4eGl3a2N6M2lEQmJKMC9ES3hsVUhua3RBY3VlQjBnY2xsUEkzRG9xZWY0VTV0THYzR3Ru +NzdqUmY5OGQ4a1BLNXp1THMva3FHOWM4K2Y4T1gvT24vUG4vRGwvenAvejUvejVXZno1OHA5eFha +b2dnZWI0aVJ5NU5sYnNpaVJvaVRUYmV1UUJqeHhnL2ZvQW9KSllKNFo0R3VoNVl0NDZ6ZERjWGtp +VUd6bUVpb3ptTUlnUVJtdUpTZ3hHMVNnREtXSmFMd1pTK3B5NE5VRHhHbTVmaDJlRUp5ZWM5dHRM +RUxEV0FPSm9sTXNhNjYyenB1WkZ2QmdFeGh6V3FzTWRiM2gwdHg1dTlZa05XMFV0QnNIaEFvZDRK +U2h6c09oTHVMNithRDc4Y3NBaUwyeXdhTDRoSjk3VXpHaTRzTmQ2WERoUUNmVTU2TDVrcjVyMGlr +R0pqMUJyUFBIaGN0ZUQxSHMxaU9HTkN6eHlpQzFQemNVRTl2UkFNSThMdFJoNm9CNTcxVUdjOXZ2 +T0pFWnJzZ2MrdXJQUEVMODlJbXJQeGJHZjJPMmhDeHJKSVhmZjFZNGYzMkd5Rm42ZnZjVlNPODJM +cVU3NXJKVUhKdnJHZ2Jya2l1Wk94K2JVaG04MXdVSVBlaWptVTgvWFBMUEhmdnkybjJyWC8zcGwv +cHcvNTgvNWMvNmNQK2ZQK1hQK25EOC9rejlmZmlvdldQL1psQWswQ2loSkJEQ1FEUlJ4S2dKd2dp +RVFhejMza3pWQWdoZU1PYitmVFFTYXE2RTFoSnhwMHVYVmdCanBDTFlYSnNBTlFQM2tyNkRHOHJa +Qm96U0FDQWdJVVlSZjQ4SkhZUElwWG54dmxvalJKWmM5NHNQVkdESG8vL3Y4L3IvNnZwbjE1c1R1 +QVlNVGMwemtna2RkQnF6V3VqVGZuSHoyNEVnZFJPand3cXY0YWQ0MW1RZ2FrMGprYzlXMHVNZVIy +RDVISklkZlBMalZYUXlNNDIwYnZQTDBzRkF6OC9yTDcrSFpmK2EvdmZLcEUxZkY3ZzZ6MnVURGs4 +OXFGNHRPOE42MTRzR2lOL25QOFZjVEhoMFUrbVlPZmpvalZMR1pRZzNpNFVnTUZ6emkwSWRMWHV1 +dHc2bmhPVkVUTnl6Vmwzc1AwSmptdElCak5jaUhTM3RwQ3o5NmdZK2FYSDNpTUNjdXF6VjU3S2RE +R0dsTFRMVTRaT1Z4Q0hucjlCeCt6eDhHT0gwTzRhUGtZbFZ6WXJqRElJYUxCdUNmUCtmUDhEeC96 +cC96NS93NWYrYjcvRGwvenA4ZjM1OHZqZmZUYVl0Rm5vVUlyUm1JR1dtS0Y0eEJBVmFBZlQ1cmpq +Z0FLTUJQeEVoVUlHR0k0N252eEtFZ2U1R1huNGhQSERVblRBZ1EwMC8vQklOZ3o4UVMxOXNHTWZ5 +MGJiOWlyVU9LendiTXZzTXNsM2ppR0lnV0QzSHd1TXRMWUhKV0pCcUZUSGZyOElOOHVZdWJpRFFS +ZHJ4cHV1YjdybDVjbENOenNEaUF5cjA5MXFaNUoyalltRk5NeitDMDNqcDV4WEtYd3pyTnhTOTh5 +WDE0REh6NDFRR21rQnQySEt0TGJuWGsxd0xDMThPamZvaERaSjRUQ1QzNExDL3UzV0Z6bWNNeFRk +aW5YclhVb0dMU0QvNGNqcTd3ZUJjOW1NZVBOemp3aXUyQWdjL0JZdzJPOWNNQkpZNWVxZGZuak9Q +TU90OXAxcnk5NnRQTGNnOFhidlVZcDdEaWl6SDFCQTU3MUd3L0hMaFNvOHQzQm1kNlBPb0gvdDNo +aVJhdUoySzRhQTBQRE1lYzNycXBBVjU5cmFia3FrWVpNOWZoYkovbXovbHovcHcvNTgvNWMvNmNQ +K2ZQK2ZPeitQT0ZGT1M2Q3lTZ1RTYjkxSzhKbWdkY21uYnJKQUlZV0VFVmJzNXo2NUZPRkdMNEhp +TmNRVWhVUUl2V2RBMlBLQThVWW9nTm9RcTBEc0Z5RUJMaWUyZ1F4WVB0MEIzSnlCYVBrQi9CZlIv +eTVCYW5qZFVNZFRwYzFDMTJtOTM2Q0F2WkRnTjdEUEViQnpicmtBZzNIaEF1cjV6d3FNOTZ6ZXZC +b3hFeDBIR0FXMXhidzJ6RUVZUGV3TFhhZkxmR1BzYnhyTS9sOTFsTytISG5NM01TczU3QlhvUHFL +U3lFcWk3aWNaY2ZaajJRMTJlY1dLYzMrcUYyT1dwTXNkUW5GN0hyaWJkUWVMRldYUHYxampueERS +OTk0Qmd1bk9BWWgyclh6NzdkZ2Q4YU1XQ3lCajV4OEdnT0IzSjM2SmxhOURlOEhEL3R0ZS95WmQ5 +eER6L004dHNYUEhmQkJvczRMcHpMaHdlZmFRWWVlL0Rpd3FsTFRQRm9IRzV4bU5JYTllQ2IzdVNQ +WG80N0hPSlZEcHlKUVUvZXFGbmprRkxIL0RsL3pwL3o1L3c1Zjg2Zjg2ZTdNWC9PbngvZG4vbFhj +SW1ZU0NSV21LQ1NJRTBRUXhMa0NlYW5mQVQ1YWRkYWdRSFFJRDg5Qys0L1IyczBjaVFYRjNoRmVB +YWdoZ01zcjMzRUtKNTlqSVpVd3pQTlJaSTNLb29YTThLNmdUUjc1RUE4UVJzd1dxY0c4NXJBR0VT +REJJSlF0MzJhN1JCQUdCTnBoand1M3htenpVSzhmWVFUdzEwZXp5UGErMHdVYXJOSFhrU0xvNUY0 +TkVjTTl2VHdjdUdFc2ZCQjZOYkRVM3o0eGlIK2FoajlrNE9nckRlUEQrWlFMK1A0VlFUNXJhdDRp +QmRYM1lNUHZWR1hQbGdEQSs3Vm9jZnlXZXM3N1BES0svNWYvY0d2Wm8wOWFvTlBIUTVySElualAv +L1RGQjNCcGg3eitscjk2SmZuMXNDQkw1d0c0dzMxVmlNd2RCQTIvdlhRV2xqaDBldmlGMXVkdnYv +RmYvZ1gwWThlZWpzbGw3N2lSMnljdzZFT2VjVERxMXJrc3M1NmV1d2VzWG9ZNEFVV2MycFVUOTlx +MGxZMGMzanJLZXZWeGdkeTZKKzk4K2Y4R2UzUG4vUG44VFovenAvejUveHB3RGgvenAvRi94SDkr +ZkkvZmtkWVVRSUJqd0NiQ0NzR3ZhS0lqdkNKbXZtQTFReHJnTEllWU1uRjBnam1LNkdFb0tFVnM3 +MUljUWRHY2MxcFAyS0kwa0FzdzVxdmNZbWc4NHFTQjNhMXlHYzRLRFFlQ1FpRTNiejFhaExIUG5I +TU1hOTFDSUxCUHZ6QXAzYk5zRjVNdUd0UWpiWlBqVDZManpkTjFoQ3hOY0Flb29YVGdZTTNSaEZY +M1RXb3VPTGdnSkR0aDlVemUrUVJGNytFSXhkODRoT1Yzb2hoRC9NUW9QajI0OUtoZ0V1R2dKRXgx +V0V2Yk9ydXdXYWRQbm11YjJwVEt4ejRKWHhHOTd2NDVqMkhGNmRxeGJOTGJtK3ExS29tT1FpZHJ1 +ekJ2VU5Cbm1wQi9kYkFGd084bVpETzNOdC9Kb1RMZDZhd0ZpOXl5YTFHZXJHdjZ4d284aHJxb3ht +OWhrdGVkZld3d3JPWURndlk0TENPRG5Cc0R2Zjhnd084MmkrZk92QmtQWjZxR3ozaUJYbk5XYS8y +SEs3SGlRdm0rWFArbkQvbnovbHovcHcvNTgvNWMvNjA5clA0ODJWQ1VVQklyUGtTOWpraURJVUpw +bWlKZ0ZFNEVNZ25RTUFrVllqUDlpcTJRcE9EQ0lIMFdUeUNad0NBRk11QWlCUG5FZXozaVkwTStj +d3pmMy9YMm9BRlp2azBHaW1HdzRKQXJiY1doczVwSEFKZ0VKOWhIRHl3d2tJVUdrYllodmhpYTFo +NHVVYTBXZGJpUUVOOUpreFlYV0tMU1h3YXhZRHFsdy9YREFxRFBFUkZiT0xpQnpmd1Z2QTU0SEI0 +M01qanV4Z2FhNjFudU5Oa1BaQlRIK3duQWxod29HYytxMDk4bHpyRUV3c2VCcFVUYjJwdnIyRERj +dzkwY2ZHbW5xeDUwNFI2VXUveFREdDRzdzRlK2N6cmp5R1d2c0tEQXptc2d4K3UvZ3FMOWVwMTRP +QlEvL1RLVUs4YzR1QUhabjJ5RDE0NkVBOUgrdVROMmFQdHIrbUJlbnZKSVJldThRU2ZmSGhWay82 +cUFRNjkxWHQ3ZXJDWGJ6WGkwQnN3MzNsSFRMeUpJeDZjOExnN1pNc1pEdXlmUCtkUGx6cm16L2x6 +L3B3LzVUTS9mODZmOCtmOENlOUg5ZWRMQWg4a0FWNURpTS9pL2tSckNBcVVCbXNHRXF4aEtxSlFQ +TUxzUlpZbVNRcTROUW9IMUQ0eGZSWVBTUXBXbUtZZ0dBYUZlV1lvVGw1WXhUWHZiWVA0QmdHWVY2 +Z1k1ZzNGK2w2aFd3T3pvYkhxSmlyeG1ZK0FORm10dURCblRRMG5sbm8wRk5GaTFBdzRrQmRtKzlV +b3J3dHVUZlBaWWVRdWxodzRKa3dYL01GMmEreG5sdFl1dGh6MnlFbEExdVBKUEdIYkJ4OCt6T1BL +Zm4waEpyWGdRUzYxNlFsc0xyWEFEaHNPeExUR1oxaDh4amNSNnBtM1NUN0Q1WGUveFhibFlEbXU1 +Q1V5QnNBSG9mdDlmYVlSeC95anJTL0JEcXVjZEdnUG5ITGozbkNYVS85b1RlM1dxOVZ3VndmOWlB +dVhYSExqa1dZZDFPYndwNGZWajg5NGs0OE8vR0VBQjNQalE3MHVldEpyZGNOaEhkUHByWHp0dStj +MEx6OU05cW5SdXZZSDErckJpMWl3d0FtYit2VkRmZmJqZGY2Y1ArZlArWFArbkQvRk1mOW9hLzRV +QzViNWMvNmNQeitXUC9NRHFHQ0NhS3JpbWN3R1RYM2UwbnlOTUpBS3NNUVNha1NhZU9EOFoyOWdL +ekpnaUZCU2x6dzFxT0o3Q01pcllJV0xJVGFpRkVsOEdpZ1BrU0ZZRHBpSUFtbUdncTBsWU9LckNj +V0V3WnhuNG5aT3c4V0VTeTFxOWVzTDZoWVBSdldxejAvc252ZlFzYzhlTVdDWFZ3NWtQNWdlMFdt +Ry9UaDFQYm1mTjBScXdUZFJFMFdiMStiZ0I1Znk0WnhnZmNhZnhycWFBeWVQMkwvUEhrSmhGRFdw +d2J3OGFvSUJUbHlvQis3MkRGOXllR2FlUUt3WG01bXRjd2lJRFRzRDFxRG0xZGlEdzM0Q2o4R09Q +ekd0Z3dWbTlZaGp6ak5ZOUs0R3hSdWNlb0VQc2RTTVg3bkt0emxESHIya0cvMkErMjkvL0ZzUmZM +ZzRYZEU1N2RBaHJibnJsWDdpRldiUHhlOWhxQS9XeVdzTnZjS0NWN25rd1MyODV1REh1eGpxZ2xN +Y25NZ2pEblBpeWZQd2ZManczdDdsTDZRZnorcWJQK2ZQOW16K25EL256L2x6L3B3L2pmbHovdnpv +L3N3L1FsUmlKZkFkRVV5aEtKOE5RWWtkc1g0OUFCbkEyYXRKQVBvcDNIZkRITkZKd3FDQUtsQWNw +TW1uVVJxZzBlNmFGTEtPVUFVaW9jMzFrNzlZaXRaSXpTWlFCTnN2bGdKaFZMVEJQQWpQczhPSUNE +VzFxWEtWWEhkdmlHQ0ZEOUh5cUZjTzhlRnpGMGN1Z3NZWkhIaHpoeDBlRGNaaFRTNE96aEJ2cmVa +cVBxd2FDaU51MUF1My9JU1JYdHo2Zm4vUEllT0lBWmQ4aG5reGZGZXJmc2pGYk5ickMxenF0Yy8r +bWh0dWRlSEFPbnVzdFFaKzY0alRHbmowRUg0SEcwN3NZUUI3WE5hcXhYQWdlVU5rSFE3bGZ3Ny83 +L01NMS9yckdZN015OW45QmtIakFGZDRoWStZRFh2Z3dTVjhCQThQTG1EMEt3bzB3Q0N3djljakhj +aXRQK0xTSHZ6bThRK1R2bHFMSTVkNVdxeC83R2N1Rnd4aTZqc3M5c29IdDVnOWJNTGZZVkk3dlBR +VGc1N2UxQTEzNDgrZjh5ZmM2cG8vNTgvNWMvNmNQK2ZQK1hQKy9NaitmRWx1Z2NVdXphaEJHU3dO +djBFUWJiaGlOUXZSdml2YWV2TkErR2xkUWdVRGpraEF4UG9tdE10clBnMDV3NGh2alp4dG9MMkdR +djJuZkhlaTBqaDVDTWxnYnZsZDVyc1BFUXpxbWJpRStuYy8rZTJZeXRBSTRsQ0x4dmhQMVQ2TGh3 +Y1lZQzlaRGlDNWtjOEE0c010cjNWcCt0WGp1VHJoRXhlLzRlQmlJQjgzbnVHQmlPQ0Q4K0g2UzVv +a1BpeUpmeUx3MldVUG9haEJmTnpqVVQ0RGRzTitITUhxcm5aN2ljQjZ0YWtmVndSSHlPNXlxQ2NH +ZlRNVEx1eGpFR3Q4cHhWMXdzYWdlSUZGRHZXSjh4eUVYeE5iL1F3YUhSeEhlRW05WnpEUDRDc256 +R1k5SFBJNUNGM3FsY2Q2V3NCM3NOK1FDMGFITEUzQ2dGTTR4YUVmdGNBcmIrNkhnMWE5b2NNWDNj +dGh2ejFxcURIaHduRVBaYm5oeFZNUEJSak00NVhlaXRFK21QMmhnanM1eGNCbnRVd2JNS1YveDVk +WTVXcituRC9uei9sei9wdy9xN241Yy82Y1ArZlBqKzdQbDhRV0FTRzRBSURib0VEUERjOXFVSUxL +bWdzQXBNWUFZNzFraHJrMjJHYzVnRkl3MGhRdkJ3RWkyVjBzYnhLc2wwZHhCaEtZbzVpc0l6elBE +WVVTSFZLUkkxZkdGZXBmcjdLMlpJck5jSVJEbkJvQ0k5eldXb05BOWNGdlBYenVzSWxkY1NHendy +Ry96eGtNSG5oYms0YUpZUkJQMWg0WG1rS01PQ1d3Tk9jRVdqR3B6VnA3OU1JYWRjcHJYdTY4aFlp +NXo5NlhtOGs5RTVQdzVjYUIvUzdjKzA1WStxL2VHUFROY0JVcHZMN0xCNDhjUFh6Z2trdnY5VWFk +TUlwbmorZm14VmFQQThFQlNDYzBodnNjS3BkWGZHOGRZYUlSNGl4ZnVMZWZlZlJEVGZiaVJLL2dh +TjF5K0tlZ3pkdnJ1L3p3aUY4VHFFOGY0YVZGcHZZZExsanNWNjhhNG9lclc2OWdvRk1ZNWFkRk1k +U1VBL3V3NGdkR1Z3OXRuTXVKSnpFY1N1TElEYWU2elpjejJvUmJ2ZlBuL0RsL3pwOXl6Si96NS93 +NWY4cVZNWC9PbngvY255K0JCUU1BTVFSZ2t3WnJZQUllaWRhbDBYZjVDOUkxc1orOGF3NkpmQmZI +UHZQQUE0a1lvbXFoYWVUdEFSUmdSQkM1UThBK0pJa3JsdjJJOTUzZ3JFTytvaFNpU1dMNUxwLzlI +Y3poUHlXYisrNUh2Lzc5VC8vOVAvL1dWR1pXSTVJcklEa1FEUitDN0VNNnNoR3NrV3BSQnc2UWox +Z053NXZuakZ1UjFLQyt3Mmk0aTg4bzhNb0xvNGFwbHlERjFBTmN5UW1mejNJemp4cnNoNTNRY1d2 +RWJEZkV0MDVzRi96MjRsSk5udWtWOFhVUGNSQW00ZHVyMTNEWUI3OTg4dEFJTEE0Nm43MHhWS2U0 +T1BITVpWNHQ0cXJQR3hJYWdVSGRNTXFOSjMzRnQvajQxMk9jNGg0bnVNR3QvYkRoeEhwY0cvYVpn +d1cvNm1VZ1psS1B0MU42cXdiNTlSNXY2bUJRdXJOUHZkYXJ4VnJ6N25MREs1OUxmRFY3THA1ZXdh +Qm16ODJyQzU4MGJrMzlGTTJjM3ZWT2plckpJWHV4MU1xZ2VMTi8vcHcvNTgvNWMvNmNQK2ZQK1hQ +K25EL3grMW44K2RJZ0FUVkJNWWhsUnBNQ3R2RUVJUml4K1UvU2dpTWRVUXBRbUQxRUxJNUx3WWl3 +RDlDUWZQbmNnWlJUTGtXMWFkYldnSjQ1SE53UnJDR0loRUZEeFZTSWhpTDAvZFZocmJjRDltcWUv +ZklaOE1vRnB4dzFKRUVoaXlqaFF4cHMvbE8yUFo1cExqRnFscnQ1UENKZWZIellyMzVZOGFWZW83 +WGlBbjg0WXdZNUNkWkZETWx4YTZ3WEI4OUVwcUhteFJQWCtqVDJEcnFud2Q4SGgzb1k4WDErdkVi +QWIzTTlCQTI0clhIQUZSZmgwSVJuc0RBTWpzUlh1MWdPVCthd0IrYm1Lcy9pdzg4dzF1aWJtbWhL +L3p4VEUxR0xTZEJ3MnVPN2ZuZ21GN3l3bTdlUFBneHIxUG5kRDMrUWVibm9FNmVlVzJ1dlhzbXJO +OUhpZmZZSGxOaDZxdzdyeGZETUd2VzBMdXRkNGZGcWhFczlMdHpVdk9iMFZON3lSSWU0RTBzdmsr +L1cvUHcwNkRPdXc5bjFVVS90bFgvK25EOE51SzJaUCtmUCtYUCtwS241Yy82Y1ArZFBheitpUDE5 +WmVPUzdNNVdmam9uTFhZRSsrNG1WSUJxUVFSbEpJUldQeE9ic1FSb1Rpb3Q0UlFOTTJBcG1UaURG +czk4aEFBeFNDRmNoTllQY2luZFl5Q2tIa3hIWGcrMzVYWGh4WEpvZ2I0ZTFpWFhQN0dGWW9yaVdo +a2pZTlZGZU9PVkVEcHp5eWFINTVtR3dCeDU4YVZUWDJrc1VTQlpiclF3clBnSEE5dUQ2a3JpZTRh +SDFpQ3RtRHh4dkVsemlhSzUraUEwUExzWERXZkpkUTNPOWlkWHdYRzdta1VzZkNFeHUrTjNWUTZn +ZDhnYm5XMS8wVWw2NDhBb1BmR0lUdXMvd2lXK3RtTDZMTHhmZTFPdE8wR3FGdmZIVWp6ODg0YzlG +QzBTUEQzdDhoMHVQN2JFV2JselNuT2NaVjd2MS92K1A0TE1XVjJKNUUyWlBjRit2OFZMOXdHQWVs +M2pHazRNY0Z2SE5PNHg2SUlxbjcvTEg1TWVEbVBaNURnZXQyT3VBZ3hkK3ZNZ3BSK3E1QzdldzRL +V21mZzYxaHpQeGNTV1grL3c1Zndibi9Ca3M4K2Y4MlhybXovbHovcHcvNVo0L1A0NC9Yd0lCQndq +aWtLczVBaUVlQUFNSTZ6VFByeWd3bURjSCtlbjJ3QUxnUDBFVHFhRW9wQ2dVWUd2RkoyYXg1RldV +WE40Z3lHdXRKaXZFWjRRRGlsakZLa1I4SnZOZFBFTVR4WmRIVEZlSGVLbm5pcmRQQTRuQ2NDREFy +MWJyeEpiRDRhS2VOZ2tHYTVEWUF5ek52bG8weGZjSzBHZE5VZzhjMXFmNWI2WXlQSmVMaU9Xb2dl +WFZJUFcwOGZKWUp6OU9yVkVyN0VTSUErczFOWFZkYm56YUp5L3h3UzIraHNPcGQvWTdZTlhYMFRj +WitvbExlOHkzRnZuaEs0N3lJbzVERUllRURKTjdEb3c3T093eEp3YnNPWnd2UndSOXVEMnZ5V3RJ +T2lOZW1OU2pOalZaNHpMdmJ0NWdWREg4NGFFdmNNbGxYbCtzbFZkZC9pNEVQQTRjUFBzWDZPRFJP +L01PVE56aVQ2MzJOYVphL0VGbUhwOTZJNDc5bnNPUE45L1ZwZS9xaElYV2NJazdlTzIzaG9ubHJV +RWRmQTRDbU9mUCtiTmovcHcvNTgvNWMvNmNQenZFU3ozejUvejVBZjM1RXNnQ0d4VU5oS1NhU2p6 +SVFpTFNFYVFJdjFPc0FSSVJJR0FLWVZETjBqZ3hmRWFFWWdCRWhqM3V3RFN2SWJhR0lBVmg4aXNJ +WUd2OWlnTERFeHd4d0lZMDJPUkRpZ1lSbmpvNjVMZFdYVXpCb0IzV01xMzE5b3FORkJqVmgwQVhj +Y0R3bm5EQ2pZbnU4bGtPK1BCanZib3FjSmZuT0VTK3VaajVPTU1OSE9vV2l4anhLVDloV09Pd3dJ +OTl2b3VucnZaSy9Sbk1lZkZkRGx4N3ZHMVRveHpXNmpjY3VJVkJMT3NOb29IVEduUHl5MTNCT1RE +azhKeUFZcVI3ampQcjFVS3NFZlN0VGR5M0EwT3ZjYXhPbWlISTUyQjUrdURnZ0FtMzF1UGRjNWk4 +SmRSbis2eXpWeHpyNFRERW9odmFsSnZlNE1hbC9scXJOdnZvQUhkNHc1UC9QeVZyY0Vxekx2UDZx +U1o4aWFrdStHQXlEdytjZWtJWDFzRVp6UjBmOE1PQUUzaThlZXE2ZWdRbkRnVFA5YXNIRzIzRFBI +L09uL1BuL0RsL3pwL3o1L3lKay9sei92d3MvbnhKd2hpQSttbGFBWUlwcHVhendUT0p6Uk55R3dr +ZzRNQUFJSmJHS1VnUmtpS1VFSkFvWGt4NjgvWmFJNzdZeU5kSUpwS0g2QlVzQnRLWUZybldXUytH +NWpHRU5URGtZRGx5bnZFbGErR0NnYkYvOXNmLzhwdWcxU2NlRXNVak5Qdk5JOGhlYStDRDFUeENh +emJpd0pzYVlGVTdMaHdxekdTUC9lWEJQbk93ZUU0VWNzaHZqWGs5d0I5anlPSHVJZ0xyOEdndDdq +WFZuYUFma3ozL24xRTRya2lKVnFPSkFCNFlZWkhYaFdmN0RkamtJa3BySTh6am9ZY044VlJZUkZl +OTZJMmFyTUdIM1BhYXQ1Nmc1VlN6UzN6MzFvcFRoeCtjOXVxblB0QUNRK1RYTDY0Mk9LeUJ1M3pD +YTZoZnJmb3JQcDdVWUQrT2FNS2U5cHI1NUxJZk4rYXN3NC80bmx0cmpkclU3b0taZm1IUkIzTjR0 +bC92cXhXOHVNT0JFMnNjSHRiM2tNZFJEdy9ycklsQnIxYXg4SXFuK1hQK05PYlArWFArbkQvbnov +bnpHZlBuL1BteC9mbXE4WkJvRTZJaXFndEMwQkh6RVFTb1FnaVJnUlRnc3BkNGtLVm8zeEdzc1Fv +VUI5Rkk4MXdPY3lYT3BVbWFudThIV0Y0QU5jNmM0aFZjTVloWHd0T2NFNWdDNGJiR0c1RU9NZjdt +VDM3em13REVmc1Q4L0JxRmVIQjJIcWx5eXMrUVlsb3ZybGc0MEJqa09uQmNjRGdjN0xXdm1Hc0NU +ZGNZTmVMQVp3MVdxN3c0RXJNRzZBR0JVLzJCQVVZMTRoeHV6M3pHSzdQb244RmszalNKYjQzREZE +YTF3R2U5WHZvVkQvWElhNCtoemg0RThyZE84Y1VqTkd0OEpsSmlWWnUzVU9MaHN1dlY4c1I5K3or +ZHZWcjF0MzNFUmMwcmhndEc5VnJ2TzI3ZzhCMXY4dE9YR3R6eCtMd2graG9zZXRQZlF4ZEhQampG +WWQ2YVFYMHd5QzhQVGRPVXd4b0dhOFN3VG4rdHpkdkE0ODBlMnRVTGNXa2VIM0NhVnc5OEx2bkZj +R0RpdzY5UGVFNGplUEpaYkZyV3EyOG12Mkc5M3MyZjgrZmpvL2x6L3B3LzU4LzVjLzZjUCtmUHor +SFBsOFFDTllFaUxQWVR1b0lWYmdDbG9ZcVJpRGcwUW9LQXVYMk1nUmhrdWxkY3lQUzVUWlZjUGlB +UndXREFLZFJkb3drRldRcTJSbXgzSkNMRzNYN05zVWF6Q05HY25CMklaMURQekhtRDRLZDRnMGdZ +U1AzMmFxSTdnNm10WW9EUFhlMmFadzN6YUo2MW1pV0c5VEJaejNUV2g3TnJBSDVLdnZVYUp5YmM1 +dkN0RjdDcGh4amx3QWtSMVpqMmxEY1k1YlVXdHgzaXlDRTJBY0dzZHIzeTNEeE0rQlZmditSMWw4 +TmErS3lWeHpNWERPcDFweFU5SkNJSHRyOE1McjQrMndjYkhoaWFpZFFBcXo3Z3hqbzVpVmUvaThk +ZSs0clh2QjduOERnKzRNSVhYdFZYZmNLT2I3cXdUdC9sWVc1eEhDSnlxd01uT0JBYjcvU2hwelJH +SCtMUXFEbmNpaFhOWHMzeTJldXd3NnNEWDExNndpTndpR1d2UzI3cjdYVjQwSnhldFFmMEozNjV4 +cTBobnpucnhKOC81OC81Yy82Y1ArZlArWFArN0pnLzUwOXhQcW8vODE5QWdTWUtDUWdBQ2NRR01J +QWg1OVlnRURsQVNRZ1U0dHRNQVlsUUVRcVFzTThyYU9Dc2NRSDdtT3o3TkV3Y0Y4RG1GQ2dXb3Zz +VHZ0L0QxeHg1NGZVR2dnQ1E0bGthYzNrN3JOVUFSQ0hlUDJPc0prMlhXeTNxc2tkek5aMUFyTUdC +bkRXY1JoQ094aUM0Ynppc3F5RkxzSG84ci9Ec1JicUJDd2NSd2VBSk5qRmo0SDk4L29saTRoRUxM +cGpVU1BUNDlGeDgzODJKYTE1TlJJOHphL0R1TUlYWk9oekphUjljM3VyQXlIUUdNYXNEM3F4L0V4 +bXN4YStITUxTSGhLOG5jdW1aMk9FalBYanc2RGx1VzY5NU1mSEs1SGhUUDczUm55R3YzcGszckJV +ZlYzb2l6cU9kTDVuSG0zcjhCV3hZbVI1T1BES0c1ejNjYUFsZXNUMzc2Yi83cGVoTy9oeW9oNGZl +MVdDL1hOYXF0MzBRQjU5WmUvMzAzUm9jV2srSDdpNjh1ZUJ6SU5iSTlxcFRiampVVnYyNHEySCtu +RC9uei9sei9wdy81OC81Yy82Y1B6K1RQMSthcDJDQkpiWkJjeVR3ay9UVCtPZi9YRmJqa094M3pw +R3VDQVVCSjdFbUlGNWpyQmNYV2NqM3ZQOTVsN2dCQU1nK3VSbmVkN0ZnSWtBRUtNUmVKQk9WdDFa +aU0yTUZUelJ3dyt3dVo4Unh6Vk1Edk41a2VJdmg4ek8rWkwrWUxzS1VGd1kxYXd3aGlWVU02bzNw +anRqd2N1Snoxd1QxMkIrVDNkQXNPTVBqWVZhTFdnbEpMR0lqSVBIVm9ubml3NFVQL01LSFE4MlR4 +NkZodmIzNENmN0RJZzllSGFUcWh0YzZJdFFmSEJDQnp6REtpdy9tMHRQbm9QdUZRZlhMZWpFaXp1 +dE43bGNUODV2elRCLzFwWEU5VjlzM0F4NGU1bmVJcWQ4ei9MaXJLVnE0T1BLNUNGSk9aaFFUSnpT +Sk03bWlxWnNQdHVORkhKamxvUzJITGZQMXNIU25EWi9GMW1mNTFFNEhzTUh0clJDVHdZQWJuT2lW +UERpbmlab1NaaHpnMzFvWXpPbmR3K1BYWU5SM05icjB5VldEVmw5eTQxVWN1cVVESEJ2aXcydk4v +RGwvR3ZQbi9KbWV6Si96NS95WjNQUG4vRGwvZmx4L3ZwRHA2ay95UHR2a3puQUsvTGJoQ2tXTW43 +WVZKNmsxZ0V1aWFPQU1nclVXcVFwM2I3T0FpNENQUUExRE1vTGtWWUJtSWdWQnhLWTVmbm9YeitG +Qm9FUlhNeUFHTnZ2a1I4b3p2dVR0QldPS3JYbitvbXlIZWgwNDlyaGJJNmVhSTliRDdEa01ucmRw +YW9hcE1UUUhmcHdSUUREZlhmUFVDSE1GUlh4dFlMRVNqemM1TWVpSmtRREMwZVZRRTF6NHhKazQ5 +c2pudXliRHFCZjJabHd2OFFlL21OYmdESWZ5K3R3M1kwUWh0a0hrTUZyam1RditpcE5ZOFVJTDZw +TlR6L0hLcUhEaEFFYnplcTNuRElRRG1IQW9oelg2SnFaOE1ES2xkZmpIaWQ2SlV5N1UwajVaanor +WURiajB4YUZEaStad3BEL0VqMGYxaWtmNGYvZVQzNDUrcWl1ZjRSTy9ScGJIZnJsd2JZM3ZES1lP +Ky9DVGcrYm16ZUZlZldycEgzYmxFbGV3NmEvMTV2Q0ZON210VDR3enVYVU9kdk91K1hQK25EL256 +L2x6L3B3LzU4OW56Si96NThmMjV3c1FvbE1vc2l6V0FFMzBoaUVHdmNackJoTW9RSE9UNVBiR2ZB +ZFNNWm9FdEVFY2ttb3NzRUJyRmlLQnE2aDlKbG81Z1dwZXBOZ2pqN1dlV1k5UU9PU05RWThRUW9q +Z0wzZEZva2dES2YxbmlNWDhxei80MVcrSER0TFVRbUQyaUdGLzN6aW95M09ma1ljblluVTRxTXVB +bjNqVWFxM1BEQXFiL2VyQmpmMDRFUnNXeHRBZ2ZKckRVVXgyaDRyOWJicDE0dG9EcjFqMnhTUTNi +NjluY0QzalMvclZ3OFNjQ3g3OUlkVHlpRk94d3FPZFY2YzUvT0llSGpYZ1h4M3V6SkxjVnljTy9I +OW00WTh4MUljL2VhMkplYTQvN3JoVkk1UEFJQjQ5cUttSEVFRmFaNzJhSGF4d0VHMDFBeSt1OFNG +R2Nyd2RUT0k3Z09FdzM0T0w0ZXlWdzE0Nml3a3ZwcHpSN2R1aGFhNzdZSlViUG10OXBoMjF3MjJk +UEdyRmhYbjk5WmsrOVFaZVBuSGh5QjRleUI4QXg2MmM4TnJqVHU5aXdLa1A4K2Y4T1gvT24vUG4v +RGwvenAvejUvd0poL242N0NQN00vOC9vQUlSdWNRSVZneGlORkJEa0t5SkNKREFjOTlkOWdQcnJ1 +a0NHOWI1S1IrSjdnRFVvUFo1VGtRK3g0UW5EQURsUlI0eXhXVmVoVENTR040YStQeitEUkZ4V3VO +U2kvMXRtclYrUjU0NVliSy9jM0pwZ3ZYdTZrTW9qRXlDZUFaQmZFeHlOYmJoR29OSWJ3czhRN1Nh +SEhMZHF3YlAzT0hTZkFlSFdPS3FIMmFOd0p1MWhyZ002cksyKzhXMHpqT2Y1U1Vlc1FqQVBybVpW +QzY5VkRkczVoMVdoQ01Qc2ZoUDV2YlgzT3JHZWZzc3ZqejBvRGRpdEZldzZZZS9vQzBPa1Zkcytv +SmJXTVNzNGVoQVBuWGI2N25ZTU9GVFh2WFJuUFYrbGFDNndYTVBRajNTQzdub05vZlNYZDRRTWJV +ZWxGdWZQY09mdmVxQ0RXZk1wdS85RlFYNDVNV2Qybkduem04bU81UENoaSs0eEtONThlUXhyMlk0 +OVRTYVBJdzRGY2RoNWpsTkcrYmthQzB3cU5Qb1lXSnUvcHcvamZsei9wdy81OC81Yy82Y1ArZFAz +SDUwZjc3OGhDd0owUXBpc2FZSWpqQmdrSXhJNUNxVVFRVURTR0FDZG1jYWF3d0VhUnlSeUdFL29o +RXFUNXZvczZMc2R3RldZUldzdFl4R1dFUkdDR0krZ3Y4KzVIUi9CZG1oQm04TkVHUE8yNHlNcXhm +SmhOR0dxVXN6Q01laHBEN1AxS2R1ellWRmpUSFRyYkZYbzlVQmR3Nk5peTB1RHVWdzk3d21zZzc1 +WWhBSVRKNTVzeEpvMTBCQ0VMZlAzV3RRc1FrSVBualZacTNZSFo2TGI3MGNQaE84dzBYdjlJcWdD +UkovaHJwOXhsOFBJL2hoeDYzdkRBcUhlbWxEVExHSUc3L3lXa2VVNHVraFhIQllwdzd6OUtIdjhz +RUVCNjdWYXQ1M3VIRW1Udm13RCtmdStnOVB4dlVDRDM1OUJtWTFpQ0VIZlh0TDJFTlJMMmxURGZR +Qmx6eTA1eElYZnV2a2haM3A4QkFESHUvcHczRUVMd3hxdEFaRytNVVVDKzh4NkdtSURtbW5md0NZ +aDlrYWw3elc2VC84OE02ZjgrZjhPWC9Pbi9PbmRlb3dQMy9PbjhiOE9YOStaSCsrMmtnUEVhNzV6 +T1N5U0VJL2ZVdUNHQ1QrL0g2aVJ5encxa1NFMTBCRSsyNFFwWFgyZUh0UWNXcVltT0xKSlk0R1ZN +Z3VUUlFMTHNRajFYZUVlck9oS0xGclVER3o5dzBEb2NCc2FGTEZvMkJ2aU5ScXlPbFpENWdLak1I +a2hWRUR4VGVuS1RDNWE1WjZpTjJWR0ljQmI0Ym0rU3lHUGVxdndNMXB2dWFxQXovV3hkdzNOQTlI +K0hVb3dLYlc5eGdiVHh3WXJVMWRzTi9BSVZ6cWgxY2NRcFZQSE9MWEt4aWVIbitmdzlObmZYbC9V +TGN1Zk9GQ1BUNFRKT3plRXRFRlBQaE03bHZ2UU81aEo3OCt5S3QvY3JUdm5oT3VROWRldGNpdGwz +cVBNenB4MlFlejUzREo4WXd2NGVLN0gvMTY1dFdNTTV3d3JiZUU1ZGh6OC9EanhBSFp3emUxMzRX +N2FPUHQ0SVVwR3I1NGN1TzFCdlFjUnZVem44K3dpMk5PSHMvODRTSVBubzMydnp5TG1UNmVydktI +NFYzejUvdzVmODZmcVczK25EL256L2x6L3B3L1A0ay9YNUloM1NZQU5jOHpSUWhtc1ZIQkFWVlJB +eFRoWEhDSnZIRXdid0N1R1dKcENCQ0Vwd0JFQVdjdElwQnIzbHJpSTBLWE5RWmNmdUtXVStFSzFV +eDUvU2QvY1IwRThDSWVvWTlCdjM3TFh6RVNnK2VHV29rN2g4TjlScENZTUJvT0xNTERqN3U0Y3Jq +THAxbkk5MHdqMUNLVzV6NWJnd2ZyOGVlNzJOWWozelA4K1UvWDZzWUxnOW12VVFRSlA1N2FGM1hZ +ajYvaVZyUFBHUXg2bDM2b1Z3NmZ4U2RHOWNPam5oNFNSR2dRTzRQaVBqVmRiZXI0WnNpcmtVNGl5 +di83LzByL3ZXbng5Z1V1ODNvT203M3dpNm1lSHBUaXlnMlBHdHRibXJLM2VIQ2xiL0tMby9mV3d3 +NDNMdVNwUGcyeG1SRmYxanphK2hweitqVUVYSWlQTDdIaGt0dGJMbnZrMEFOWXhMWk9UckZvUUwz +cVZ5ZnowV0o3WTE3TjdaMFk5bG5iZmZMb04xMFovQlFkMzM2YzBnUWpHN0RpUzUzejUveHB6Si96 +NS93NWY4NmY4K2Y4T1g4KzJ2clkvbnlaSUVvL3JkdllScnczS0FNSUFvRG5BbGxySHBFS0lYVEpC +RFk4OTh6ZGVuZk5CMTd4Y3FhUm1uNU5SSXdjR3FsNVlvcmZOMFNhaDNERWVNdERHRERBcDZGd3lT +R3Vtam8wQkE0Q2dzTm5NUTE3a0V3azloQW13cC81cjRrTER6SGFCMDhGSXBmUm5CcGtYak1ZQkZk +d3krKzVPRFdST3N6NXJuWUdnaTk3amRzdk4yN0V4Z2RlY1NRL251eDNOd2ZQTjRNNm1HNi81N0F4 +ZzBaYmd6TW1WUXN1UE1Objkrb3pJYlVIZUlIVmZENmZvT0NxRmx4NjhaZS8veXZoelRwOHFnTWZ4 +RWF3K0xSSEQ5V2pWdXNkVm5USEpBeFR6T3JHRzE1d0Q1TmV5NDBIMk5VZkE3NzFVcjl3bzFieGF5 +NzhpdDAzaTNxaHozS2F4NGw0OE1JdnZyaHFwUS9QY1FnSFhBNExkN3pSZTdVQW96azFpZU5YYW1B +cGQ1N2pYdjN5OXUwWTA4SXFuNWppMExUUDZwOC81OC91blQvbnovbHovcHcvNTgrTytYUCsvTWor +ekErZ0VlV05BTHdKQVV1RUFwRXNpTTFFWXM1UDA0RGJneXpCZ0VJeWtTQkM4V0lqeGo3N0ZheFpD +dE1FVGZaY1RvUVFSOHgwOHdoSFBBSmdLUzdrRW9hWUJtSVUzaUxoZW0vQ3hrU1lDd0dHMnVHV3h6 +cjcvR1NmY1NLSHkxd1BvNG8waEJMSERmZXVDWVk4LzVMdjFzUHVtZmlhckI3ZnplV0F1dnJWU3Rp +TmFSQzU5ZVZGWGZEaUY5L0JjdXZiSjZMNU5xNTJlOHpyZ1F0bXRYcUxoNy9HVkZmek1paFRtY01a +REREcXNYcndiWTM4UHJ2ODUzMXZaZURRVnpsZ1l5YTl4cUZMcitHVWo3RHhRbGNPYlp5clgyM1cy +UStiSHVOYlRxSjIwVk43aGcvQzc5Qmpidy9oVnkrY2N2Z253RjNXdTJBV0F6NS9nVHNHdkQ1RXE0 +ZERiSno0TEdjOTBVTkpYSWVjT21ER2d6cjBpL0Y4eDdPNjlJVlg5S1IvZ0ZrTHR6a0hoM1hxbEJk +djV2QUVEN3p6NS95WmJmUG4vRGwvNXJ1NStYUCtuRC9uei9uejQvcnpSZHhJSVJ4TlVLUUpkOFY0 +cm1rQUtjaXpHdFF6UkdpZ2hoSy9Zb2hiTWo4UmkyVU9lSEdBUXlDQytseFIxb3NycjMzSThkM1FU +RVFveUY0RitkMXNnakRnc3NZOFBJaW9RY1ZEbHZqMm1hdEJrV0FPNXN5ZGNMdlhHa1RKSVg0RktZ +ZjF6R2cwTjF4STlsbU4rTUVua2RlODlvcXJ3UzdHRkIvbWlpVGorTU5MTDJ2bGhGY2Q0aENRNzU2 +TFEwRFBlSDVsUTF4cjFFT1E0cnU4a2VpYkdvWVJyN1hvQTl4TUtTN2MrcTAyUWxXYmUydUZnL245 +am56TkZiRmRYa0xEZzU0emFQVWhubnB4S1o4NU9uTDEwS1pKT2NTR0FSOTRGcE1tMUpINkRrTjdh +ZmlEd0s5NzVOQzcydWlJUWYzZVBCNzhZYU1HY2NXWEd6YzBnT05pN0lHSEovblZsc1AwZWxGak9R +alVpaWZQeGJLMmVxQWxHdGNqdllHamZxazI3WVBYT3ZuMUFyL3RuenJuei9sei9wdy9ZWmsvNTgv +NWMvNmNQK2ZQeitMUEZ6SWx0RWx3Ly9rV1NSWlVNSWdtZEFFWlRrTTFzbWF5dGthd0Z4aEZJY0o2 +VFpGRFBPVExpWXdVZDg4MTBUcmZHN041ak81M0VHaWluNm9SQzROaTRGWWdmQnFJaUE1RzkrWktn +N3h4SUtZTytXQVdYMU9JMTNkY2FDWUJhYVQ0U0lZbjRyMGNCR2JBZ0ZUTmdzMW5lOTFUODEyRXBW +SE1hcWpEYzNzMFJTUGd3d1B1REhIVll5MmNHaXMzUEQwcytsME05VE1uTWVQRWM4L1U1VERyZ2Vo +dGp2WDJtaU1tOVJ2cTFTUDcxTmlEeFhwNDFRQ1g5VG1zRHA5ZStSMTA1cUNESERxM1IvMHg1M0VC +ajl3Ui91MkJUVjd6QksvbjlxbFRibnpURVpIREVNMmNHYzNSbGZVMXpLRU9kb04yOWJDY2lBa3Z2 +ZUE0WEw3cHc5MStPWm9meDB3cGgxcnRzWjhXNGVnaG1IM1hMelhJNHpuTWNQWndzVWNjRjl3TVRG +dHkrMHhqbnYvSFAvMVhlWVl6T2ZGRkE4MC9mODZmOCtmOEdhM09uOWt6Zjg2ZjgrY3o1cy81OHlQ +Nzg5VUdhQ3d3RmpNUUFseUN1WUFIeUVaTkpMWTJQd0svQWdudUtlSnIxak1HMGoxejJhY3A3aVVh +OFpva3A2TEU2SnVFQ2tmanhVT01SdnZQellSQk5KclBBSW9sbW04R3ZYcmdFSTk0NVB1N24veDIz +aFowRUlHR2lHK2YvMXp0dTZZVEsxeHF4b3ZtNGdwZWE5c3NEVEVIcTcwYWFUOU1hbkpwSGh3eDll +R0NXeFBWaVUrMW1TZnFDTzZhSjI3enFobFhjbHFyRnMrdGtSZHY2djgyYnIrNExudTl6V0JRZS8x +VHlXcUNTODlhbCtGdGk5N1FRL3NtUHF6V3VhdUxMdXozVEZ5SG5vUFFkOXpVb1BEcHQ0dll4SExZ +eXgvUm5oak53V0dQR0RCNnBpZU1KU2NjaVgyWTlOSTZlZUQ0TDk0UTNYTzF1dXV0SExqMmw4anph +eFMzSDFmTWhSY1lmUlliQm55Snowamk2d2srN0xOV3pUUUJuMTdMYjUzOWVseGZXQzllUDhPaGJu +OUFPQkNzTS9EQlArbkg2UXJ1K2cxK0I4NzhPWC9Pbi9Pbnl6NjF1YytmOCtmOE9YK0tOMy9Pbngv +Vm4va3ZvQlo2S0lsRlNDRUNHd0VpT3NRaHpVWkZBMld0dDBEMmE0Qm1SWVFuRU91QkFWUWM2eldY +b1JpdFJKZ2pTdnVJRUNrTUprOE9qamV6ZTZiaDd0NE95YTBvODRUc3MxcGdSTDdubXNjRXpHejlI +LzJiL3pibWhzR1F3enpjOHByem1ZbnM5Wk02ckhDTFcwSERuRG9QTjg2YVg3TTF4YkJQSTNJZFgy +cDdHdk1sTVFoU1BCZzBHWDhhMzd3RUVWRmNYT3ZrMTJTaThObHpPV0RCazlwajdodGkxR1RxOHRZ +RUJ4cXVSaUtWMHpOckhyNk01MkF4THdkamlFOFBzSHNHTzh6bFFNeWYvZEZ2NUs1MmF6Mm5KL2dx +Nk9JSkgvZmNldlhSQ3EzWlIwdDRZbHd4aUZkTzMxME1aQi90d0V4Mzd3MXFyem5jMmdlblhvdERp +ekRoanhiZzhkMnZDRmd2TGh4eTZJVTZjYWUzYXNVOUg2akZQdkdzazhPRnQ1ajBlaTIybnFtWGR1 +M3p2RytEZXJqTDZ4RGlBN21iRDJiZjdaOC81OC81Yy82MGZ2NmNQK2ZQK1hQK25EL3RnL09qKy9P +bEVNMUNnRW5nRmFGSk1jQVJLSUZuUUZzck1ISzlJV3FUN1NOUTZ3Z2NlVWdFWGh4RlZ1ekVwTGlZ +akxBdWg1K2lrWVNnSEFhWFE5TU1lUlFoSmdQSkE1OVlCQUMzb3MzTEI4OHp2bVk5alBiLytOLytE +OS9JZ1ZFdUdNV0h4K1Y3bW43ekVka1JxeW13SUZBZDh1REVPdk9FNlJraGhMTWIzYWRwYXBIREhw +aGcwRVQ1UExkWFBmTFpJN2VZRHFRWTlOYmdDYmNhcDBZeDlhTzh3V1cvVVRGYkJ6T2VDUU12WXVG +WGZuZ3JoR2Q4alJCOWg1R1p4SVZONzJEQnRidyt1K1BMcnlqb2lYeSsxNkE1eE41NGhFY2N0YXFM +am5BalgzcDIzOVdoTG4xVnE5NnBUMDR4N0xlbU5ZaFZneEsxTnpCcU1zOGc3bUtwWDAzZStua2Vm +WjN1eGZWR0NUYnJ4R2J5OEh2ejFsa2pyNzM2Ui9lZXFkZGhnQ2RjMEdGTkxBWURxOFZldmNPVnc2 +QTZOdFRvTGFvRHRMN0tJWDZjdGRiNWMvNTh6OWY4T1gvT24vUG4vRGwvenAvejUwZjM1MHVEYkZD +Z0RRSWdOb0hmRWhNYmdYcG12WVRBQ09BLzFTcWFBQkNpTUc4YUtoeXhGV1cvNUM2ZkZhY0lzZVMy +SGpIbVhKNm53V2RldUdMbXcwTllDSkJMTEExQ2lyWElVcVM4SGRiMWpkTGYvL1IzdnYrTC8vQXZR +b0M0c0lublRsaUlVcDg1TmFoZFRxYXBVTjNoMDFDaWdTMkNQYnk0c0Q1dmxvNHpIREVMVE9JU3Vi +am01Rk1IckpwdHJ6dzlITVNNUVM4dWZzWHd6RjZZN2ZlWmtRbEozdUFPOXVjdnRsc2ZycTkyaDRF +Yzl1bFJSSFI1NVJmUHlDRjlkZm11SC9iN1ROeWUxeXlkYzJlaS9scUFtRGlDUiszNnF2ZjRrTE1t +dFZjOTZlL3hBWlA5ZWtVRCtvbFhjVnMzZytnVG5jSHRzelVQcDk4bkQzTTZrT0NRaStpdDg5eG5O +YVR1MDUxY0RnZ0drUU5lMkZ6ZUdxb2g4VTlqK01lTjcyTGcxaHJjNGo4MU9xd1BpOC9tYUVsUDVM +R09OcjJkZzhWM2ZkSmJmdk5tREFaeHJjTVh2ZkRJL0RsL2ltZk1uL1BuL0RsLzJxdWUrWFArdEc3 +K25EOC9xai96ZDBDUko3QmtObWk4WnkxZUlJczFTcUZwMUFIMVhZUE5JOFplbncyTkpRaUVlb1pr +aEJDZDJPTFpBeEFqaUlrY3hKb0hWb0dhN0xuR3lVZG9QcnVzRVJONW1pMkg1Nzh3NkpkOFJvcWZ6 +djBuWW5jRERrUWhSMHdtL29QLytiL0puVUZpbENNUkZ5NDF3T1V1TDhIQTVydkd5YStwNHRvTGV6 +NGYyZGFZSXp5WXJMV0hNT0dGQVU2R04yK1BXdkR2M2tPUk9mQXVOcjdFVUlQdk1EM0Qvd254ODA5 +NzI0TlB1ZlhHbXpLODY0MmFtN2NHVlM4ZGVJWnJ1ZTBuYXJsaGhhbjU1QmZIbXhucm0wOWRCSzhP +TmZsc1BhMjFGdC9wVFU3OWhkRmRQWGdUMjRGcXZYM3Fob2YrSEFydFJUWEtTSERRRlB3d3dDSWVR +NnNiLy9hcFIxeTR6RmtIQ3lPS0Q0ZDVtUFRaM2VFcEg3M2hDNTlxc0o0KzRLTmwvUFhBRllQKzFZ +TXorbU5lZGVpUlp3NElmY0F0RHNQSnpjbVJPdWZQK1hQK25EOFAwL3c1ZjdZVzMrZlArZFBuK1hQ +Ky9LaitmRWxTSUVBSUlLR2ZyQlZ0K093bmVNVmFHMUEzWjYvQ2ljV2RFWWpQVUNSd3dDSUZLSVMw +V1g1eXRrWlJET2FaWnJ1UXJEaDVOVm5Pa1BGMklHaVlndVNDbThFUkl4YXlyVEVZQlc1dkdoREtm +SC8rZTc5OE0xOWlRbXY5ZmpjY3lFZVVPc1F6YngwQjVNQzZuRERBcEJhNGlFdE96d2hLUExVWXlO +Y1lKaTVQUkc3QWpUK1l4RlV6am9oWmJsY1BoWXBTclBiRjViTTRzT05GbkdjOGZ6ZEFnOXQwKzcw +VjhkYkNIYmQ0SVE2aXRLNERYcy9VUW5UdEMyM0lxd1k5VksrK016ME9HWUlCUEJjUER6aHlXVTg3 +K2lLL21PSDFMalhENm9MVDN2NWg0WTJLNXpqR2c4L3FoZDA2dWZCZ2lPVXZZK09adHVEU0F4ejV6 +RERXNDlTejlvZG0xUVVUN2FzVkRqbnBEaGE5U1UrdVo3N2pSN3p3KzdZV0wyckJnWHhpd1VITE5D +cUdmeVFBVC9JV016NTRSRzNpUG9mNDkxa0hHMXp6NS96Wk1YL09uL2JObi9Qbi9EbC96cC96NTBm +MjV3dHhMbUxVQkJ0OHQrRDVhZlVSVzBueEhDakVBTWFFQ2tRK2NoVGZuM0FWYXgrZ2lORW9qUk5m +RExFcWRnS3pYeng3Q0V0OFEzSDJpRWxjbWlUZjA1eTN2eWo5SmlhNTdNODRrMW1ITUxsK2ZqOXgr +MWZDQ05pd2xnZ3FXbThPckJOUERTNFkxRVN3ZUlCYkxmS2xhVWR5RzIrTnZlTGpUa09MQ3liTk5k +ZVlES09tSGlEaTRjSTZYT0ZWYkZ6SXpSQXV6L0JsdnIyUy8vM0FrZGptZlNZby93cWFuSVFBVDBY +Mm1QdnFQYjcwcHdZbHBCcFVQZW43MWN2RWFuQVJvcmR1OXNBbWxweTBwQjdpSkY0WThPOVFnRXZm +MVprRDRmcGd2NHNwOEthM05ZLzluc0VNaS9VMElWYzEyZ05QYm4zRUthencrNDRmTWVXMkYzWm02 +Q0VncGxyeWg4M2xzZDVlOFdIQWpmMzIwQ251NE5VWHo5U3J0elc5V1BLb0I2ZjQ5TVpMTDlXamJ2 +V3JoejQ5cDhGSDA2Zk5pKzBQRlBXNDVzLzVjLzZjUCtmUCtST3UrWFArbkQvbno0L3V6NWZtYWhK +aG1QVFRLb0NTS05EdzNFKzZKUjhvY3dBZ3ZDUklaazR4MW1vb2cyZ01BalhMVDlGeW12K0ZvWisv +c09wNWhXUHYrd1BDdkNJMVdpRUkwR1RDVW16RmhHZ205MXhOL1E2YkdvaTBBMmJrdUl2Wk4xT0U1 +ZTJQd1NpYWlVRDV6R3NpY2hFUHYxcFNML0xQa0RXb09kemh5ajZ4N0ZPUEp2bFA2c1RoelJTKzVa +UmJERWJITDZIZ1Fxd2FvSHcwUndYNEhDclBxRUZoVWhjaDlwK1RGazlPWmhXUHVBeDgrV3d2VGNB +dGw4OUVhSzArcXJzWThNY011QkhYUlNQNnJCNTM5ZUJRTC9UY1h1dkZVcTk4YW5CM3Fjc0ZvNXJ0 +MTJON0NWZjhhcUdDdGw2TjZjWHhSdHp3MHhSajJPZTdlUGJCZzJPOHc2eEcycmVtNXNLcHl4eWNq +RXBuc01KR1YrTEFJRmIrQUxwNmVVSU9PQm5VRU1NYnJCeEFsME05THYyQjFmcHcvbGFQM3Fsbi9w +dy81OC81Yy82Y1ArZlArWFArZkFhZTVzK1A3OCtYUkJKYnJFZ0JOTkZQL2NSa0FCRlRIV2dia1d1 +TzZQd3p1NTVKNENmMHgzQmYwenlOaU9tT1dBVm9tTHUxMWpHdjJBYlE0aXNlVVpxSlRHWVhLK0o4 +eTg5c3hPVzV3UkFWTVB3T2hJN3VJVkxFLytSMy8xbldHM0pyRUdLWUcxWjVDZlVSKzVjWVNSNkNR +S2JMWjNVNGNNUzNScFBWRTNNZlpwekduSGZKYjU4OXhHM09ucjdSMElPK0laS1hjTlVUTTc0SnBL +SzIxMmZZM1gzSHA3dWh0alNlV1M0bnJoeDREb09mL2ZHL0RMYzRsby9BNUk5QkQ3TTdrZG1yUnZG +eGFnMXRlQVpiRHkyY2Vic21ObzdvQVI4dzQ2YUdWNWRuNHRoRDVHSS8rdnFhZkREaHlmVmZHeFJ2 +MXVKWFRYSXduTzlpR2ZLcnJmMDJoM3UxMDUvY0RobDNQWWFmOFIwd01ObHZyZjI0VWFmYzVseHF3 +VDhzbnVPVjFxd1hxNzJGaDNmTTY1ODE2dGREWHRFcm5PUVBxMXV2Ym4yZ3o4UzdQUWFjL2pDWlAr +ZlArWFAraEgvK25EL3RtVC9uejQ3dW1UL256NC9venhjeklrOWpGWVE0aXdnWEtaNmJKM29OYUxF +QUU3UzNBeG9IdERtRkdSVzl0UzNZSUV3NXhMUmZUb0p5dDE1dWVUU2l4Q0FjcVJycHMzaElFRU9E +L1ZSZVVWdnozcUJ3ZEM4Uy92b1BmKzF0eHZpYVJtdXNmSnFxWVJyWGVVM1ZHUHV0RWM5aFFxUUk5 +bHp0c0NCWWZmQm91RG8xUjBQc2ZjVDBKWjg5a3hjL1JJNUQ4UXg3NWNTRno4em5UZ2llNFlXQTNl +MFQ3K0hpYmR4Qm1PZUhqUUQwaGRqOWpyWmMrRkVybnZWQUxmb01xMTdZaTJkOHFnRkdlS3pGQnpH +V2x4eVdaMUIzOC9ZUnJmcndVT0hDQ2cvOGRJWEQ4SHhZelJHcFhzQWtyNHUyNUJiTCt1S0ZqOUNM +M2RBWDY4Vnd3U0czMmx1dldMRGhTcTFxYUE0YWNhbUpCdWpSV25sZC9RTU1Yamp3S0Njc25vdFhE +OUVtVGhwWHZkWjZjeFQ5bkZhdGQ4RUpJMjcwV1R5Nm95bC9vTXlmOCtmOE9YL09uL01uUFBQbi9E +bC96cDl3ZkFaLzVsZHdKUUFBVVJwQ1pJcFNnQ0JBRTVOZ0Vrb01xRFhBQ0N5eE9jUWFoR0dkV0lv +UncwakJCNUNZNWZaWjdvcFBMT1FqR1VHS0JyNkNVb0E1NEpHUjhZLy9PVmhoTnllZklnM2lSYmdE +UjR6dmZ2aUQxR1NJcnk1dkJ1eXJrTVhLdUxqd3lkbm11bnZHYkFUZ21Yblk4YUVaY3VQQnZOcUlT +RjN1M2d6Z3lweThlQ0pPTlpzekNFZ3RUT25TRi96aHlGNDVZWFNacjZqZkQ4L0V4TE82MU9rTmhW +d3VuTUx0Z0dZQXVmUFc0a3psSUxNbmVqZ3MxcW9aVmpuMTFIYzU4WXN6K2RSb0RTN0Q4V2xFVE91 +THZZZUplaGhVZjMzR25YM2k2S3NMWGhnclp2RnBETmR5K3g3T3JrKzBoRS8xcW8vQTFVSW5EblE4 +ZVpZK1g4MDk4THhwdEZkZnJZTkY3VDJZWVBlNVBsQVRYcTNGajNqcTBVODQzSEZOYStwMXR4ZUhj +c0V0cHJycHdacjJYeHc1SGVJK3d6Ri96cC96NS95WjJtN01uL09ubU9xWlArZlArWFArL01qK2ZB +bFdBRUFySG5CRWFRWUNGT1lOUUl1ejNuT05aQWFrVzZNNUFrdHVQME16cEQzV2VLNEFCNEsxQ0Na +MElwWWJTTS9jNVhyQWZrMHNoY29CbjNoSXRmWVpiLythMTRuR1h2T0dmREJvbXQ5UDlsTzcvMHp2 +dVlFY3Y0ZmNKakd2bitJSnRrTk0rZUczWGp5OE1DSU81RVUwMHhDcXorcUJUUjV2dVBBRnR6Y2x1 +SzNaTkJrUHhBbXpIcWdYSitiRnhxZFlHb3VQSEZBblVnSVdCMis0RXA5NDdKZGYwL1hIV2lKMXFV +MHVCeFpNYW9FQlpoem1RTHFjUFlUbHJJRHdZTDNlRVpoYXhNY2JEY0Jvem5yY3BQYTdtSVFSeGRJ +YnVXRldvNWl3dWhPakd2QVlFZDhlaDcvbk9EVE00d3NPbjlVdnZvR0RtZ0plZC8zNSt6Ly9YMkpj +Wm1LUzZvT1dhTkhCSGIxZHpkNFdpWXNqL1B1czF1anN1TzBmWkxpeFJndzU0WWZYR2pIOXpqdS8y +Q2UzR0hLb0IrOWl1T2lvaHlTTjRpWCt1WHJGVk1mOE9YL09uL1BuL0RsLzZzMzhPWCthTitTRFlm +NmNQeitxUDE4bVV1Z1JUb2lBV2tpUUFpTmJFMnBRZDBFVUR3Q2drdHZqczZJTWNaRk5TUGFseUJN +QW9oaEFRWWhHaE9meWxYVDNDUFQyYXJJaTVHakJESVZzenpQZXlDTXU1R25pTTU3L3Z5RFBDSkZC +L1dmNlI4alBQeGVOQlBFUW9rRUlmNXIrdkdHQ3cvNmFFMGVlNGNzNmRXb0tMTEF4bWRyc3daRkJq +UERLcXlZWHZnaGJrMkVqZGswekdLWUhqcHJGMGt4eDdKUFRkODNHRjB5ZXgyVCs5L0l5QXM3ZzFq +TWljQmo1dld1NVlEQ250Mkk5aDhQekw2N3BtN3l3d2Mwa2F2Sk1UcGNjRlRiejZ6RXMrSzV4SG1N +OWY4L0FXdnlwSlhQWFYzblZxbmU0YUk5ajBOdVR2MXg5OFdIRGpYbFk0SExSQ2J4eWVBNEg3TFFF +aDdoNnk0UjBqRlB4OGVxelo4d2tGeTdvUTF4OVZBTzg1cUl6aDlkeEJnZE82RVFPZDl5NGVnRDcv +ME1TUXk2OWdWR1AvU1Y1ZWEzaEFmbnhVWC9vWVE4Qmwzcm16L2x6L3B3L3JaMC81MCsxekovejV6 +UG16L256WS92ekpabkVKaVFEVkJEZ0JOQUlheFJ0RGhHQVNhb0l6VWVLejBEVU5KNkxvV0RFQWtX +Z3pPMVNPSUkxMjVERFo4MEppVytZRkNJT0ljbnZFRUNxZFlscEVQNWhocUdpNjVCZlhZb2xVT1E5 +d25tTXc4emlJY3JiTXVMMXZDT0NoUG51MW9pbFJ2Vzd2QldDblpCeU1Gd2p4YmNIZjc2ckEzOEdj +VlRFUGV4Z2s5dGFRODFxTXljMmZnbkFjNXpoVTJ3eFhPMGhzVHRZOUF3dVdBbWsvZk9NWVBFamhn +dGZ1Q3h1SXZGY3p0YnRzelh1OE91ZDlZU2xIM2d4RHg4OCtwVkRMZ2ZoODM5STYxQ0JRVC8weWxx +OE1KL1BEaER6NnBERE13Y21ycDhEOC9tTHkvTEJSU05pdG1iY3UyQmlOUHdST1Y1OXAxc0hnbnB6 +Y05MTTVjR0hXR3JERDB6aVdJc25Xb0FmSGpIbHd5ZHVlMWppUXgzNHM1L09IQXB3NGxMdnJQY3JG +MkswWmpqd1FUczBBUnYrSEY2d3E3VzlGVmNNYStiUCtWUE8xdTN6L0RsLytqeC96cC96NS94cHpK +L3o1ejkxZjc2OFNSQUFDSUtSRk5sQUNHQ2g1d29rRGovNUtrd1NRYzBKQ2lSeFcyTjQ4NkpSU0hX +M1ZtSjdrYTRSQ2dpZ04wRmJneXpyNWZkZEhzQVI3NWw0L3JNejh1dzF4SVVaZGdVanJXOUxmSWZk +R3dnRis4L0VZaHF3d094Z01hOHhHaHJCdmcyNHhCQWJEakhnWXhDNDRVQytPRERpeTNQWUNERUNQ +MDRZeFdqOWNzQkpHUEFSeW1QUTUvK3Z5cno2NWNLSncwQ2ZpRmpUZmRkc2EvQWluMzJHZFFSU0h1 +V29RZFduVG9ZZ0poajFvcjJHdHpYRDNidjZ4WUNyaHdGaDBRTmV6RE1TSE5iak9GeGNiK0NCUlox +aW1JY2ZGN2pDWmZrVEd5NnhjS0xmekdLSURYOXg0YjY5eEVrT3k0dVAxOFQ0Ly82Zm1GTnZ2VzND +bFQ3UUxMM0thNC92ZUtJRHRkY1QraVMzWi9qQmQ1L3J1WGd1ejlTcWQzZ1JzMzZCUjEvdzdRMVJl +RHBjMXNzdlpnOU0yS0xwdDhQU3MvbHovcHcvNTA5cjU4LzVVd3p6OCtmOGFmZytmODZmSDlXZkx3 +VWdRMEZJc2dFZ2dRVVNYQk5TMUFHeFJtQkFnREFIcEVZRFRReUFNaWh5RllONGF3ejNOc2ljdkFC +cEZ0STEyL1AzQmlRYSs0Z0RVV0xMQmVzenZxU3g5cmR4ajlqUEVJZExmdmpoVWE4WWh2ZzFGd0kx +eWRzYXNUckV3d1ZjRlprYThTS09tTjR3K2YxendrZThxMEoyeHhleWtlNVNTM0ZxR0F6eTQ5dUll +Ryt2U3krc2tRLy91SU9Gd05RR2k4YWE4NmJFTUVmSTZoRlgvUTRXR09HSFdXN1l2QUdSN3hsZnZv +bGZEbGNNZXhjTW50T0tldXlUbC9oNytPRlRuZXFyT2EyRkJ4YVlXck00WWpDWWZiRGgzbWQ5OVp4 +UjRCRUQxL0F5clJ6VzZjZGpqT2N2NDdkbW1qS1BFOFpVTTB3MDVtNGRESHJoclkzY1l1S0pJZVVR +UTcyNFVYOTdDcGRjY09rTnJISlpveVkxaTArallqaGMxUWtEbllpSmo3d3R2VDFxRTh0aGgwODlm +V3I2bXIzejUvdzVmODZmMWhqdThNeWY4K2Y4T1gvT24vUG5SL2JueTRUaWdkZE1vcEJRNHkwMEJ6 +d1IrMnk5SWlTWDBIZkYrZzY0dllha2dDakFmc1VZN29nVm8wVVNodS9XSXRBeklnSVdVSVdJNzds +MUZaeThIVDUzbnNHUmI0aXBjT3RoOVR2aTZqREVaU3JtZFhmWTlPMUFCenhFaFEvMTVmUGh4cGRE +Z0xuRUZEdDVyMEVWbmNiMGtJTWhCeEhqM1Y1NHZjbkFrZHpQVzYwejJEWE9ldnRoRDBlSDB6cTR5 +cEY1L0dpNDJIS2xzYmZmV3Jod3FHNnhjZVlOaUFOS0xmYXBSMnpDZThhWHJDVU1lZHRYYThXWDI4 +R3NQaGh4Z1RQNUNkSWVHcExYT3ZYQ0lvZDV3clcyY2VDdjhXQ2pNZnpZbzQ0YXJqbXRaeWkxcWQ4 +QmtrUHQ4dlFQQXBxRUMyWjhNSi8xdnROSC8rQlFsN2plR0hyakNCY01hbmJnV0dPZmRkSFo0WkZm +RC9UYW5QenRnWjdBSXFaK2lrWDNhc01WcnIwNXdxMDExclpuY05NZ1hzVExRWDQ2TWkvWC9EbC9Q +bVArVlBQOE9YL0NPMy9Pbi9Qbi9EbC9mbHgvdnBqRlQ3Wkk4Uk02TUJyNEdPZHF2NFZJaXBnT25N +OE43aWRwWUJFaURrQUtNdHdKVVVMZ05NL3dFN2RpRUtCeFBnTXNmNDFmODJxVy9DVkFJYkRCNGlm +dW1DVGorV2VoclZFa3NtdFFlelhhaFNBTlNWTnZ3Q1lPc3RXdUhtYUxRTjRHakdyRWg5clZnbVF4 +aUViZFJDQyt4aENMZXVBZ0l2dVFyeDVpVkw4YThBeW4rT1p4SjU2R3FkbThldVFTbzgyejNuZHo0 +cnZqdzV1R0R2eFZYUENLcjg3VWVBWlZEOXllNFUwdjRUYUlUbHp6UkMrZlhzR2xaaGpveFhyOThI +dnM2dmJaT2pYQlRDczVNTTcwc1BtOWNRYUVSOS9FZ0xONjZGc21PZFdxZjh5a051WXc3Tk0vNit5 +eHRsaHdxcjg1TEErTFBQcWdSbkhVQTRmNjdaTlgzZDJEYjMyc3RuQmNnOEZUamJsd0s2WTQrbW90 +bmNGQk85SEpYVFc2dnVzQmZVVVR4Nm44c09OVVRyM0J4L09IZ3pkOXovOWhNOXp6NS93NWY4NmZ4 +dnc1Zjg2ZjgrZjhPWDkrQm4rK2tBczRzQnJuTTRKanFFdUlaRTN6ckdKQW5nUitEMXNSbW9KOGQ2 +WXlGQVYwbTYwd1F3RnlXSTlJbjRGMHQ5Y0ZwT0pyZGpFSXd4NnhGS3Q0QmRhSTV1WFRLRzh0L0ZS +dXdLeDRkVEtSSnRTZzhwZ1RVOVBoOForMHhlcFFoNFpvQXRMVjJBYkxqemY3WVpPRHVkUmlyWU5M +RFpwcFAweEVnek5ZOFlvWGIxV3NiUzNpbGcvTlZJTjFZdWxEbTZ4SExwZ2VNMXpMTGorT1lTWTZj +ZkdoVGpqazh2YkNjeGh4SUpmRHdWNDF5S0VtR0dISVlYcjREWFhEZ3NQeUJndHM2aEhUdkZqTWFj +aWhYL0MwcnpXSVMvenF5Q1V2RHVDRTJXY214WU8vWkM4bmpQTEJUY3dNNmJrNkhScjZBYU84T1pT +dXQrS0lsMyt0NityeW5UbTlkYUliK25jWHgxMHRhbnUwKytVNUpHOGY3cnhKdE40YXVOVEFMM2ti +ZE0rWXZEWEQ2THREU2x4Y2lta3RianlEUyt4b3o0RjBsN241Yy82VWEvNmNQNDM1Yy82Y1ArZlAr +WFArL0F6K2ZDbXNwQ3VLZ0h4RzBpT1lMMmtFTU1oRW9NMSs2aVVDVjBYTHZNUmhhQUxnaXJMZjNS +Q0R3QkJNT0g0cWxnTVl3RjN3d0JKaXJtakFGWUg4NW1GU3d0SWNhNnhGSmtLUVVZUGEyOFlSQWRM +VW1Ma2p3eHNDODh5clJzVC80czNUSXk2aXJpQ3MxUUNHMEdDNDFBR1hKdUVGTHJuRVVnc1Q0WUJv +NVZZamJzVHpHVjRDZXNUMllJYk5oVGRydTBkOVBiRGtjK25oWXdodkZwNDNNbUxpUTk2S0NVNTN3 +aWRpbUZ1SG5zSUt0K2N3dS9lQVZUL0J5NnZIOW9pbEY3QUYwL0ZHTitaN1lCZys0OTg2c2ZSS0x1 +dnBCSDcxOTdNWWVHSXMvV28vUFBlSFFqbFFJODdFRjVjTzZkR3ZIT2duakdyM2F3dHk2bzExWXVB +ZUJqSDBWNzN1RGlqYTY2RVI3ZDBjZnNWUUc2enFvVUVjNjVGN2pIV0RnWmtSMy9oU2k5anFrZDh6 +dGVMVG5NOXlpU0VmUGJ2VU5uL09uL1BuL0RsL3pwL1dpYVZYY3MyZjgrZjhPWDkrWkgrK0NJeVli +UUpFNFJJakllT1NlZzZVWndvZ0FudUkxWGZOYzBuT2dJSWpBR0dLSWl4M0FrS0tJb0ZCSW5DRy9C +cVI2MENLK3pUbVN6Q0pENE80L1FtOHdrS2UvZGJGb0VlR1BJbDdzZUFnU3FUYlY0TWlTRFBnOE9i +SW5KcWVRK01aYWNnSkVlbnFNNDhQZVJHSVdNMGdIcy94SWk2aCtQOWtna2VOeEs0V05ha0JYbmZQ +cmRXSDFIUGNpUUVyemx4cXdCY2VQYmRQWEh2TDg3ZkQ2Z2JCd29VemQwTEJDUTRZQ3JkRUMzT005 +TWFoL1BMQWE2KzFjb3J2R1J6d0VEb080ZFlMbU5RZXJGZGZ1SDhUcS9nT2VrYWhLM2l0ZC9ERnBC +Y1RyM3JMbkhMaTMzTnZnN3c1NmVFdWg3YzV6R0dkL01sMW5IVy9XSDRWUUM5aDBGK0hzTy9WblhV +T0N6V1lMMWVlNnpPczRyajBDVjUxd0dTUGRYREpydzh4Nk1Xckh1bUZwbkJzRGtZSGpXZStWK3Q0 +cEJWNzhRTERvL25yNDhVeE4zL09uK1Z3L3B3L1laMC81OC81Yy81TTNJczFmODZmSDlXZkwwMUNv +STBDVndRYVVuSUpBWERGYVphTndLWkJCNWE0N0ZGb3hTMGhJVWd1aCtjeDB1MnJBUHlrTHFiaE9X +RXFKZ2ZDWVhsaWZRMDJHRFRZOHdvUElmWllBemVERXNsN0U0cHJiYzFOcE0rZTUvK1BTRDJhNURr +OGlFUjZoNlphUXhBMWszbDdHVlJlZ3ZFTUYvSzZtTUovL3ZZTVh1dnNjWURCb2U0MlJ1UFVYTE13 +TW80anFGdnovdkFSeTFyZnhjUzkybG9UVG5ESU9QREtyejRIaS9wd1dCRmFweWN1SE1LdE4zaTNQ +LzA3bkdwbktIajBtWWpVenZTRUNvOGV3K2FDN1luNUhJS3c0VlZmeERkdnYzeHl5V05lSEwxU3N6 +VitIMTVmeERUVTd5QmxRT2J4SENaRFRXb1V5eHAzbU5VT3A3N1ZiUGJSQ2E3TXF4VlBQZkRzaDhW +ejYzcDR3bzR6dWEwelY2LzR6Q3ZXd2E1V2I0VGtaRVRZWEw2ckc2L1dtaE5MVERuTjEzZm01cy81 +Yy82Y1ArZlArWFArbkQvbnovbnpNL256QllRUGlDSVV6Ylk0Sk44aWd5bUFyTGdVRHBTQzNsOUlZ +U1lEeVdLN0s4SWVnSkFzdHVkRURLZzhqQVM0aGdMS0ZNRG1yY3JOK1M0K291Rmtpb2p5VGRCSWx4 +dFJ4UGZzZlF3R20zM3E4SVlCRmtNOTZpVkFabkpwWnZjYThHbCtHd1pIRFFxdjJNUWx2em0xaUt2 +Sm1tUi9hOGF6ZldxWDB4ek05dnRlZytMSld3OXpla0g4OWpDa3V4cmxzQiszYW1oTjhzTUZNNzdr +dGcvWGhPMXV2Yy9XdXBpRm9lUlRwN3Rhck9zQjEvN0paOTdBcDE2b3pUcjM5RnhQR2Y4dHR1K01S +a01WdVhrOGU3T2xsL0JZSjJaemVQUG1VUEVkTDQzampRKysxU2F2d3dWV2N3NGdmODlCblE0U3ZE +S0dQc0VudnRqMnFha0hqQnpNaW04MWxRdDV6S25kSHMvdGlROU9XeldXdnVpdm5PWm9FRTU5MGc5 +YWdNTTZmWGJ2NFFVYm5POTFvaWI4elovejUvdzVmN2EveGVENS9EbC96cC96SjJ6ejUvd0ozMGZ6 +NTB0eVFBRlJwQUlFMTFTTENFV3ppUVJBUDBrek0zSUJzY2VjNEQ0ajBhZ3dpTWxkMGdJejV6bFFD +dEFzKy9MNVFGbGpUaDdtczlZZU9PUlVPTU5GeUFxNS9lL0ZxK0VWTE1LSWhzakUwRGlpTVlpRGlE +VlZ6WWhYLzVQM0dXcXpGMDlxbHg5VzhSSHZPV0ZZUjlRRzRtRW9IMnFScCtZckpudndJcVltQmRm +eHJXbGlFTE42Q0Z0OUVjTjkxaXR6OW91REY3VTg0MHR5MUR4eXE2dUNVYXYxL3ZPL1BNbDV1TnlK +UTB5eEtreWY1UkNUSU9XRldaLzBvSWF2eU8zRG41N1lZMjBPc2NPQ2Y3Rm9CMy9FTHI0NWNmQUhh +dzhmR1BFSWx6NjdPL2hjNmNkYkxNTm5CNTBZZnY4ZkpnY0J2aHk2NmNYcEF3L01vcGZxMXhkYzRR +a3ZjanNVZks5MjlWOGRNTVBselE4elY1ZnE0dzN4OEdOZUREaGdsdGNCcERhZjZjWTZkekZnVmo4 +TzRjVnRkYURPK1hQK25EL256L2x6L2hSci9wdy81OC81OHpQNDg2VkFoQUlFdE1JVjhCVCtOTnRH +QkFocVhsRGtTbWFQejU0cHhHZkRIczlxbWlZMXI0a3RqQkNOR3BTSmtRSURRUmoyRXpVamlFdGtD +b0hOZXBlNGhJNFFPT1F5eEVLOGVJanduNzBSWXlEVmN3MXFYUnIyMU9CZnVIcitQNXJrY2hGdjZ5 +RXNWdzhPSXBPL2NTdGN2T0dXbUROM2RTVG0xV0V2VEJVNFFSbndpZUdPaytTL2VRYkZtWmh3d09P +WkdBK1BoL25NMWtNT0QvWVJ1WXRCaVJXZThzQklMZ2JWYy93UkJtenEwUVBjbUljbEI5L2xzSmRJ +OVVKdGVJYkRmalUrL0Q5L2wwSjk4b3RYSFltcmY3VEhORG5Rcmc5aU1vT2EvUjJEbXNZUWx3bHhq +UWR6MVFnTVAvdWozMGovR0VTTjh2ak9MTmJUWGMwTmx6cmtrMWRQMU9LT0k3eXFwZjFUUDEzWWl6 +dTV6YXRKWERqZ3BBa3hHUklPZGVnRnpITGpWYzFpdGY3cVFLL3dtRVB6dENmVy9EbC96cC96NS93 +NWY4NmY4K2Y4T1g5K0puKytMQlJZc3lVVkVLQVVjTU1pQ1FnWkVJQTEzaHJCM0JWdkRiT1dUSVVn +VkFIbU5MWWlBTWd6KzRpU1FKQnB2WFV4NkpFa3R6a1ltVUxSaVBPVE43TXlwbm5yNElYTlhqaHlJ +TnhRbCtkSSsvay8vUGo3NzM3MDY5OE1pbUJOVVlONUlzZkJNLzhsc1RVTTNwS0hBOWpsVXd1K2tH +dGY4dDRlYzVyaG1iVzR5S0Z4UTUwNElSeTF5S3NXSXRFd1ExM3F4WTNjMWhWRHYvdXNOckhWRjNP +ZjRBd1kxQ2FtdFF5Q1Q0SWhjTHpMK2VUN2tyNm92V0p4T05oSDlQRERheTJlOVNlOEg3L1d5QzJY +dnVOQ2ZhNE9YTWpkR3J2V2N6aDlaaUFHcmZaZ2dkR3ZHdUNVK09XVVg3OFlXbDVyY3hqY29NKy8r +WlBmVEg4WUNIYXhyZE1MMytYR3QzMXFoUUVtL09odEQwcjlWRnMxcW42R3dZdDVPaFNETm1qVFhy +RmM2Y2RoeHJWL3JjeGVlZFVvUG00Y1FQYmcwaHpNNXVUQy82UGRyNmw3L3B3LzU4LzVjLzZjUCtm +UCtkUGUrWFArL0N6K2ZFbG9BOEVJREFReFZhUUFSQnhIWGcxcWo4U2VLd1lvbisyTFFVOG81Z0VR +azVDUWkzakNzOThWOG0rTlJnRnVUbjdDRnhjRyt4QUJyS0lSNTAySGdyMkYwRGd4Q0JScE5TalJH +ZjFPQUlUaVAzdEh6RGVDNytyeFhOT0owRG81TzBxZ2V1UldZL0RjWHBqVjVybjYzWkdySGhqVkVC +Ty8xV3ZZQTQrOHVMV2ZhUENmdHlwM3lVOUFjT0pXWFBzcUNIVnFxRGh3dVhDVnhyNXhJcTgxTUZq +UElQMFh0bUFsZEp6cWNRNmoyMHRnNmNHSkhsNzdpZFJudmJNZU5tdHBSbXdDNzFyYzBJazlCQWFI +UHFqTnY1amxyWms2WUZNYnpEaG5JSHZWQ3F1aHByLzk4Vy9sdS9VOUZOU3M5KzdXMTZEV2ZmZkRI +d1NML3g4a3Z3NmdkN2lEalhiVXhzZys2NmsrTVlXM04rMmpRNVdaNGNHMVdxeUZ0UWNZdlBMUmx1 +OHVHTldpZnZNdWZLdFhyVDAwNEJQSFhuZDV4REludng2bjFzTW12L1h6NS93NWY4NmY4K2Y4cVE3 +WTVzLzUwNWcvNTgrUDdNOFhReUNmU0JET1lMOW8zdk4vVkN1WU9RVURZQTl5a0toQnlCTGNuS1FT +K0s1NGw4OGFxNm5XMUJoQXhVZ25LaVRFbkhkWkw2YkNZRE1IRDVISlMvdyt5Mk5va3NMc2dSVXV1 +UXpFeTZPcHNQcUwyUEliQktrdTlUS29mZjZ6dVB3ZEdvVEVDa2l6Y1JLc2x4Y25tZ3Jqcys5ck1C +T2oyTmFKKzRqMndZcUROc284N21FdzFLVHVIZ3lhaHl2N0NFeWRFZTN0UzV5NzRNSlBEeVdHazAr +OWFoZmZteUc4eVlNblFxMm84bWJwTG5qMFgzNDVrL2Z5eUd1ZEhNUm9XS01HaGxlclB1UEdIVC9Q +K0pKNjlFUSt1T0d0YVBFa0gvSENKWjQxNnNBWmdlTmRuL1N6ZjVEOC9COStuTHJNNGNqQS9VLy8z +UzhGZ3g1Ykk0L2FIY0R5aSsremRhMUQ3M0RqdTlod1dDczJEdkZkL2VFTGZscXB6dHpWb2c2WFBm +TGcyRUhnTzczbFVEaytjVm45Mk5lYzZwRkhqUFl4aDltdG16L256L2x6L3B3LzUwOTQ1OC81Yy82 +Y1B6K0RQL04zUUQzUVRNRUlRUkdlRys0MUtIQVNBNjBBbndWUFViY0h1YyticGE5cGxQWDJpdEdH +QU1rMEZScndtbzhnejJEUkVMR0IxV1NmUGZPVHZKZ0lGYjhtbEFOMjZ4VHB1NSt3aVU0dTJQM0Vq +Z1NFNUUzS0RmdlZUV0FhS3E1L21jcitqaHF3SmhNTFRqWGhTajY0Y2xCZGJZWUdhTEo5R3FKaDRx +akZQdnZsTUtkdXpSTUQzbUM3dXpsaXF3aVp4ejdQMWFuQjhLZ3Z3cjhjUGJBY2VMREJ4RURxVnI5 +RHpWcDdDQnFudU0rKzIwTVFjcmhnMTlmaXRGYk9wNDduWDErRHc5dVlpUG4ybEgrSHNWclRneHRp +eVk5RGZSTkhuODM3ektERW5BUHdNTnR2YmZlb3hYbzQ0WWJMWFIzbURmVjdvNlMvRGxuYzBhSzFU +S28zTkNqbW4veHYvMzNXdzZnMnZSZEhUUFdxeDZWWDZpMkdjb3BIZmNzQmZNOW9XQzB1OCtyQXM0 +TkNQUFVFdzF2ZnpNTnVuL1hlWnRHZzV6VFBKd1pzTU15ZjgrZjhPWC9Pbi9Qbi9EbC8rajUvenAr +ZndaOHZDU1JUR0JINERMQkpZaUUyNUVzSXFFSmJoR2ZXRWdBaXhKQVVtY0JZcDJGeWlJMGdCb3Q1 +anlCN1BXTm8rWHpXQk1UYTU4NUU4cHBUaEJ3S2tsOGVBMmFYQXNVbFhOaUpUejdrTUFDY212RHNl +OTdrMkFNN0VYbURBbE5GYUNEczI2SGlFTGpQT0pGUExrTHd4c0IrODRaNWNUd1R5M04zRGF3QjdN +ZVB1QnJKU0dxdFFXRlR0eHlhNmxJTExqM0RsKy9XNEVJZFBYZ00zTUVHQjc0ZEFuQmFhMThGTENj +K0dBNHUyT0VTOXoxK1hKcGpZc04zWFBpOWRQZldaYTllRTMvN0k2ZkRUODN5V3ZmMC9VdnU3WTJj +N3RIUHhmUzc2T3FFRFhjT0NXdlVSRit3cWNXUTErRXFGOFBEYmowRCtHeWRHSHJ0TDNNL1BYNTA1 +M2Z4clJGVHJYUnRuZDZYang1UWNydndiUThUaDVQcm1WR3VtZGZkbkRXMDY5REdBUTNpd1p4Yy9u +RERqNW85Nng4ODlZNDk4K2Y4Q1ZmMUtPNTcvUFBuL09uei9EbC9SajhYYy82Y1ArZlArZk9mc2ov +ekE2aWdBZ0Rqb1NJMVQxSmlFTlM4b0FJS0JKeXJZTjE5Unk3U0pSVVhHRGtZVjJKZ3hVWVlReWlj +S2VWRklKRnBvTHhFQ2hNeTNmMWt6cUJ5aVcrZmdVaHJrSTFNZU1VUkd6WnZCYnlCMEN5MVZUaEVw +aUVNTDY0TFhyazYxQUtybUJybzhoTy9YSEJxQkN5NHNZYm9tSzU4cUFNbUYwRzRtNGNEaityMmwz +bGhxMEhoZG9rbmhydDh1Q2QrbjkzaGdyZUdMUitHL1dLNjFPaDMxSEVBcjd3T2hKamtILzl6ZUcr +ZGVKUVBiOWIwa0toWXk0MSs0c3V2RVlnSlI3blhiN1VXai8wNFZqY0R3bTQvZmNrTEcwUGdnczRZ +c2tiRHNUZ3VHR2lLME0yYmk5RzhFVHRkbFVmR2NDaUxYZFBybXpYdWZwZGV2dlQvWWppNDlNOGxm +bnA2T1dDMHA5clY5Mmp2NXV6VEc3WGtqZGhiTGRiaFFReTFtSGZoeWg2OWdLSDRyWU5GWFB3NEJI +clE2c0g4T1gvT24vUG4vRGwvenAvelp6VXlmODZmWW45MGYrWWZJVEloYVpzRHBFU0dlWUVFY1dt +RUJENUxhcC8xektQeGdDb0lRWUJhS3k0eVhBQXB4QjRGSU41NllKcFRJeG9MWUJkY2Zvb1dVeTd4 +V3dqRFcrT25hM2l0clVBMDNVRkFCRWlSdHdhdGdlRmdNclg0TGxZSGNqMHJSNXFvSVRnaFZrMGla +QUtVbTBFSkZEL21tQXBuWWhpd1dkZG5qT1hOaHU5d01ZdWN1SEx2UWFlMk5oa1dHUEJxemlWdStU +QndRcXh3NE5LL3JPYnlEQ2FpSmV4bmZFa3UzSWJMRXlXTThzbWhoM3FpTG5jajJJNFA0b1pKRHR4 +WXI3K0ovUlpmYmRXRHZMQzdlZ0Q3aS9ONm9GZlZoeDc3OVFkWXJORXJ6eDBpZU1XQnZPYlY3Uktu +ZGVxSEhISTZvR0ZUQnl6V21hY1ArNzJoZ2s5dUdpbGV0YXBIYnA5eGcwOTc5YzA2TWVDZ1ZldkU4 +MTBzWEt2UlhuMmtjM2pVZ0YvOXB4MjZGbGN1Y2NYUkUydGM4K2Y4T1gvT24vUG4vRGwvenAvejUv +ejVXZno1NmhzQUN6VFpUN0JBZHBHQTFraUl0QWIrK2YxRXEraGVDRFpIWUlCb0dHSTF4RjVrSWRo +UDcybm1nU2NBT1pBcnJ6V2FTb3pJTklnQ0J1QmhWSHlLT2xMdE5XQlZtQWJEYnIwaExreHdJOEdj +TlQwSXpMY3BST3NOaXM5STd2QzVOWnVEaXpoN0lPQ0dZT0QzVFAwd2kydVA1K2JMamVlZSthNVJP +UGNyQ3RiV29QZ2pCbndSRjh5NGhORmUvRVVJOTdrMXV4eEtIWVFoRjY3MFNvNEtsMWpFTXVURE9m +N0VWWnZjeGUxT2lEMHd2TDNwUHVzWlZOMXdPQ2owQnk4MXFKb3FlTEhrVlI5aDQ5OWFCeFJkbUZl +SGVicng5a25kWXVpeHU5N1NXdzY2NCs5OUxqSDhmcnczUzNEUkRDUDdWUWNZZkxiUGdVMFQrbVMv +TjBzOVFOU3NEdmx4b3IvNklEZk96ZEd0T09IbGVMWlBuSmoxYXJDSFZtbmNNNXpoM2hveDhHYXZl +VmprMWhQUG9yMHpPMjV3T24vT256aWZQK2ZQK1hQK25EL25UK3VOK1hQKy9Pait6TCtDU3h5SzAx +QkYrNjVnaXhUcFRRQkRFTEQ1RWs2c0NuSVJnb0tJUkVIV1dHdWZ1RWpYVk9RcTNuNmY1V2llaU80 +YVRMejJHRWhFaXRpYUt5WThTSDhNK2pYTnM5OCttQlJtSU1mZUdoU3hNZjRSOEl5dndXQ05QZDRp +d1NWbnpZOGZPYzNMajN5RSs2bWZlRHlYMjNQNDFlaTV1T1d6d2pBOHd5OStOQk5tZVgzSE9TTm91 +aHh5RTVxOU5WQnorbTYvTmZCMEtKelNBQUQvOUVsRVFWUS9CbjB3RzNwQUFIMnJSZXd1UXNpem16 +TndqdyttRkV2dFBRekVWSnY0NWhuVWxYMVhwelVPRUp5cUVTZHdQUWZrOHkrRTZZMTZhd0NYZ3po +NWI0MjRSQXBUKzJjUG5ONFFxUjBmWXFtSnlIRm9IWHd4cUlQcHVGTlR6UzZtdkRoU04rMnF3M2Z6 +dnNPTVo5cWdZYlU3ekt5TFdkNjRmZDhUK0pQL2VtRy81M3FjbW85SGRacnZId2llNGV6OWdTR21Q +VXpzdVVNVGorVmJyZUtxRVliNWMvNmNQK2ZQK1hQK25EL256L2x6L3Z3cy9zeS9na3RjRnNZMFY2 +UUFKWSt4Z0RjUFBOQUt0OW5selJBQkVEK3lCRmFvTllSa1hqeXh2Sm1SaTdrQXNrZWpBTkpZc2Qw +MUVOaWFWL0VLZzBQREdGd0QrNmFualNNc0RiQTJCOHlKU0c1azJTT21nNlQ3NVBYTUdnMFJFMW1F +bHFiZkVCTk9HSEFqdGxya2hEV2l1bWFKSHhOZVhpUVRDRDZ0citqTldaZG5WNnVHKzJ4dERTeXUr +b2xVVFRERmpIZHd3WUhYN0wrK2lTa0dRZUE2ZFYwT3cxN2NxMDlzbkh0emtvUHpNTmVnR1c4aTZ1 +RklQSERxYTNwOTljbFQ0UmdWSFpIQmhVTmFzUzRtdnBpRURZLzk0b29QYytzeHIwY09jYnpEckE3 +OE02eURTNjFpaW1jUGZxeUIzd0ZoclJnR2JUR2ZXR0xxR2U3OXBXenI5WVUrOENBMkR1WDgrNS8r +VG5wbDNwdTBISjZYUzE3WTlVTGRjdWtiZmx6V3c0TmZPQTM2b2xNWTlJV1A5Rk9QeFpUVFhqaHdi +TDIxK21PZk9Qcm8wcnY1Yy82Y1ArZlArWFArRkgvK25EL0QxZnc1ZjM0Q2YrWWZJZktCRUJTaEtU +SG9FY0ljeUJiSVBFSVVKS0hQQXRhZzlvcGx6aDBJeVRSUHdRclRMRVFneUhxRXl1TS8veW9XQVlo +QVNJcTc5ZmFKaHdRNWtTYW16eG0zeG5yRjF6QmlSYWgzRVJpY2FoQlRBNUhVdlhCNFRteWE1ZzBE +b2JYcEdxQStITFZ4aEFWSER3VGNxRGNtdkdHTnQxS2E2eG5zNWMxYStNemh4N3pEQ2lZNWNlR3lW +aHk0ZTNEQktnL2hpV2ZlNThheUwzWGZJR0s1NUxaWGJmNVRQQzZJL3hIUGplUGZIalY3cGlmdzBZ +QUR1R0l5aitmM2g2SURPd2YxNWRjVHZVMi85TnE0dVBhWVU0ZGU2Q1hNTHIxMjJKbkhGNDVodFVk +Y29sYUhOZGJpbkNiVjRHQ1FoejdUcTdjYTFLeFdWdzhNcHJOSGJEd3pCTk8ybC8zZGV0L0YxVys5 +RmhzZjhQUVBLM3dVcDNvOEUwTmRSdmc5RExoUkYzNzEwaDhTZWkyV211ekZpZWY2b1g2YXhyVjYx +R20vdVBQbi9Lbm0rWFArbkQvbnovbHovdlRjTlgvT254L1pueStiTEJBUUdBSVh3TnNBZ1lIc0dw +Zm1lYTVJM3pVbHhyNExXRUNSSUlaTFVzMUdNQUZwSmtINkNkNmV2b21TUTJ3QWtRUUhNUkFDVWdp +TmdFb2lrbUJVQ0JJSVZENk5SNTZCT0h2bGR4RzF2V0ptL2pDcEcxbnVCUEhkRDMrUXBuYllBN00x +WXNGWXZMRENhYS84NnU1aFUwUERvdUZpd2dPZi9PckhuN2p5RXB1OUdsamV4WXhCajFNNGZQWXNi +eW91bC96V3dkV2VHUnBzbmZ6aXV6dVl2QWtoZkllcWU0ZDk4S1gvRjh0ZXZTRVE5ZUZjSHRoY2Vx +WVcvVERmZmNRSkt4MFkrTFdQUnVUVWc4YVRJNmE2dlBoMUVTdTgrcTVQM3BwRUU3Y2VaK0tab3ls +dlZ1eHRMdzNmR2EyL2toRFRuRGJvemFVWDhqdUVIVlo2cUpmV01vamVxRmxkOHNPdU5ubjFSSHcx +cUVVczYvUU5CajNSUDNYeGhnTUNIM29oanQ3SjU3TThhbEtybXEycmo4eXBVVS9Fci9mbXovbHov +cHcvNTgvNWMvNmNQNDM1Yy83ODZQN01yK0FDVFVUQVNFQU1BbHVvRVFpWFFDQkpORW1EaVpBNEZl +T25Za1hZQzRRQzB1aUxpMUJ6WWltRVlBb0lnUXJVSVBIdFZ5RHh5WU5BZWNTQ1QxeTVXcnlCUUJo +aGN6R3pZUy9oMnE5d3RZb05oMkUva3hDWDU3QjZXMkJ0Qnc3Z2ZKKy9CcEtybkNFYlh1dGhFVU9k +c09IWG5Eck53WVY4K2ZCS1BPb2xmQmlKRWc5dzRZblE1TU81WERrRVR4amllcTVHT0dwUUF3ZDZB +enRzUFlEdzVqTWhNb2hmeFlCUlgrd1JFL1lhbEpCZ000ODNhdzFpMUhmNDFLQi8xc09sUHFQQ2xs +T05ZbG9uRDB6eVIwdjNUQitJMVp6WXZqTVNEdVRIdS96aXdhaGY1ZVZiSFRldlJ2WEpoeGY2RXRj +YlFHdHAwU0hNb0hpVzMrRmxqYnJkclpFL3VTNi9OVDBNNEdQbUdqbUg1ZUdRMjkxNjgvS2JoOHNh +bXRVN01lVzFWaXdIdGQ3UktKNzBYejF5ZVRaL3pwL3o1L3hwVHF6NWMvNlVaLzZjUCtmUCtmT2or +L05sZ3dESUpnRGdKQ0cwQ3R3elFTVndCOVlhYzIyU241cXRKeXhrdXBzSFFHTGlVYWpHK09rK2J3 +c3VobVlVREJKOTFnRGlaaUF4TlFoNUN2RVplY1JkUVJKRnJpdk9aYTI5YXJQSGVubnRsYnY3M01X +UnI1YzNET3JNT0tJWXFuZ0lTUjV4bVZPZDR1UEVuRnJNRWJYYThFa2doS0dCZURPUFc2Sldzd3Nt +OGF6Rm0rYkJnQ1A0N0xIZkdweXFUNTF5ZUNZZmpnUFpvWFc0WWRZVHVNVWdHbm1JVnovMUxPdTlZ +WHNUaEhya2xMdGFnTjF6R29IRE9rTU40aE1xREhKVXNESG94Y1FIa3pZL3JjQmF6T28xWUdkRzhk +UmxQMEY3Y3dPL1ozaVJrNGIwQUQ2NGNNVUV6L2lTK25DclZoeW96ek9tVkJjZGVJT2t6OEZ3T0dI +emF3eHFZMWE4MnNOQThuanVVaHYrNlVsOGMzcHFuZjdBZ3dQMXdQOFk5UGxuMXhtL2h5ajg0c0hp +b05GUGV4eGc1dFNCYTcxd256L25UN3lyWi82Y1A5VTJmODZmOCtmOE9YL09uNTU5VkgrK0JCYkFB +c21BcnNDUllST0NFVVJNNWoyWFhBRDdKTkZ3elVDWTUySW9GQm0rUzZwaDlpRURXWXFWdjRMMVR3 +d3JWcTd1RVU5aG11VHV1YlhJbDg4YU1UeG5GT3ZVQTcrNDFpR1NPYzNCK3V5NzN0d2F4b0tGRVBQ +OTFzclRnVENZMUdudXZVSEZRN0k1Ri9MRlZuc1BLR0t6VmtQVUFxTm4zaUJvb3Z3T01Ydmx4eTg4 +Y29xakx1c2RGT0w0empodzVOQzU5WVRoMlNQVVI2eGlFSkw2WWZ2YkgvOVd4TWNFK3NsWVJneDZl +K1NBVFgvMFRhL3R3NlZjZXU3Q3RlR3pHdkFwUDUzQVJrTmlxVWUvWFhpelR0L2FYN2dUNjB4cUx3 +NzhaLzF5UmRDZWxXZmNpYVduOXFvZFAvQjlHMmMyeG5FNURPekpJWHp4Ly9SLy94L0RoVmhpOTlj +MUROcWxQWG05WVJLWFFadEhYdlhhcTEvaTlZMVhlMm9OSHV6RFF3eDhhL1hVdkY3amxYYlVyeTY5 +Z0VXdjRNV1A1K3JBby9qejUvdzVmODZmOCtmOE9YL09uOWJObi9QblovSG5TOUJ2SnJoRzJPd1Np +TmdFMVh4QTNhMDNweWdCQk5Wd2pRV1NrQUJVcE1UTUdETmRNOHg3NWlkeWpVSlFHdytzdFo0UnR2 +MmVhd0JzeUdJeW56WE9tb2pyaWlFd1dCV0ZEQmdZVjNQZ0ZwUFkxS2NHVFRic1owd2lRYVR2MW9o +VG9jc0xNN0xGTVUrQUVlNDFUSzNJOXlZREJyR1I3THVjMXZxTU14ZlQyRWV3bW1PTmVuQWpQMTVp +dkxjREJhL3dpVmx6dXRTSlY3M1NreHgyYndlUFlhMjRjdUNXK04zeEo3ZWVoYnVMM1lNbEIvVGx4 +WWs3SEhwQXVPcU5KbUxRTDNtdWp5NHg4YU5uOWxxakZqRjlOaWVXR3R4eENIUFczSHdFZkx4NEk2 +Um1IQks2eTNjY3h0QVhEMDlpNEZSZmVyQ21YMWVMUGN5alZ2bGd0L1l2Zi85WHdvbGMzZzR4cU9j +NGtKdEIxZWdaSG5DSEF6MFBQNGNUMy9waHZUeHcyT09DclFkTHNjTnNxQmNlTllmbjB5Y2NEc0Vl +Sm5RZ25qM3lpZWtRblQvbnovbHovbFRmL0RsL3V1TncvcHcvNTgvNTg2UDc4NFU4R3dHckVRQmtX +bVM4bndQUUhBTHRFMVFDZ1JUU0ppT08wRndBUnp4WFlNbnlxd2llS1JhNTlpQlY0ZkxicHdCNUNB +RlJSR2tPNldMNnJzRUlsdGZkbmhyY2QvTVZXb3ZYSkhVWjRpUENJVVFJUGlOVG8zT2RFTTNCREpP +OStMQ09NUWdTTDRpMmpwQ0tWeTJNZ0VjeFlZRzlid0hhUkhYancxNTRySFhKcHc0NVBWY2JubjJY +c3dlcFpydXJMenpjd0tsYTRjT1RQdW1YUTFWc0J5dk1FZlliVDhWWmpzV1RTNjFNQUFPTStvZ2Ja +aVU2aDVOK3UyRFI0K0M0TmRiQ3hzVGltY2NsSGVFbzliNzk0WUFEYjJma2s2Y21zSTVndTE1TTY5 +L3JJVHBJNGY4NW5QdkwzZDVNcWxFODY3d2Rnd0VuZi9NbnYvbjlYLy9ocitXUW9nVWE5b2NEcko3 +aDJqUFk0RzFONXVXRnpkVTV1S3BqaHd4encwNHJobDdpd0g3NGFVUU9mV0JPdmFFTDk5WWpGbi9N +bi9NbmJQUG4vRGwvenArNG5EL256L2x6L3Z3TS9ueTF5SkloaUVra0lkUWk1Q3ZFV3M5OEZsempC +UVkyQW5sckNCSTB3ZDA2Y2RzOFRWZEF4V01vRUNISWtjTnpZbXlURVFVOEhOYktwVWtheDBRS1Jp +QkM3R2NXaE1tSENHdGQ0b3NUWWQ2d2ovajhzOWxNYjMyd1h2MGQyWE9tOTl4ZXVNMGpIVWIxZWM1 +TVJCdURIZ1p6dUlIRjNvanNua2VvdDErOVBheXM2NkdBSjRlRmVIaklZWFBQUFpOTEhmYmgyVFA0 +MVEwamJwN3hOYy8wemdVcmN4SWgvdTMzWE55S3E0Y1Rqc3VWdFdwcXo2MVJuK0c1d3dWdmFzSUIv +bnBJTVl2NGNNZ3JQNTMxb0pQTE9uVVFOUU41YzZOZitHSXladk5aVE91c0Z3TXZEZ2ZjNERmYzNh +QVZKc2VwZURXb05VeUpaMXFTNzg5Lzc1ZWpXMzF4eVBTUVZBK2VhUlIydGJXditvYm5hUDcyMks4 +SE9KUWJOalhnTGdhL0dnMDF3NE5iL2FKVnNlUmhVUGpLZDdSMzNNRWQ3NXhtNXMvNWMvNmNQK2ZQ +K1hQK25EL256L256cy9nei93cXVMeEw2VFBBQytTblZSb1FCU1ppZU56aVFuaU1CVUhkN2FoRGdm +Q1lFOXpZNnBqMHdDTERma0JmaE1jTTFXZ01pV25OSGFHS2U0UlVxbm9QQmQ0SXdFQ3RmN3JkZTR4 +Q0t1RFpVOFo0VFNvYzlMbVp5QlRPREhzNk83b1hCSFFiek9GTXpnbXNDSXE3SnJLMXcxVlZzbW80 +L2ZNT216dFI2KzRoRkEzRlVnNnJUbkh6RXdWem1DTll6dVBCbnovdGhIYkhLTDQvNmZJZlRIQTRm +L3I1OHk2MStPV0RzUVdDOVBBU29obkx1cnUvTVJ4Zmk2N3ZhYUNUajRzTGxYMTZUcjRlbDllV1FM +dlNFSVJsSEREMmdEd2VBL0RoUmc3aXArZVp4amh1eG5vUDYwUUZUTTRjNy91RVdrMEhzTTd5dCtl +bS8rNlZ3WTQ4L25PUldrN3o2SVFhOHVJYlJuRXRmR1FkbWNYQWx2eml3K2xXUThxRTNocnNjK284 +ZnRjT0NQL3FSQjlmd2ltT29tYjdtei9sei9wdy81OC81Yy82Y1ArZlBaOHlmbjhPZnJ6WkFZNEFS +VEFEaUJzQm1oWmduR0EwbGZvUURVVU1KQmlod2toT04yQXJ4ekI0QzBLQ2FURXhOUktKbWVHWnZq +VzhRb2oxeUthTEVJS2xDa0JkT1JNWVVGNWRaRkNzZVVqWEpuSDBsUVoxK292OVBmL2F2OHp2VWNx +b0hrUjMyVmVUeXc2TVdNZFFsbm9iaXpuTzFJTmVlOHFrdXorVERnYVo2STZFdWM1cm5yWTZZOHRp +SE8zUHlPL2owQ0Q2WHZEQ3FWeXl4clpYN2VmdjFOYlU2OUlqWVdqamtGRnUvOEZsaDI0TXY2OHlM +aHpNMTJDYzNZY0ZYN3VTR1JlM1J5SEVPS3hIM0RSeXRtUE12Z0Zuck0vN1VCaXUrMmk4RzhVOUg0 +OFE2aG5Ld1dVK0xqTjU2dy92TjljQ2tJVU51aHdHekU3OWFjSTREYjM3a3dhVTdJK0ZkUGJCWnIz +N0dwakh6Y01BbmJucDNtamFIdS80aGc1OXFXQjQxNklWNDhzQU9jd3g2dlUrY3l5T3ZRMXBQOE5i +RG1LYjFSVTV4NXMvNWMvNmNQK2ZQK2RQbitYUCtGSGYrbkQvVmd2T1A3TStYSUlxWDBFTEpOVWdo +QUZSa2lwQkVBTVFpUndCbUE4WlB1bjBPU0VSeDV0QkFvdE5rZHlBSTNaeDdtbmtGZW5zaEZoS3Nj +UmthQkdOTm9qa2xpUUJjR2dlem9oUnFyWkhtWEUxSWRsY0RBUkNqQWFNM0hITDRWNlEwQlY2RWRz +Q0JTRUlRV3gzdnpTYTI1L1pvQnVLdGtRZXZNT0hFWjNuc05TK1A1K1hSOEZ3K25GdXZUdmYzLzhu +YVd2TnlleVl2OCtEdXFldmhCQzVpRTEvLzRQZDc0N2dSUTZ5dUphNGVMSGlxTU5PbjIxdEJXWU5y +dzFvSGdON1RoUFY2WTQxKzFxUnFaamJjdytES0FYNHhJK3JMNTNCMndXYzl6dnphQ0k3d0lSY2Uz +R2xCclBLbi9pZlhsK3o5MlIvL3k5VG1FRmNydlBiNFY5TGdFNXQyNUJQSGQzWGd1a2JTVDVqVkRo +K2V4WUdsdjhKZ2piWHFrWitwWUtRbm5PQmVmcy8xcWdleXV1R2tGWEc4aWNJZC8rRGJlaGQ5d1Rk +L3pwL3o1L3lweHZsei9wdy81OC81Yy83OExQNThhWllDQXVZQVNDS2hadHZBRkJaYjUwS3FCdFl3 +MXZqcEhuQkZNRG9BaWhVTENNUmFDNmc4MWlBTHdRYXdTSXBCYjQxQ05ORlFFSkxhS1BQSVZKRGhJ +RUd5dUFwbEpwaThKU0ZjT2V4WEk5SWVZVDREVG5tdFE2cW0yT3Q1aDdocWhVZFQxR0hlZDNPd0lo +SW5EaWg0MVE2THorWEVkODJFWHo3UFhjd21oaUV1dko3aEIxNTNRMTV4NEpNN2ZGNTg4WmdITmx3 +ODQwdjRpc0RmREVka2FvUkRQV0k5NjU5L1RRMFdhNHJUSHJpc2hVbVA4Q3luUFM1Y0VwNTkxdGpI +ckxUamJ0QkF4SDZZeGZNZFhoelk1em1zK2tOSHZ0T2JQWExqc3RxcHVmUmZiYml4dG5Yajc2Lys0 +RmUvYVpKZTVYSm4zQnhzaDE5Y0J3QXM2bVkwZFZRUDF0VjhzTW12ZnB6b05lN1U3azREL1VNR1Zu +K282WVhMZDlqOG9VT1g4c0VydmpuNythWWNxNTIvNUlKVGpQbHovcHcvNTg5eUxJODE4K2Y4T1gv +T24zTE1uL1BuUi9Wbi9oVmNDNEhSbkFhMXlBYVhJaFhORkJJZ0U5a0NlT2FuYlQrUkkxQmhGUmh4 +aUsxd0lKR3NZS0FVK0JqcElaWjRFQ20vTmNnZ0F2dVFvRGh6Y0dvZUlydzVDVGwzTjFmald1TVpF +Y0NwZURpUWIwMzIzR0FvdGNEaWNFQ2lYT3A5eHZPZjJEMVRpelZ3cUVNTzNJZ3RobldhclpucWww +ZWNOdjE5SER4YlZ5NnRONndYMXlXUHR3Zm1EWHZ3NHk1VythaFJDYWQxR2VhN1hsNXJmSmEvaHlO +alB1TkxzTUNLKzlZbWQ3K2J4eWZqeFJBblNMZ1pwUWIxM1RyYWNjR0RkL3FBZ1Y3Z3dKUDltVC91 +NFRMbjdRcmM1dkVuUDY3TjE0elcweGhPMVFLVFhJYllmL1MvL25lSlJVLzJlRVlyak92QW95ZHgv +U3RoOE1xbEJwcFNCODdwMGxvY3dPTVBCbldyQVVjd01MVGFQTU9IUTBrODJzWTdqTENiaTc1UFc2 +M0JPcjJ6RnpZNTdZRkxUckdzbytYNWMvNmNQK2ZQK1hQK25EL256L256R2Vibno0L3Z6NWZpbVFz +aEF1YTZvZ0J5cjdHQUZpd0ZId2lORThCUDdNandtZUN0WVhwTnRVYmo3RkdvejlackdFQUFHZ3BR +a0djUjhvRjAxMEJyWEJXdElqVlNVUXFKS00rTThDcVFVQkZxT0RERVFZaDlHdmNjQ3M4YkRpVElD +Wk9HSTV3Skk2QWJ5TFVmUjJwU3A1bzBFaS93aU9FUUVGOU16NnlGZzNnWWlKRFVvQ25XVlFEaDlv +MWZRejF5aTJFZlRzWDJ0a3Y5c01MaXMrZmxSSHo5dzBHSFhQS29IYi8yNFY5OW52a2VveGxubG5J +bEwvN2dKRVJ4MU91cWVQQU5NM3c0ZDdEQWtGNWZQZUpad3ppMHBWZm00WSttMUh2emhqM01BWmM3 +ckdMb0I3N1ZxQTY5c1Erdm50T2c1NTRGMHcxci9hcUpPMXl3cThYZmcyQmMyalRuTDJoLzk2TmZ6 +MTU0NEdNZy9VcmUrMHlqOXVzM1h1V0FyYkhOMDZ5K091aTgyVkdqWEhnVEU1YzBFWU1lWmozRFkz +a1dXeTQ4MmtPZm51T1RsM3lmUCtmUCtYUCtwS1A1Yy82TXB0UTdmODZmOCtmOCtjSDltZjhDQ25n +V1h4QUVhSnlFRllobUtnYkJOaVBMZXFSYUl3bVJDd29ZNHQ0THhQTVdaNi9DM0RXYytJak1NL0hG +OWQzVk9XS0JDMWJ4L0VWWUJkV2c0cnZNaGJURWZmNGlzVXZEQ1FGV2J3TTYxT1M3MkFSdTN1L0ox +K0NFWmk5TXNGc3JoMFpxVEd0Q3BIcHJhRnlJVlNHYjF6ejQ0U2dXY1ZxZldxMzFYUTd4Y0dTdEdo +dFhMOUxjaTZkT2wvVzQrMmE0RzlZUWt2MncydCtld1NGV2hTMjNmc2NjSjFoY3V1UEVmdmdjUkhJ +U21HSGVXeEt4aWxrK05ZaUhPOEpsSktKWEQ0eTRkUmpneHAySjdZZkxPdk11K09CV243aDY1RHN1 +NGFJNSsvUW5oL1FOK0w3NzRROVNPMzZENSticDI2OG9NSThlK292YzlxcVhYbHkwcnlacmN3RGNY +Ung4dzZrVzYzR0lFL011M09BTzkycVVBMVovNTZNNnhKbTgxcXBOSFhqQ25WclVwTmR5aWVGQWNO +bGpqVHp5enAvejUvdzVmK0pqL3B3LzU4LzVjLzZjUCtuN28vb3pmd2NVT1lwakxwOGxCTWlDaU90 +QUFjMXdOakl0Z2hRdmlYVVMyR2VkZVlKUXVIa05sOWd6eHZMV3dCc1NUZFZNb0JFS2kzenlBR3N3 +Z1B4OUpvNS9kY3AzR0FuQmZybmhZVENONlUvbURoUUVxYytGdkl3VGlQeSt1NGhONDhSTzNMYzFZ +bGlIM0pJTWc3cVFESnZQY3NpbFJ0alV6Z0ErYXdKQnlLOXA5aERqZS9NYTVxelhRT0tvQ0ExeGZi +YzJoOGJkNGNLUGZiajdodnNHM0dKbC9Ra1BiblhpQmw2eDNodGFYMXNYZkF3cHB2Vml5ZWxBd2JQ +aEdSSGFBKzk3SG5EUGVPNzZUTWhxaGFlSEdHNXpFRnhjKyttT051QVFBejRZeXB2NCtrK2o0dEdM +M0M1eERHdjg2b0U5REU2TDhEc2s2S0k2ZDhEYmp4ZlBHaE1XYjM3bzEvTWNoQmVUdnVUQXN6bjlG +Sk5tekQvNTc0QzllUFNEQi8xVmw2RU9lMkQxMlRyOHRROTRocmUxaWtjNzlEWi96cC9HL0RsL3pw +L3o1L3c1Zjg2ZjgrZG44ZWZML3hDVFJpUFRKaVJaaEZCTkZReHc5ejVYb0lEVzJ5ZUJRUWlJOU54 +ZUJSR3VJZ0cwRnlERmFsb05oaEQ3S2phRis4bmZjODk4Qng1T3Y2SmdiUTJxYURtOERmSlBUbHVE +TUkwMmgyaHI3ZkhUdlRrakI4WWRMT3BBbE1iaEk3Z3l2dVN6T3VCV2t4ckU5eGx1TWV6SGkxeHd5 +c040NXNVbkVzMjMxek1jNGN4M3RUYWZ4bWxxREhIUGFtSTFNcU05K0xSUEhtdmw5aDFQajFCTysy +Y0NHR0ZtS2hqaDFUUFBhenoxaWUzQ0hWenR1YnhFWlI4ODNwSjRSaHY0MHhNSExUemxnU0Z3a0xn +MzdNR0Z2TENLemJnd2lLVW5GYjAxRE9vWnpONjBoSWZqRFFaN3pUc1U5RWd1dlRUUDdITGlTUCt0 +VnlOTTd2N0NOdjNhWjg5Zi9JZC9rVjZybVRiTTRSYVh6S3AzUE9FN1R0UUVrN1h3MXFEMnFNODh6 +dFhvbjhpR2xlSHAxVEJQWC9hcld3eTErZDQvck5SbVArN3pCdTBHN1BQbi9EbC96cDk2U0VmMnln +dGp0V2R1L3B3LzU4LzVjLzZjUHorU1AxL0lrUkJ3SmxNY0VvbGJnUm92a2Q5ZkpnUnoxaXJXOXpa +VUhJMUR0UFdLczhablRaVlVQTUp5dHo3a25qaUFKZVRtUXNwanN1Ky9HVmVlR3RWUCs3NXJpdGl1 +RXFoQkNqVUlObk5IRUt4cUMya2FlbGRORHdlc21zWVVCTmZCTkxEQW9TYmYzWDFIdGptNENLdmkw +MkFYUEsybnRadUhIZWMrdTR1cEZoaGQ5aEFiVFBCN1cyQXRic1d0Mk1QSmZkWXJocFBmaUZnT2sz +bTFlSzR1WXJCZS8zQVJjMTdQekt0RlRoamw4TjNkV3RoeDVJTEZNRStFK3VZenpPSTdLSE1BWHV5 +OHRiczVhMkRCbTF6bHdScjE0NUxnNmNnem1MMTlrUmQzTUlpdmZzK1poNm1zdCthcDQvbUw2ZWJw +UmN6eTlPZS85OHN4SlpQZzVlOS8ranZSTTgyb3lScDZoTmUvSGlhUEEwRC9YUENvaTA3aFVJL2U0 +RkF1MnRZbitkV0RSeGl0MVF2OVZaL2V5Z2V6V1BJNUVCaTBuT09HYnZ1SDAvdzVmNXBYeS93NWY4 +cnZQbi9PbjliTW4vUG4vRGwvZmxSL3ZteENBQ0lZVkZERFlqOUJBNjh3UDdHYlE3QmlCQVhJWjgv +dE42d0ZIRGwrd3RXSWl0Y3pCWmxYSUxJcVRELzVLMDRjQlNHR2lhekxzOE9EVUordFRZTnZJQTVP +UmRtRCtBcWVNWDBtWVB0S2FNYWJRZUgzMDduYS9Zb0M4ejhpTTU2M0R2YkE0WE1QQ2JWWkp5Zjg4 +bXVrT2MyeDNuTTRJL0REM3diaHl3V2J2Qm9NanpWaVdxOG41aGhDSStYMm5UalVvcUUraTU4RDdt +cXdyZ05YNmMzaHdZTzllTk5QKzgzaGpSRDBCeGJQR0Ywc2MvcXJkakZnRkxOWTFVb0R4R3l2bU9v +bVZMd1JwcDZMSXdiODZsY2ZEcTAzQ0JTWFloR2t2ZmJBU2kvbHl4NmNPbWprOFNiR1BuSGh0dzlu +RGczYzBJR2F4ZkQva2VRdlpWdVBLejJtTmJ5SXpZenF3SHVNZk4vbHFuN1VnVnU5eG9YNFlqc2c1 +SlJmM1owckRySHhhNi82NU1hYld1VENSWFByaTF3MFVJN3R4OW44T1gvQzR0bjhPWC9Pbi9Pbjlj +YjhPWC9Pbi9QblIvVm5mZ0FGbkhndGtzUkFERUJBMklnSUNZaGRJeFZvbmtBVVUrSGJqMmpyTlVG +Q3pRSkdMazFIc09UaUdjQjZ1d0JEWTVyVEtJWTNiMDQ4ODk0T3dPZk5BSEpjMXRvbkRtSTF6RnBZ +S3lBWWk1UHdDVng5U0VMb1gvNytyK1RYSCtCOXh0ZnNyeGw4VnAvTGQyU1hKMDN3R1lkRVRuaGlX +NmRtK2UyRHJYemJyejdZNGNWUFJhRVdXR3NnM3hrSEZyR0toUUJ4TGFkRG9nUHY5aE9MUFBqVXA5 +WXN2MTdpVUwwNEZzOXpPZXlCVTUvRWdsR055WEY3UEZNajRSRW5mRVJySGw2NENjMXpjWEFFai9x +c0Y5OHdod09hc0Y5c3VieFJzVWM4OWRrTHYrY0VyYzl3aXYrOElmcWEydnlmL1hxdWoyTHFDVlA3 +TllVZVRnNWlPZUZvenp6dkhKN053NHBYQjVQUGNGamJnd0lXOC9MalVpMncyVTlQMXN0Qit3NE04 +ZFd0ei9vbUxxejJSS2ZIcVpwclVMWE5uL1BuL0RsL3pwL3pKenpxbXovbnovbHovdndNL253Umor +STFSbUJOOGhPeFFENHptNTlvQlFKQ28zMlgzR2RKZ2JYZmtOeG56OFVHZ2dBUUlhRmNtbXFPRURY +VEhxSlJ2TStJdG84Z3JBTll3WXhrdjdjSGlySFhHdlBCZTJMU3VFZndqL0VWTDRiQzNhMHgxRWhn +WWxZZ1AvdWozOGpiZy9lakhMalU1RHVjNHFnbmZCelptZ1NMZ3dzM05RSGVOTmNlYzdEQzVMUDZm +M0dnUFArSkhiZGlhaGo4RWNkZGNoQXd3OGtMQjA2dHFWa0p3Y0IzaFM4WHpxd1ZHeWI4bThlZkFU +ZlJpZUdDUXl3WTdaVWJwOHdDaTMyMEFFL3czek00MUJ4ek91Qk9ZT0tLNTljRzhJOURPTVMyeHlH +SlUzandEaE5qNnlVODl2Z2NYZzRETEV3aFRnNmIwNnY5ZXVtQTgreTdILzRnSEhVZGJwaURmcW9E +djRyaXUxNm95eHFjNmx2M3dhTkduMkdGV1M3OUZGOHVPbFNmUVkrZTFXdzhBVE0rNEJNUGZySDAx +eVdtL2JSWFA5RVYzdXpqQlpqdG16L25UekZjY015ZjgrZjhPWC9pZFA2Y1ArZlArYk02K0VqK2ZD +RVZjQUt5c09LMkdCZ05KV1FKRVZvRG1tTWtwR3BVZ0Y1VHhHbXlpbE15ZDNzVkY2Ry9BUkZUem1M +d0U3Vll4SUJBK3hTQ0tQbVE0QTBSc1hrclFPVEVJVjhiZ3dUREhKSEJnVUJOa050QW1pYXBTOTFx +OGhiQjUyYzhmOUhZSHJnWVdVNDU0TERlRlhOY0xjV3JRZkFUUXRmREl3YXVDTTQrZGFqYitncE1I +UVJzVFRpNk9QWjZMcGE4bW14T1BHdDc4SGd1ZjRkNW5PbEY0L2xzblg3QjdWQXg5QWNPY2VUMDFn +TjM4cW1mMEloVmpURERheTFPOEcwT1RyV3FnOG4wTlFmejhhNWZ6RlN4dzVPM001Y0RkMHh1alhr +OUZjL2hBck44NGVYV3dxSm5hb0FSbitMYkE1TWMvcFV3bWhiVEdqd3pxTy9pMDhMZi9lUzNvd3RZ +WWRGM09PUmp1bXBNdlo3UkgwejRhOS9sRXZQUjRTRzRldFdQZCt2c2h4Y1hhdTVhZFVjYng2bm4r +SVpGZnY2Z1k3NVFqOXJtei9sei9wdy81OC81Yy82Y1ArZlBaNWlmUHorK1AvUC9BeXFnaFFwRFJJ +MGhnWTNBYUtxTmJUQXd3TnN2QVJMTkkwMHMzeFZhZ3hhVUFzelo1N2xjdnN1RFVLSkpBeStXQWlv +SXNlQnpPVERFY3lBb3dubzFkTTRlVFVPRzV6REZTSWNMQVlaOTNseG9XSE1qbGVncVhBTTJQSlRF +TmttemlkUzgySEtwaGJERXdvMDVkem5Gd0NNT2NHZS9lUENxUXlQRmc0c1l6T0dhQ2NVMzF6bzAy +aDBIRmJ6bUVra0hERERMancreDVNZS8vT29nUUhrSlZkMzRFZE5lT1BCcG56ckUxZ2U1WFBBek5m +enRnV2ZXNmF2WWhBMlhROUE2Y2VtaFFqVm5qN3J3NXJPaGZzWlhvMWppNHhZV2I1TndhYTE2bm41 +K1RUN2YvUVhzWXRNbldxVWQ4ZFNqZm0rSTVJTlB2ZnFPYjNuRXQ1K2hmTWM3UERqRWp3dGV1YjRa +OVBwSHIvTGhGcjZmMzBHUE43aGdsRTh1ZGJqYmh6TTFtblBIUjllVUh6SG56L2x6L3B3LzU4LzVj +LzZjUCtmUCtmT3orUE9sNkFvTmVKc2tjeGRFc2laVkVJRGVIRmlyQVB2OTFDNHc4ajFIaElUV0VW +QkpCbFFzenhYcERZS21pMjJPYUVvYWtBNEx1RHlMZVlqL1NKRFBYa1lpZUNJVXczN2tLbzVRV2lU +eEVZZmN5RGJVUjhEaXVjTk5TQnI2QzROK1NYM2lxMXNNK2RURzJNeFQ4ZHNEczFqcTl4a21OYlM1 +NXV4WEI5SDRMaWVCcWFjR3RiNW0wbUI3emRtRHQyTHdET2Rxa2crR3gzRGZCeCtlOEdFOWpQTGhR +QjB3OXZDVVc2N1VkejIzVnp6eDdTZGVjYTJGRFhjTW9BNzQ5Y3NGTSsyb3g0QkxQbm4xejd5OWVp +cVczaEN5TlRIdzlVSWU4MkxMS3g5dGlDOFhvOEVsUHo3RWtBOWVNV3JRSG5EaTI4ZDRlTlFUTWRR +S0E0eml5Tkc2ek5FTUhIanVJZGc2NWRRSHNYRUxNeTQ5d3l1OXFGbE11THErUmhmUG5EM2k1M0E4 +dko3UkZMMnJDVlkxcUhmK25EOVQzL3ladWZsei92UjkvcHcvNTgvNUUwYjVjS0NPK2ZOaitQTmxv +MGxCQldlWWlzcmQ1Z3FVQUt4aHNKQnd6MXdLdHBZcHhOSmc1Q2hjSEQ5QkF3cUFKaWlNSU9TeXgz +UGZZUUhPWi90YzhnT05ZSG1zSWJ3STY0aFJoRnlFUmR6V0c4eHB2M254RUVFSUJHOGdSek5nTFI1 +MUliWkRmT1JxVkVVTmkzenFsQytIMW4wbWNuV0lTWWlhWUszRyttd1BiSExJcDVaOHY3VVZ2MXhp +aUdtZG5PSVNnVGw4ZW83cmNxOCtsMzJQT1l5djMwUWhoNFBMR25uVm9BZHd5dFBERERjdzJXZStJ +cXFwSFZiVzRVOU5SRy9PcFU2WUhHNlB1SjRocHhvWVIvOXdJSzVMNzJsQ1RyaGM2alR3S2o0czhP +bVZIc0F1RnV4cWE5L3hwOWYyK1BXREdzdGRYR3YxV1QzVzV3M1JtMDZzOGZ2cm5xdFRQOXh4M1lN +UUJzYXNEOXpGczBidEJ1N2hveU14SFREbXFtOTlxeFlZVlR5ZnJZY2JUKzJ6ZGZhMWgvUG4vRGwv +enA5d3pwL3pwMnYrbkQvdE55L2UvRGwvcXVlaitmTlZvaEVNREVFSVVFTXhnODBNeWFDYUlKbTFo +Q2tvOHBIYVF0b29kdzJ0NlJVSGtFSjhOcWN3TVJDV09MZE9MRGpFUW9KN20raE5nMTh0c0ZjUjFy +cThOZEF3emJCWHd4Q05lRGpOd1Z3Qm1VZHlEZU91VVo0NVVBejF3R2t2am5EUWVFUXNuaVpZWXkw +Y3VOSmthOHlMWncwczZpWXVOWWhubmRqRWoxdTE0MTV0bm91RkgvRnJPUEZnMTBUUGlJRUI1Yksz +USsvODArSnFnNVdvNU1TenUwc2N2V21kRlNXTVlzTUFvL2p3NGFWbTBDdXh4WU1iTDJKR3NOYmVZ +Q3k1YWN3NnZjZS92bGxEb0g0L25jYkVrdE5nYlAvNVh3M3F4NGM2WVlGTlROcHlwdzJ4NkpVcCt1 +c0grbWtQdnNSeUVLZ05QdjlmV3ZDckIyOStoMTROZXRUNERnSjRYRERqV3orc3g1VjE2cEpESEJq +MWgwWndyUjc0ZThoYjZ5NVA0K29YVHVTaURScUdVUzd4Zko4LzU4LzVjLzRVWDEzejUvdzVmODZm +OCtmOCtSbjgrWkxRWW9Xa0VUY1FXN0VvRkJtU2VvdWhXRUNzVlFpQUNCYTRSclJlRXhBcUVRSTFz +T0t3ejM2a2FvQVk5aEJJVEg5cjVSS1RTRFJTRVlwaFR1dGlvcnVDOWMwazhsbmpEWWs5RWR6RjBu +RDU1QTZlRzBnd2gwUU4xVEIvUWZjeHpEUEVob2NJMUlnTGNWeWFLVmNQQ0hqRkZoTS82cEZQemVy +MFRCTjZFQlFQL28zV0MxZDR2MXBpVU9ZNUREZ1N3d1dQbmxudk04eHFoVmZ0aG5uL1ZETmgySzkv +MXVTUXVYMzRra3ZQNUNZRzYzQUpsN2h5d2FBbVFzdDQ0eHhuZWlJK0RxMnh0dnc2ZEh6MkRBZTk2 +SU0yZXBnN1JIQWlGaDROL1dJYW1HRzBWajlyVU5oaDB3UFA1WEpvTU9GM1AveEI5cG8zUjJzTXlU +QnFnOU0vUlIwTnZlVnlTTUNpbjNLb3gzcmY4WUlmT1BDc2IrTG9wUmcrRy81UTRDRzF3S2RXc2ZI +ckRwdmFtVjJkK2dPZkdtaFBMdGowR2I4dTljK2Y4NmZjOCtmOENmUDhPWC9Pbi9PblBkYUpOWC9P +bngvVm55L05zbGpqZ05SZ1lBQVR5RFBKaUtadkJnUWpMR1JaNTVsa2lBY1NPVFZORFZxQ05WWXVv +Qld1NGVJQ2lnQ0VLaktGWEVFdHhENVlrQkp5Yng5UzRJcTQzZ3hLRUliOEVkZWJpQlZMZ0RBYURn +YlB6V3VHdFM3RzZyRFdQampraHkzaWZqTU1RdVVuSUd2aHhJMTRMczNFaC8wNElVTGk5MHl0aVFu +djhWTXg0MFZzQjVIdk9NS0Juc0FtRGdHSkRZdll4S0lIeFBBWTlHdmllOFpBdUlCVEgzR2tkMkxq +d01DL1dzUlRnM2c5dU5RYVhSeEdnMUdMUld3NXlyRjk5bWZjZW11ckV6MXp5Rm1uYitwVnE4TUt4 +K0tKWTdqalQyM2lFU3Q4OEl1RmorcUZUZ3lZNFBrLy8rTGZmTk9SdXNvemJQcWtQLzYvbEdnUXQr +cjBmK1NMRDFqbG9DZDc1RXp0ZHdEQVFtUGl3Q3UvWHNKaWlOVURSQnoxaUlOamNmQUVEd3g2RVYy +ZHY4VHpoeDY4K0hEcGwwTzlQWjQvNTgvNWMvNmNQK2RQNitiUCtYUCtuRDgvZ3o5ZnpHQVNVQnNS +MG9ZQkFhQ2ZvQkZCT0Fxb2tBQUJHakVJdDhhOFovWnFiZ2c4TTVuM1hhTVVKam53QlF4RFJVZUVD +TmNZUlNDQllPVXpwM21HbVBEWkI0L1lpQytobnBtM1gyTjh6NkZ3bzhSNUpwNTlPVUFPLzdkeElz +TUxIT3BwUFBYYnEvbHB3dFdRZUlmZGZueFlhNzVHMWJCK2xrdTk0dGlmY1J6QjZMdmFOVTN6Y2xC +ZHc1aFBET1pXanhnd2FLUTRCQVZIRFdxdk55VzR4b1dZLy9DejM0MkFySmZmWGlaeXVPQlZmdjNY +NXhqNll1S2NtTjRiRkI3cjFTbWU5WHByUCszZ1gwdzV6ZVBFZWdhMVZxMncwb2xmS1RCWFhhbFRm +VEhvOVQ0YU9QelIxdUdoQTdud1lMLzFNS25GRzc3dmZ2VHIwWUdZTlNnZUhJNTY1THUzTmZiQ0lE +WmNhcXd4eGJkZXIvRkJNMnFCcnpxUzN4cjl6QjhXaDZFYU1tK3RPdkRncmo0eDVCTlhMdldKdzd6 +MFJUOXc0RWk5dnMrZjgrZjhPWC9Pbi9Qbi9EbC96cC96NTJmeTU4dUVZRFlnUTlNdGtnaTVnaXBR +VXlxNkdqZEFydEVhQmFTQVFFc2dSa2xBbEliYm8rRVZHZkJNaGdUUFlVQzArRDR6aC96Mkl0MXpG +L0lOKytSQWt2VXR0QUtRRDI3ekpiZENFeE4rSkNIV1hnSldYNGVHaW1HdEdEVzkydkFrdDNtNTFO +N0dPUFRVWVQwODFtazYwWm1IbjlsOEZzZVFBd2V0V2IxaTR3ZFhHdWFaU3g2OWdhbUhoeHJ3MGFH +T24vNzdmNTZZc05tbmo1N2pRVjV2YW9nUGZuMnpKaUk2emx3MFlLMTY5U3JtZnp1MEdMOEhBSHpX +cHBidyt5WEdzWmVoeEhWZ3FCOTJhK1RXTTc5R1FiejRweVY0OUtRSE1nN2xFMDhPT0hHcGZ2SEZZ +UTQ4aU1GOERqZjhxZ0dIY2pCdjlISjc5VmxOcmFWNTVHZFdkY0NxSi9xb0hyem9xN2pxclJsOXpx +Ri9tb1BKR3YyV08vMjRIT294NTVsK2loVnRYazU0NE9hSmFzdThkWERwN2Z3NWY4NmY4NmM5Y3M2 +ZjgrZjhPWCtHZzhNOWY4NmZIOVdmTDgyMGtNbmNOUk5RSWlJZ1NmeWxZaHNxOGdwVFFPSkFKcUpx +MWhyZGQ4UXFVbUtYOVpxbE1BWW9XTUtSbjVrVjZidjRpb1BCY3hkUkFoN2NOeWVlWnk0NDRET1E2 +NkNReDNycmFqNE5SWmI0bmhHS2VtRVhwNE1vRWRqOThGbUxveEI4TVR5elRsN1BySVZmNC9zZER4 +clF1dFR2MGpSY0d2REtZWTg2N1RFSHF4amlFM3FFZDNXSHI3dkw0N1BlNExJRHpwLzg3ajlMak5i +SFVQYjdERGZ4d0k1ZkpzbnppMm1QZk8yM09oK0Qrajh2L2hLTVJPVS85VmV3RlpoMU1GdFhMUkd5 +MmwyZXhjUW5hUHNZd3Q0ZTJuUWtuemMxY05PU25waXpud0Zod2hXTk9Wd1lSTzBPQzNoZ1U0OWF4 +UEpyQzc2WFkvK1V0VnBwR21hODBvbzhqQWNqelZzZmsxMHREa2c5ZzdNSGhMVnl4S0IzZU1IbldY +cnpwbVZ6Y01JdHJucjBURDJlVzQ4REhNRml6bDYrd1ozaCsvdzVmK2I1eFp3LzUwLzQ1cy81Yy82 +Y1Arc3ZNWTM1ODN4NHVPZlBmL3IrZkRFSlV0czBwRlFvRlpuR0l0MVA1eVVaU0EyVUJKbU5ZYTND +YTBhSmtHZ2RBdHA0QUJsWERtdVJpd1RmR1FWaHphTmdPQlJuci9WR0NYWjU1aTJBT0liQzVlc2xq +c1laRmIxOTZwWFRIR3lhMElIY0NrRmVHSHFKNmE0Sk9JVFJXdnNSYms0T25PQkhnOFNRcnp4NFhv +TVNtUDE0Z1FkKytQRG5tV1paWDFIZ2gzaHdaSjNuK0RIMFFvNys3amY4OHFuUk9wZVlqN0MrRDFi +NEV2czA4UCt6ZCsrN3RuWFpWZGpYS3llUkVpSmlpK0NMWkFRR1EwQmNiUk93WE1ZWFN1VmJWVmxn +bklqSUQxQVBrcjhpYzdiUmwvNXJjN2RUaDFJZUlIdXJEV25XWEd1T01YcHZ2ZlhXeHFjOXp6Nm4y +bHZZaU5kK0F1eVEzeHVVLy96RGY1RTk4TXIzckdQaTUvLzNDVGJ4bUViOGFnZi9jdHZqT1k3RUU5 +Yys4ZFNxcDk4YVZNMjRwUTk3Y0MybmVEU21QbStEdkNIeW1iN3M4MDlYSi81aDBTYzZZYkFhVUR5 +ZmNXU3ZmTFF1TjI0Y0tMU3BCM3JVM3BXYjUwRDZMaWFqUDJzY0RIUmhEbTZHVjd1WTd2TGh0NGNL +enFzbmU5MGRCdlBuL0Fuci9EbC9xbHR0OXVGdC9wdy81OC81MHpWL3pwOGYwWjh2Q1lqQXZac0Jk +Z2RLZ1VBakZLaVNJcW1mdXZzVE16Q2E1Wm05MXZpT0pFYVMwRG81Q1BjeDJWc0VXMk1nQzBqcnhK +RGJ1b29MOEZ6WFNBTW04Y1d3Rm00aThQWkJJeEFnbGp2QlpTN2pMYy9GbGtjOE5XcWF1anFJUlQ3 +clNub3g5bGxKRlk5bzFBMnZab3JudS93OVBEVERwYm51c0J2aXdseUQ0RkVNd2xNakUrSmZEQmps +WmVxS0RuOTRmc2FYeENEV0dQUnF0TVorT2VHMC94bHZYNDJDUHpIa3NGWk90YmcveHZ2cDc3N3Jy +WC9oUy8vaHdRZGo1ZTJQY1QzQXEvcGhJMUFtZ1FXdjZvSXgvMHJZQ1pzaEhreGZIb05lL1hSQ3lL +MFhqK1hBSGhnOEY0c1dQUFAzT2Z6RmIzUDBhNi9hL0lkREhmREFvamV0eFJ6OExwL0Z4Sk9lNkcx +MVp0NWNEeWc5ZlBUeWNJTXJsNTdLQTVmbmFsQ2Z1c1F6SndiZTRSUVhMKzZlNnlsYyttL3QvRGwv +enAvenAzWHo1L3c1Zjg2ZjgrZjgrUm44bVg4RjEwYmlpOUNPQkFRQXgweWVLd1pBUkFPQVdJRTFG +RWlKN1BVWkVERVZvbm5BVmRRMW5PZSs5NmRuWk1qUnB0YjBBREtCeTd3NGloZkRnTTEzK2NWSjQ4 +NkVpRWNDclBiQ3d5Q0UrWXhId09ic2pTQ3U4WDJ6ODlQeC9ERTd3c1RYWUUxM2x6Zml2enpxZ0ZW +OC9JbnBMbDV4RUtKNUhNS3BabXR3WXVDdnZKaURUWjN5dWNzbm52bzB6ejcxd3dhUE5YclVJWGIr +UDRNWS9kMmtjRGpvNUEvL1o1U3NQUzQ4dzdHNE1MdTNEdmx3b1U1aTFTTnhIQUF3d3lvMlBMaC8z +cGk4ZlJXaE9kaGRjQkt1TlhMZ0JTZjZJNjlCTTdSa3JkenB3Y1hDTVp6VzQ4R2hJMFp5WGkyTXlK +eCtCNTRKNmNXK0hvNE9IN1g2bFFYZnpZdWR3Ly9Xd2NuZzh1SkNidi94RXBzWFBIZlpnMU00Y084 +L1R0YlJpSHJGYnAwT0xIdXJyZjVIVUw3bTRTVzEyeXNHUGVJQXQ5Yk9uL09uWnpnV0YyYjMxakYv +enA5eXpKL3pwekYvenAvejUvejVFZno1QWxnVEpGU0FoVFpKYktPNytRb1NLYjdYVkFRZ01IRWcx +WGRKckplZ3BoSkxZdnNSN2JsOENOYmduOXhQM0loR2tnTHRnUTBlK1J3R2lxcDVEWVhXZ09KNHkr +T3RnaUdYV0o2N0dEQnpkd2dnWEV3WHJJZ1NYM1BnSXE1blBELzE0MEE4RFRHdlhtU0w1enV4aVNV +SDdIZ1JVK00xQk9iK2NiNzZyY0ZCRzJJd291ZHkxY2pNcC9GNHR3NUdzU3BhbUpqQ0hua2lpQnZ3 +NEVVOWVpSWVEc1JRajlpK1A0YitrdWZXd0NabkRTcUh6L0xnamRod1Z6NFpJanpxMDd2b0huTitp +V2piTzVxdzF0OWg4TjJjSVJkZWNLRTJNUXkxNEVJZW42MnJnZVRGbzdkQk5BSnpqY0RzZEtRV3Bp +UnlleG1XUVh6SEMvUGFwMzYxeVc5T1hIUHFrY2VjMkE0bE9HREhuWFh3dHMrNDBUOXh4RlczV3ZU +T1pXOFBvTlJ6UFJQYjVhQ0JHUzZ4MUF5TEhzczFmODZmbnMrZjh5Yzg5cytmODZjeGY4NmZucnZt +ei9uekkvb3pQNEJhaUJCdktTeGdQZ0pzTWtTNGtFS1VQZ3NvdUgyRXBoQkNhanhrS3FqaVJScGdn +TnV2U1gwalpVK0ZKNlk0MXNMaVdjWHVlK0pjc3d4enhOVmlrZXM3UW9rV0JnMXFmVEVva1IwbStW +enlCTU5kOWlPMHc3cUk5dkw5YkEzdUVjZlY0YUJCS3Q0WVFxTjg5cmJDT3AvbEYwUHR2c05tTFp3 +T0JERTBIdjV5clY3TnR3NTIrenh2VFpvdHJ4N1o1N3ZSZzQ2SThPbXVSL0REWnM1KzYxenk0RUtP +NHBBTGR2dk1NNmFCVy9qRVVwUFB0R0lOWHNSVGo5N0M1SGtOcW9jNE00ZGI2NWtISGdhaUEwTWRE +Q0l1N3RYbkRyTkRtS0RWWlQrc01CdjY1MWNNekhrTEJEdmpxRTA4dk5HU25HTGd6ak8xdGkrNHdn +UE04akV4ck9MWTY0SlQ3ZGI3L295MzFJRVhkY1BHRTlVSGJISTQwT1ZWby9qVzQwVk0rMXcxNkxk +K2ttLytuRC9uei9uVC92bHovclIrL3B3L1c5LzhPWDkrTkgrK0tsS0xCVUtFb29GRXBpTGRBUlZJ +Z3duTmMwVlhaUGFMQlpqdllyZ1R2RHRoV1FNa3dqVmNNODM1U2RoZURRRlUwWURLWTE3aENQSFpI +WGlFYUg3M1dJdDBoTUJxVGcxd2lLK3g0aURiWGpWNXJsNzV4TlZjQm43RzIxY1JPWXpFVXJ1NDhz +UGlHYkZaUzdnd1ZHak02VmNFQ0VCdDhzbnZxcmk4WVRCdmFCZzhlSlZQOC9Hakh2dGRST09PUnow +akFMWGlBSjRhRk9meTZJOG0yMWZ1eFBaWjNmYUtENHYxZnIxRWZYTG9xN2crcS9FeDNwZjBEVjhW +czdwZzBTKzh5RzBOUHVEczJoNWMxaE83ZGJRQWs1cjFEbS8yNG9TaFlXbHNlTlVMSXgyMmQrSTlC +bjFMRG44aG05YnlOdXJpbFFzNmNNY0hzOGdIcjF5ZTRjUjhjVlpuektKMmR6bXRVNU1jMVdvUEwv +V3FGZCswZ0RjYzIwOFg2bFNQbkM3MWl5bVdPWmQ2NUxLdmZaOC81MC9yNTgvNWMvNmNQK2ZQK1hQ +K25EL0YrK2orZkxVUkpqVUtLWklpQWtoZ0JVSXFBZnVKVmhCckpHMGhBRWxVWTBoc1h3c0N2R0t5 +aGpBQXFiSGc4Tno2TnFRTmlnSHZVcFE0c0JDTE9jMnh2b0lzbWVvUkd3NllZUldiNlZ6MkVDdHNG +WW8vTGllQ0RzMlYwejQ4YUxLWWZzcTNSNTN5WmQzaEZaTjR4QlJQVHM4MXpuN3I1WVFURDU2SmIr +QURmN2pGcDNXUDRkN1NGelV3dEhyRXc0MTExb2psS25hQ3dZZjg0c2dyQnZ6SmVaaHdKNDREU1N4 +NUdhNjgyVzhQdnRXclJxYUR4M28xaUk4SFFyUld6Qmo1di82WHJMWE9QT3ppd0E0UGZPTEpRNFNl +aVJXRFhtOXdnVWY0MU9oQUYwdXZpUjhQOEptejVqa2tuMy90N0lmZisrWHNkU0NyV3l5OWtGLzlm +U1BqTTUySXJXNzQxYTR1QjZobmVGSTd2UEtJQTZPNHJRZHZhc1c5V013dWg3N0NKYjhlT1RqVUNt +UDR2M2s1eFpEWGM3b1JYMTAwcmxmejUvd3Axdnc1Zjg2Zjh5Yzg4K2Y4T1gvT24rcis2UDU4RVlX +SEZuam9NK0w4RkkwSVlKRXNvTGNlakdTTllNZ0NHbkdBK3E1b0JRQnFUNUplUEhNYUNGekVkTURO +STlwYUJ3S3d6ZGQ5aWxNd3NJQWprTkVKUlJ3a2lHT1BOV253a1JVTTkxMU1zZFZqdlNHMzJCSHM1 +VVlRd29sRTNBNk5DWkhmRUY4RElSY1BZbG5uczdsZTFxc0ZINjU4UDY3aHdxRVlhbjhPbXdkVEcr +aXlIbDlwOEltaysyR1cyNkhoZ3MvQklOZkRpL0VsR1BSTC9mTDRUblJxaEVGK2ZiTVc5aHBGZjN5 +WGp5bGdEWTdqdEFiRmgwTlZQTHpyRVFGYko1NVJMY2xMUjhuOUY4OC8xYzNBYXNKbk5IWjF3YVVQ +OHRqclB3WTBBQ2NjYXBRSFRudlVBS3ZuclpzVy9KOFQ2N1cvSDZDbnVCZmI0ZHUrZUhzRVB3ejIr +SFVHOGZCa0xjNlkySnpMSWFZUHVCZER6WERRRnk0ZVhwNy9NMTg1eFhMQmJaOTYvSVYyK3h2VEJU +czgraWdtVGVhZ3U1N0M1dnY4T1gvQ1BuL09uL1BuL0NudS9EbC9Cc1A4T1g5K2NIL21YOEgxQWNF +R1VsME1CU2hEYUJ4eUZJVWNJRjBJdGc2cFB2dExzcG9Fdk9lQWlXdS80aldBK09UVUpQc1lYQ1Br +VXJDOWNsbHZEdmx5K2d5NG4veEQyZzFGSWFnNUZJb2tqWU92NG5RbkVHc1JTWmcrd3lFM2tpTHlF +OHEzQXo3cnhCTUg2ZGFyMFhQMU9CREVoTnNWRVZ3ZHVOSmtNY1RHQnhOMUhad1I2elhmMEJENXhS +WkRQcy9VNWk2WCtzVFdIekZoTUFjZllYMDE2TVhFQjROYUM3TjREaXFHRXdPWDN0TFlYNk5ZSndh +K1lORXJ6L0dsRGozekJzaThXSHBCZFBvUWN4OTJoeFUraUxZOWtJOXcvVVZsV2pOZ2hxVm1TMyt1 +SC9iQkJidjk1Y0I2T096SGJiVmtyL1dHdFgveVc3OFkzaG1VWm1oSmJ2K253bmlIMXg2WUdFai9y +TzloNGpPZTZaNSthVTFOZUxIWEhPN2prOE1pNTFQemM4akNadDRsdDNydzRoQXcxNWdPRG5YaHZq +MXloMG10N3VMUG4vUG4vRGwvMHNmOE9YL09uL09uK3FvTmNkem56L2tUN3gvTm55OFRHaTF4a3lC +VmNVZ0FWRUVhUTJSRXBYbkFJbGVqeklsQkpCSUxiQi9peExjV0VIZENBVVFlWU1YNGF0eUxrY1Bn +aXJiT25najV5TlJNaFZrdm56Y1ZTRkM4TmZJZzJPZVNCN2U3eHNCbTdiZmlzWTVwNURHbjdtK0hX +RENKTFJaTzFLNUc2ODFwampyaFI2cDVjZngrUE16bEFCK3RYeHpQQ0lmb1lWS3p2V3JFaDJiS29Z +bnllZVlTdXh5SndXaUVyUTg0N2NCWjMwcG9PS3dWclRtQ3c3Kzd2SVFwdG5qNGdvMG04S00yQm8x +SmozZmM2UU9NWXVHcEIwck5VbzVoRlZjY2hvYWo4K2JzVTVlM092SXhqRGhkSzJlMGNUVVN2SDZx +b1dJT3R2ZTY2ZUxQZnY5WDh6WklyZkxyai83K3hSLytvMkJXVi9FOGV2Z3UydEJmRnk3b3lsb0hB +bjdFMEFkN2NlbUNOWmdPYS9QalV4L2dodE5lK1BYSkd5KzRmVy9QN0swdTFLRW1lL0dwTm4ydzE3 +UDVjLzRVZS82Y1ArZlArWFArbkQvbnovbnpvL3Z6NVg4RTg0QlFKRmVZalFoUkRBS0JWWUNHSUZZ +UmZxcjNVNyttaEx4TDRMazlTS2x3UFZld1BJQ0pZMDVCOGpDOUl1eDFSMGJFY1dSWkw1OGl4RUZB +UlAzWGY1VTNFL0RtVUxnNTYreTFUK043YVlZbW1ETVE0Sm5ZU0lkTFhtdS9IUm9vaDhzYXRlTkdy +ZkxpVFBNSVF6UGdody8ySC8zT3IwUWtxZkVPTmx4WnIwbSsrd3p2TTc2a2NUaURNVHdmSnhIbU5V +emNpcWZjMTdUbXhjSjVEaTV2bkk0ZjMvVkdISHZnRTE4TVhNSUZ1K2UraSs5QVVUTisxQ3F1TnpY +aU1wVEx3RlZyd2tjTjZpNC9rL1V0bGJWeXVuNXEwS2ZlYWdsZnpLNHVPc01CWGFnZnZ2S2daampO +eVF1ZlN5MXFaaFFIbzdvZFR1cFJvL2dNYW85OGV1UkFFQmNXMy9GRUQzUkJTN0RLeDBScXdZdDQ4 +bmxtcjJldWJ3MUs1N2dveCtyVUMvcmpOUnd5S0V3OWlHaUNudkxHOHpESUs3NzE4K2Y4T1gvT24v +UG4vRGwvenAvejUvejVtZno1RWd3aEJXdlNRcE9DbTBleW9NQXJwRVlUR0dnRk5MSDFuZ09JYkNE +dDkwd09iMEFRaFZCem5pdEFJNEFXd3hvNXpNbWpJZkw0VGhUZVVoQkJEV285QVdpNG1PTEJZUS9D +NEdZV09RdzF3Z2VYZWpSZFU0bnUyMEV3WWhHSHBzRmluejNKZlZqVjRiQ3BhRFZlMDd3UnNBNCtH +UEJwdnpzeGFyUVlPV3p1VWcvTWNybkxSL0Jpd1lFejladXpUOCtzTmRlM1NqSEc5YkJ2Y1J3KzF1 +REYzVFA0M1FuWHFCbGdaMkFZV3lmc1l1QlVqVDBBOUtraXg1Mit3b1ZmY1YzMmlJTWpHTVZXdDJk +aXdPd1pmcXNQV01UQWpUbDlzbzZRemZrY2d4NVcvSGptb2pORFRuOFptaG44UHI0WXNNakp1SGlE +bllZWUF6WjErZFVhT05TclAvREFLcjhlV0FPRFBqSTRRMXFyTjNqOTFxRDRva1Y4cUZNdmZKYlBI +WWMxSUY1Z1ZUdHNQWERoYWcvc1VlUDhPWC9Pbi9Nbmp1ZlArUlBtK1hQK25EL25UemsvcWo5ZkN2 +YkF2Y1FTbHNXZUN5b2dRQW9qZG4rY0RhekF2aE1GZzBwS0RKNEIrZ0IvQ3lsaW1oY2Z1Y1JVOFZx +UEFBVWp4R2NpMGdoenlFSjR2bC94OXNLcWtSb2twblZJOXB5NTVTQThXTzN4eHNBNmd3aXNWYU5t +eUlYQXA1YWZEcHlvRVM1ckdkNDZqZGVvaXMxQkJvdjFtbTBkd3lOYnpmYkVvUGNkbno1YkwrNHp2 +a1FFWXNJaVByN3dnQTk1UElPbEJoT3JNYnl4RWxjUFl0QXpQQ3p5bUNjRXNZbEFIUGYwNXRhSlVl +NkM4WEtaeDJINmR2M0VGMkgxQUJBVHI4U0ZQeldxR2Q1SGxNOWJPRDNBbTB0OGExb3p6anlER3o2 +OU1pZVBuc0pVTFZUUXJaTU9lK2pBeTdnR28zb1RoTzhlNnJCYjkrUGYrM3Zoem5vOCtqVUd1T2pG +Yy9YS2p3TjRySkVQVDdEQ0FwTzg4dUJHWFB1c3d5V01lTkU3Ni9UVWtOTnorUnBMVFhpbWJUM0Js +Yjdpa3k5Z0VHUCtuRC9MM2Z3NWY4NmY4NmN4Zjg2ZjgrZjgrZEg5K2RJNFJwUVF1UXBpTHVSWjBD +SnQ5QnhCbnJrRFRQeUdwRVFoaWVjU1J3UVhVeUgyaWllNW9vQkhpR0tzQi9CYlVjRUZBd043RTVI +RDRIS0pYeEcyZ1FqejNIcWYzY1VURi9tTXJXRTFLSExFUlJoTTd2REorZTFvemZnUlV3N3hJdVNy +Q1djMVo0WGlJTEhXWHdwbUdHSWpLSHZFMTBSeDRJTy9velhnQnJlYUo2Wm4xc0ZvUHc3ZzBzQVk5 +UGdVMDU3SFFFZk5pYzUzV0dIWFAzdHJVSmhnaFE5L05aTFl2b3RuclRzT3hiVmVyZmJvajVxSURo +WTQ4ZW51UUxaR1R2SDBWSHp6OXNDdGY3UUJDNHhpMEF0Y2NxbkwydFIzK04xZHNLbXJ2MUtBZi9I +YVYrWm1VSWVqdnNrUGg3ZzBKSithOU53YkkzZWp2T001bkYxOWFoTmJMZFdxV3ZEQ2tNM2QvSERU +Qk53d1d5YzMvZVBXRzBvOXNGWWUvVmFUZlBLTGg2Y1kvdXFFR1Q1NzVzLzVjLzZjUDhXYlArZlAr +WFArRkcvK25EL2grTWorZkNsRVlJczF3RVNFY0kxeFowYUYxMlFDSWtteGlHaVJBSG11U1loaENr +SUJSRXpQWXI0VGxNdWNKb2tycHRoTWFKL0N4Q2QwT1B5ZXRpTE5JOG13SHpuTTVoTGZlalcwYUhm +N2tFMGc5aGdJVUpmNktsejc3ZmwySUZJK0dOVkdGSEJaSjdkYU5iQk45OXcvZ1d5dFJqbEVOTFA3 +UE1lekpzQ0todzVjNE1BZFpySEUxRGdOdDBkT2NlQVZCeTdjcXYvaDVRNnVqTGVJM240WXJWTW5B +ZGl2ZnJpWWp2RGt4WTk1M011SHV4cENENjJsQTRLVFR5L1VCUWNNNHBZUFBGdHZMeHpxdEJibjho +a0VUUTg1bks4MmZWY3JNN2c3Nk9BZ2FQdmxnVmtkK3VtT0Q3bnRNZHdaMUZ1NmFxbll4Vk5UK0w4 +NjljbCt3M01IdUR3d1ZxL05yeDYxaVlsNzg2M2JIandaNW1CcnYrVEdsM3owOEMyLzd2cU1uL1R5 +NHJ1ckhWNmF3cy84T1gvT24vUG4vRGwvenAvejUveHB6SitmeFordmdpWkdZSUFXbkVBUUxDSFJt +QWZlSEpKdDlsTzNKSVFvdVQxdEtnRWFRTm9yc2IyYWkwaEZJOXhhYndkY1lzWEVWNENHZXhPQUZN +L0VScUpHS3hvbW44V0ZHUmJyeEJXbkpNc3B0c1pGUENlTVloUVRNZXJ4WGF4dkI5eEl0NlpyNVNR +T2U4U1JUeTcxNDhjZjIxdnJkK1NOTnN0OG15cVhHRDdqenVWZ1lTamNGSlBjNmNmdEZVT3ZHTWsr +YXhoUFRCeUxwNzVuZk1sNmVSaTNCeFFqNGtMTTRwZlBYdGc5OTR3T1lDR1M5dFFGb3h4eTFpUUVC +b2Y5MXVQWHNGNXVPdEFQbi9YU1hnTnYrZ09YT2RqVTIwTmJmRUtWazJaaTlNTXNIbDJvTFJ6Y1JV +ZUduRC8rM2IrYlh6K1FTd3pQOU5VYk81emdUbTM2ZzFmRElTRy91bjJHRVRaNVd6TnVyR2tmNWZV +WlAzQVpNRG9VelBuTWhIcXJmZ2FGUTMzV2kyMi9uc0tEQTN2Qzk5V0RTelhPbi9Qbi9EbC96cC96 +NS93NWY4NmZ4dno1V2Z5WmZ3WFhScHNJMHFJMkMwaGtJTk1helRWZmNQYkdvRGNZU2pJeDJoZ0RT +SVRiMS9WRW9YQTVGRlR3d01saFRjWGtjNHRnRGsxOHhwY1U0THVjakdFZHNZcUxWR1NMeWRqLzZR +Zi9NSFVZaW04c01UUVJCdnQvT3I2a0VmSlhDRDZyRTFiaWthdUM5YWFBS0Z4RVo1MkJKMXpBZ1F0 +N3pNbXRTYzk0L2xpZmdJaEVQRFdrK1dkd1JsQUwvdUQyekg1cnhKZmJYUXlEU2RSbFA4d09MN1hw +aXhqcTEyOVhEd2E5aG9zNDJ0OGNMbmVIR1hadmlIQ29uM1Fobmd2dUNQT2VOejkrNUlRZDFob0xI +dHhhWTE1ZWZLcEwzWEs0aTZldk1NQXJCcnppTUo2ODZzL3pxOStBMFpzZjJzRVhFOW92dnIrOERa +ODljdW1UdUlidnRHWnREd3J6OHNQak1tZWR2UGFWbzVyZUduemI1NWxjT0ZBbmZ2WEFNM3piYjA0 +cytmVEVaM254b3dkcUZjY2MvUFBuL0RsL3pwL1d6NS96NS93NWY4NmY4NmY5NG45VWY3NFViQklB +Z3BIRU0rUUI1SzRwa2pTWUlFQ2xxSGR5Tk1KNnlhMFJ4d0FLNFhLNEVNU2c3dVlpamlPVE1HRVFC +ekRGaXk4bTRjanRzejBHVTRrSEI5eUlrWWVZNEJNUGpocm1MMy84NnlHeGV5dWVtdGdiSlBHSXgw +aFRqcVFhby9YQkliNDZFV25BWUczZVlGMDlSRktjak5KR21yTlBISEUxbVRtOVJjQ0p1T0o0VG16 +V2l3MHZEc3lMaHljeHJmTmQzVGg0RHFDTGQ1ZDU0cGRYUDh1cHc5YysyQW13UnNLMTJzVFNtK1NC +NWVxQXhadU44dWNaQTFWUXZzdVJtdi82K1V2YTRzb0ZnNWcxbzgvQmVUWHJDeDVnazB1ZDl0T1VP +Yy8wRTA2OHVWdkxvTWwzZFRDZlhodTQ4cXNJOEl1ckQyTEM1ZjhvdHozWFQ3Mnl6aEJMVERVR3ov +R0VDL2xoWlRJYzBBZGVZSEhCZ2dNNDlDOG12cDdKcDA1bU5PekJMeXhaZC9XNXR4L214YklQSnIx +UkM1M0FKdS84T1gvT24vT24vUFBuL0RsL3pwL3o1L3dwSmx3ZjFaLzVGVndMRks5WXlTU3lHQ0JB +YTFCTjhWTzNaUFlvMUZvRUFhUlpZa1I4bW5WRFlrbk55UUdjZUlnQ1FnN3JQVmUwNzNLSjdYa1BE +SmVtaVdjb3RqRXFaZ1Zyc0gzV3d1ak5qOCthWmNndGw0WVVtemdNcXBadmg3cmcwUVFHZ1VGTXRh +aFhQY3lBTTRJMEx5YVJ5R0ZZaTdjMlhheUsxYjRhbERpc01TK241dnJlcG1tazcvSVREc3ptWE13 +SlM0UnZYRHc0Mmx0NzRlOWhCenNScUE5M09JTFJuSGo2QUxjNU9hMFYyM2ZEbkZybDlGblBZY1pm +TVJDZy9OYnBvUjc0WHRHclc1ODhNMjh2RHIzbGdnR0grS2xZWWZhZEx2b3Z1bGxuemhyRGQvOUh2 +ZGJoc1daVlZ3NnJxOFYzV25GZ2UydHA0SWNlOElFSGNlRE9nZUhBUGszQmhuTWN3cUxtNHZiWlBi +MCs0OE1rTmc0TStXRHdIWWM0OE5rYTYrWHFtMWV4NmhPOWF3ODlNMmVOWEREaWZQNmNQK2ZQK1hQ +K25EL256L2xUN3Zsei92d28vc3dQb0lvRndFYUZXQ3k1UDFZVlFGQjNBdk1Yc3hHRUtJa0Y4bE84 +R0lMWE9DMWVNbUFrMDNqenhDZCtDL2JyQlRXblF1UkNyamxFSzl4K3oydFFvclRmUG9Tb1EzNXZN +ZHdkSW1MQmlIVFluL0VXZ2dsR0RQTythNWpHZElpamJzL0U4YWFwOFdBM3A2SFd3ZTA3UEM0Y1Bj +MDkwUnpwL21pK3ZOaXZMdngwalNHVzJseTROYWR4NmlNcy9NcURDN2p4b2s3ZkNSU21DcFdvZXZE +Z3JVWVFUMzU4aWFGdkxyekttUVBoOEtyWit0YjA0SHo3S2pnWXhjUkhlYmZYK3VjdDBsc09IREho +YWc4WUlSeWY0QTFhdzZzY3RHZVB0ekd3NTQvMEQ0dWFmSWRYcmRiYjV6UHQwVzNyWmhLL0h4OXpY +eDQ2b3o5MSt3K0xPTDRIejlYc3UzenkreTRPblBpQlhUejF1T1RIcFRwaE5ZY0huOFh4WFZ5NDlj +YWMvTGlnUGJYalR4eDVIRVQyeXRmRDJtVnZEU3JHL0RsLzRuWCtuRC9uei9sei9wdy81OC81ODdQ +NDh5V2h4TWdSME5zQ3hNZXMxd1RGSVY3aEVnbG9EYkVJVE53SUp6Z0pBUFU1ejI4UWdBWVRwVmlL +RVU5ZWVleXBjSmtHc1lBUnVXYkkyUUxkRldBb1V2TVZJeGFDNWZEMlFkUFVaSThMWm04QU9vamUr +b2p6NXNTVXU0ZUtJWVoxOE5XY2NNRXBuemxya0k0TFdNVDBtZGtSYk9EVUFXUytZaVd1TlBxNDZ0 +c1M5WGdlUTE0ZS9PSFpIcHpZcDZZMjBETnJJcVRyRlh6MlB1TkxSR1o5UmNWRVlsUU1OWk9EUVgx +RXpXeGlpSTgzMk9WUko1ekIrbC8vUzU1VjlPS3JRdyt6N2dSdkRXN1ZFVEVmRnc1TU1mVmZ2VEFX +bjVyRWZBVDkvSjlGTzlUVUlHWjBkVEgwUjczV3c2a21hNTlENFF4NnVmNzgrNzhXclVaYkYxTU0y +TlFuTjAzN2RZVWUwSExhUng5eTRNQWVuTGpqU1k5cEdGWTVQWWZKWi9pc2NhVy9wMEh4OUthNHFr +RzV4TWxCZEZ4YUI0Kyt0Si9pMXFBOStPU2RQK2ZQK1hQK25EL256L2x6L2xTVFBlVm0vcHcvUDZJ +L1g1SmFaQ09SYUNhUUZpaU1LZncwNjE5WkloaHJGV3NmMGdXelQwTUFCMUpqUEdkY2NTUnQzSmpn +bmdPRlJLTDB6QjVDQmxScy85U3dlVUtvVVN0Y0F5R2FwVkhtWUJVTENTWEtyeVhZcTFCck91enhL +d2thcHRrdTJJTDVmYWdIeHNZVjA3dzZZSUJaZzYzejJYN2lKM2ovckRZdWlFMHpORWd0WXNCanJU +aDRlOFQ2SEFqaTZBTjhiWmFhNFdQODhIVDc1QlhmYzhLQ0FVWmNHMHhTWG5DVXVUT1J2ZGJEUVhT +TUJJTm5ldXFBd1lrNWUzRk1SR3BTaS9YdVlzTlNibjNIRTF3WjExLzF5S3VmNnRBUGwvVjZKSWRj +MW9nalhneDZlMkdpQlhGOXRrNXQ2bVVzTlhrbUZoNGVmTStiSzBZUVN5L1VZNjM0OGxTZjZ0VUR2 +TkUyZk5aVTgvTFN1WDViQXhNdTRKY0RqL3FoTnJ6Q3FQYlUrOTR6L2RON1F5NjR3dWZsc0VldDFw +dHpoNnYxZEkwYTU4LzVjLzZjUHhzWGQvUG4vRGwvenAvejUvejUwZjM1L0FCNnhRQXFvSWNLbElD +d3pCT1JuOWdaMHZNMjNqUGtOQWFRUUFPb1FYNUNScXA0bmhOOG1uaU5EcGxISUdMYUlNUXBWbXcv +NVFOZlVkdXJFUGdNeEhtdXFCcVptUHpralZ6NzRCWExjM3M3RU1XOE1OZ3JoaG9lVXoxRC9Kb0tk +bmsxREtZMkFXZncybHZqSWxZeituWkFUTmcxMUI0OHdRb1BITS80OGhqbzV2R0pPL3VZVmJQa2JO +NXltU2JlSlI0TWFpQ3N4b01WRGpVUVZyQWREN2pwNFNDWG5zT0RKLzFXTC9IQWJMMGNjS3ExTlpV +M2NTS293NDZESi85YkRnanh6WXNEdThPZHVlZ0JQcm5ObFYrMU1hM0xYbThJUGZkWi9Bb1hmbjM5 +YXBCN0JwdUJVN2pvazViVWpSL2YyMHNZNFdMRTF0L3YrSVpWVE91dDdWc3hHT0NWRTFmVkFKN3gx +VDF5d0tZZk5haDl0RXBUT05WZnVYR0JjL0hLWlM5eDVhKzM1cy81Yy82Y1ArZlArWFArbkQvdG16 +L256NC91ejVmTkdxbElBWkFJTENNSmFoRUFBdmt1QVJCTmlsQUprR1kva3BBSXNEbjdpVnRCOWdE +dFFnYmllZ2o0N00ySytENkxqUUJGK0c2TnRRb25BakdRU1hqV2FFVE5xem1OcTU3a1AyTDlwRytv +RFptd0VvSThDSUsxQTNaRUk4dmV6dWNndU5yRXRJWjQ4U1VlSHNSMGRlQUVObGl0OGNmNmJjaVQ3 +MHZXK2F3T2dpQUdNZFNtVWVIN3hPYVptbkNKUDNkNE5MeUM3YkNQS2N6TEs3YTR2dmV3VVFkT2NQ +b3RSblBxemdGeC9XQVNwbk1aK1BLNzRCSHE1YlMrQm8xWlR0RFZndGhxMVU4NUhERFBlTXNlK2Fv +UE90TlhlL3U3NWpDNDFGZ1Q2QzhNdnNPcEQ0YlAzblRwdVZya2hhODErK3hPbjdEMGtMS3Vtc2V6 +dVBhSTUxQ0NSNDF5V21OOStEeHVlbmpCcUViNzFHMnQrSWIreVFtbmRYaFNLeXo5K3licWQrbTV2 +UEpaTjMvT24vUG4vQ24zL0RsL3pwL3o1L3c1ZjM0V2Y3NXM5cWJIQm1BRVVpQWliWDRJL1pLQ3JI +TzN4ajVGQWFjSjV0d1ZpQWhDRTYvSnJEV0hhTU84SXZ4UnREdHovc1VmL2VPSUNHRHhOZEFsSC9C +d21UZkVLUmxpTTdGODZ2QlpFeEdtb1M0Tkl3b0RjUVRkdzRhWjdkR0lqQmo1eTM5akVrMnp2bzMw +MlhvTmhsRnREcURtSmxLRHFNMXBocHgraDlzYUhEM0N1c1BtTUd1Y0dtSHoyUjV4OFMrdTczanFv +UUdQbXVBaGFuZFluNlQvSmJGd0tnYWV4RkVQYkRpc3FLd1ZIMGYyNks4OWNxVE95eE9EWHI5YzFo +T09PUGJoQmdmcWNkY0QvSWxkQThzdEx2dytKODROT1hEaFB4RG05SlRneGFBTGVQRXNoNHUrMUVJ +UDRqQUpzNmpmc0I0bVhOaXZ0N0RxTHl5dDBaMWV4Y1NER0dMQ0E2KzZyYmNYRnBnOGs5c3pjL0xB +aTV2ZThTWlcvK01pUGgwMXZqZ3VHTXpoVnUyMGdTLzQ1VmV6T2V2bXovbFQvUGx6L2pRM2Y4NmY4 +K2Y4T1gvT241L0JueTlGV29SNFNTUlhJRUY0cnBrSTlaMGdFQTBvc3BDckdkWUFCUVF3aVBHTWdC +WHRVcEI5Q2tZS0VBU2wwVERZajFUQXVwNFlZZkdyRElqeHJBYlZGRmprUXA1MThCdUtoRjFqU3By +Y3lETEVoMVVORlkzMWozQ2VOY3dqUDJ6SUsybElsQWNlalZHUEhNaTBSMDNlVXRTZzFucFRvd255 +K2JzR2NJbVpBK0g0RTBjZXVCbGFiV3BWbTN4NHJ4bkZVSzk0YW9LcmVGd2RlTEpmbmZxa0ZqaHhM +QVo4K21DSUU5RzlId3IyV1U5TWhDU2ZPcDhEN0JHY056andtTU9uOWRISzRUYzh3MmtQRlJqa0VW +dGVoNUo2NVdRWWUvR2dkdlg0UDlhRlNaeWFWTTNXNjVmbjZkOWhlWEE5YjV6a2tZT2U1QlVUMy9q +RW9kaldtTThCZGZ2TTJXdXRlUHJzdTU3b0Q2enkrdytFUHRzZkxiNXJvdk5pd2lhUHZubG12NWhx +VWFQODZvRGYzUjR4OVU1T2x6bmY4VE4venAvaXpKL3pwMzN6NS93NWY4NmY4K2Y4S2U5SDkrZExF +UUkrNDBzYWlFaUovQlF0QVFKc0FFYWlGSEhnS25wTjhGa2NSU0ZJNHhRc09kRXFDTGthVU5EZlh1 +YUl5VDVYdnA4QjRVT3dZb0gzRnNJUUI1WUswM3FZa0lVRVRYZkpxNUVoTytNUm1Gd0lnQTlSZmlm +ZjNnN05rOThjL05iS284YUtDUmFteEpWTDNKcENIc1Ardi96UnZ3cFd0UkMycG9ncGpuVmlpQzhH +VTVnVHgzZTg2a2xObGo3Y1BoekI3ck5ueFVsd0JpN3daWi9jNGpqby9JcUU5UXlLUTRaU3AvcmQ4 +YWhHdWZGSUxITEJ5YVR1Y0RpRXhKZGZMclYvZlVOMEE1ZjRwdzNjdzZBWDZud09odWYzK0Z1WEMz +Nll4UGsvLytTZkJRYytlcG1IVHd3OWhiRjcxTzJaT2x1N0F3UU94dksyczhMM0h3WGM0UjJXSHZU +MjJlTU9LNU8xUCtwVUEyem1XcStZZUxLSFRsdXJlc3piMy9WcXdDWGNhaGRmYm5kNHJMRzNlbFdu +T1BQbi9EbC96cCs0blQvbnovbHovcHcvNTgvUDRNK1hEd0lya3VnRVE0d0MvZjY0WjVvSklORWl5 +QnI3QkRUY0pWS2M1QVRZb3NSQ2luMVpmN0VNUUZ5ZXlmV1R2L3lERUlkUVl2TjJnZEM3VGtGaU1M +ZFJBOG1CWERFOGM0Z2dURnprRVlnR1Zid0VKSzVHMXB6dWFuMkU4d3c0MUJQejNEelNFQWFYUGZJ +aXZBY0ZEZ2xTWStCNnhsdXdpZU51cjc4Y0xwWjZuZ2EraFdPOHFhRjRtRW05c0RJRFRzVzFWMjc3 +MWRxRFFqL3NyVUhUcSt1VDJ0M0ZoMUVzKzlUNjFQc1czQVNzVno3Ymd6djFXU3VYdnRWOGNxbEZY +T3VJazhqRXk1cGJDNU5ZNVZnL3JJSHJlUlAzSExMNmJ0NmxiMktJNTUrU051Yy9EbkMwdDkrYTNs +MStQVklIYm1DeVRrL2FQM1hqVmx4cnhLQUhjV0cxQjljOUFNU2tEMnNaREFaejhvbFgvZENpbU9i +MFFRNzk5MFpRdjZzblhLbmRZYWhHdmRCRG5GanZPMjFaQTV1N1hPTE9uL01uM1BQbi9KbjE4MmRp +elovekowN216L25UZmY3OG1QNThtZlJUT2lDU0Mxd1NOSk5RRllFZ1JyTmU0VUMyS1o0QmpEUkZh +VGJ5TlVkc2lRQXdwem4ySUVFOG9yUFg3OGNqRWdieENMUUhCWEYreFJtRGZ3a2VlNUVNaDJZaVMx +eHpuc212RG05bEVKSnhwTmFjRmJuUGNsUTR5WEhyY0ZHeld3T3Z6NTY1TkE0L2NMclVMeTl1T2lL +azIxT2p5RTJNOWo2R3VuRzVjT29TVnd4NDFHdWZCbXRrRDd1K0hYSXhRcmsyaHh1amg2MDQ5dUtH +K0J4RTduSVRqQ0dQMko3YkEzOTZkakh0VXlQajViQytnVk1pMUM5eHJHRUlOWVhENDA4ZjVNWTdq +dlcrSm9UWGdOMWJTTm9RTXdmZTVkSnJiN0xzcHdkOWdzWGU2a0o4M05xckRwY1k5bnBPZy9SaEhR +NTkxZ2RyZk1ZeGJEQ0xuYjZlYnRRQnF4eWUweXBNK0FpUFY0di9rSmpYQjgvVmdSL1ljR0tkK0xB +YnRPazdYbkFvUmd4NldQQXRwejMwaHZ2NlNQNnZ1cDgvNTgvNWMvNmNQK2ZQK1hQK3ZHZmgvZXFZ +UCtmUGorclBGMUVnNGhIKzgzOFlXNkg1Uzc1SVE3cWd6Q0lvWUFJSmlCakVTMkFlWUFZUEVNMTVO +NDE5NXNSeUljYjNOdWhIdi9NcithTjh4UklrQVppSHpYZWZZWDNHbDVBbUQxeHlFeGVCaWdVUExC +cVI1aC81TUtnUmtTSC9udHVMQkxVaVhhMk5qd3YxaVNXM085S1FHZEdleURSQUhmYkNLNDk1Z2pE +TStVNDAxbWdHTTFpbjhYZzBZUExNdkR6eU1uTE5LUWE4YW9SWm5kYUlpMmM5OHl6OHhuUnZFWFh6 +ZGEwN0Vla2REbkJ0aU4zbjhxWkczTnh6dktrRHh1TFZWMWh3WEFQcWszbnI4SXdqMk8ySHp6MXI3 +L3JXb0dvVXAzajBGUWEvbGdFSHJhZ0Y5K0l3bmozaTZCMk02aldzVVRQOVZvdHlNUWpqbU1lVFhH +cUZVWHpyNlZQLzdlK0JJejQ4T1BXc3Nmc2ZNTGxqN01NbER3NW9FVjVjTTd3aEh5eDZiaTQ5dXpr +eDRQcDZlTndGbDdqVzY5bjhPWCtLUFgvT245SFAvSmwxOCtmOE9YL09uL1BueC9abmZnQVZORVgr +OVY5bHduZEIvWlF1TVlDZUMwU0lFclJvQS9tQUV4Y0E3cHFrWVpJaDNLVkk0Z2ZVZWczczJ4dTVL +a0FOS0VIdWNzbHZUdzZTdTJDQzNSck5VSnpjY3ZTWk9QWjhTeFpUd1lJY3VXQ0ZFZWtWVG5JY1Ru +R3MwU1NORlJzdm5yblVRWkJJTFg1TjhtYk4wRGlIanIxcWhZUEFZTEpYZkxrSUE0Zk5ZZDZoMFRy +d1RoejZFb0VmSHR4bS9idmcxZUI2RFByOE05WDRZVkNZOFFPRDN6MnY2V09tcTVOd1BiTW00cjE4 +NHBaUEJzMGhuZjY5UlRnNHJKajFSbTF3NEZkdCtOWTNtT0cxcnFZcXp3d0puM3pxcEQveGZjYVRl +Mk9wVlI3YzZpdk1lS3JXN0ZNL3ZtRlRPMHp3TUx1K2VJWlh6MmxYVEtQMWlPOU8zOWI2SHF5SEN4 +ZnF0VjlOWXNDUU5WZWZXbkJxalhyd2lETkRUUHZhTi9Yb043MjRyTFZHdlQ2TFNWOXd3VEYvenAv +ejUvejU5SGYraEduK25EL256L2x6L3Z6WS9ud2h6R2FGS0ZRQ2hRSllvV21RNURZalIyUHpSdUdB +SU5CbmdsY0U0REhTRFVERXNkYytlV3BRNUNqMC8vclJ2MHlUL05HNXZKNXJqajIrQSsxelFFY2d4 +dlByRkcwQ2N5bFV2aHdLVjVPR0VKMTZ6SHR1WDk0Q3ZKT2l1VWlBeTdvY1VqY1FhNzNZeVh2Q0tx +bXdtUE5NVTNDRFhDSVFzMitMREhqVmpXeDNSbllnYWF3YzRrV0FaMlRQMU91em11WEZoZGo0YVFQ +bEpocGNtN2ZQZHpXRTM0ajF5YzF3YXNlRmRmclVmN29hZDQ4aG5yWGxIM2R3NGFuR2dBbE93alh3 +b25kNHRCNlBlaDdEWDB4Q2hJOEo1SUt2c2VsRVBEakZ6cHN6UWozK284RWIxbmxlWGFvWkZqVXd2 +WnJVSnBhMXNPbWRkZmtWakl1bDVyLzg4YThIc3ppNGh3R3Y4TnByUGN6bTlSVWVQWlFMMzc0NzhQ +UktidldvbTJacVVMV0lvV2JQUGJNUGhocFUvK0F0aDdpeER3YnhjSlI2N3JLdi93RW9aK1ZoL3B3 +L2NUZC96cDlxbUQvbnovbHovcHcvNTgrUDZzOFhRQTFHY0MyQStZaEtZaVFwR0NpTkZaelFBREkw +SGlnWGNTSmNQSTJYQkdnRVJSUkhaSjRmc1VUZ0VMRGVQOStzQU9JUUEySFdpeWt2OEFycllBelBr +YXRBQWhGVHdZaFh2QXZSY25zbXQzMmVpeStYL1dKN3MxQkNEZkVJb1dUWjc1bTlZakNvTzhMaDF6 +ejR4TklRdzd4REFnY00vQmQvK0kveVhVMXFUejBuVlB6Q0Rnc08xQ00zZkdwMFlPRmJYUHZrN3NF +QmszVjZrWDdFb00vYkxYc2J5M2NZNWJkSGYvWGJJRWk5N25yeFhPSzc1SVFyQnJ5Qk83eUtody85 +aEszOXNkWXpzZlFlUjkrYVExN0M5MHl2VzRjOERJNEgvZEIvSE9LZXlmRkdyM0NLYTQ5Nyt5YStm +ZlJzSGQ1Z2tjc3ptR09NNnhFalB3Zk84MGJLbkR4NEVVZFAxV0RZWDg1Zzh0a2xEcDRTNTNEajBY +TTE0cWdIRGl5ZTYwdTBjM3F5eHpPNXJhMWU2UmNmK2lYTy9EbC96cC96NS93NWY4NmY4K2Y4T1g5 +K0puKytOTThIUmdUZTUyNzAwN2lnZ21tR0ppQUhzVXhaUVZUSWlGSTQ0SVlDRVdNUFlVaitzOC85 +cTFtSzFpaXhZRUNPbURBZ0MzQUV5dEdoWWZEQXJhRmlpUWw3allJTURYUzRJTThnRHV2RTFuQkV3 +SmI4M3d5MUlWWk9NWHJsMlJGb3IxaHlpT2QzL1l1akJ0VWdUVld2K1IvLzd0OE5GazIxWDIzcUVF +Y2UzdzN6OE12WGd4SkdjZVV6YjYwNjFVdFk0c21kY1R6S3FUYUhremNseGVXTm5HZnExMXZpZzZI +UDlFSTllcVhQTllmZVBvTDdFbzFZcjVZS1N2d1krSVFxZnpSeW1OWFFldFVJVjNzQnV6ZEI5dmZn +czEvdCt1R09HM1c2OXhBU0N3YlBvN3Vyd1REM0o3LzFpK0dMaG5ESDlNMGZEVjB1Ni9URDgvNEhn +MG5ncWgrcUk3V29IZCs0VVM5K3FnZHIrRVB0OXVERnZIaXA1MGE0dkY3Z0RwY3cwTG45MXNwWjNu +TXdYRTNtdjhVemY4NmY4K2Y4K2JQUDU4LzVjLzZjUCtmUCtmTWordlBsWVpzQW1FbUpGQ09Kd1FT +YTY3bEFpRU0rUVJvQUNJZ29BdGFjL21TT1dFREUrNVlRRFdGNmIwN0U4enZtUnB2bEdWTHNxWEN5 +L3doendRUTdZaFF2Sm56MkVBdE05aUJNTEhqc0lUTHg3VUV1Y254K3NEMy93cGFCRS9nMFZtM0pk +K1NwUTlNOUYwdHo4SVlyY2RWYVhxd1R4NEJGcmQ2SXdScXhYcTJhcnhGRUk3WStpTnMrNEYzZTVv +YWovTm5uRWdjZWRWYXNNT09CU09XMDErVmdoUlduT01rNFUvamVtR3J3R1plNGdhZEdOdVFUcTcx +V0k3Njd6b1VUL0RXTzd6N0RCTCtoWmpxU3cyZDg2UzMrR0JTTzFIdjU1RkNmblBvdWp0ejJ0UTZj +ZVVNa3IxL0ZZSDc2Z05WZUdQVkxmRytQNE5KejhmTWZtYnZyRXk2dG9RK3gxUVcvdVdwS2J2ZzhG +MU1mNnc5NzVhTjlRMTV6MXFnSHh6MlUxYTRXTlZXellzTmkzZnc1Zjg2Zjg2ZDQ4K2Y4T1gvT24v +UG4vUGxaL1BtU3hFSkRVNEh5RTNhTmsrZTNVTkhBSUFqUjNnNEliQWhzTGFEbVBRZUdHZTBGV0ZL +Z05DOENQUUJpdU9SRGtDRU9Rc1hRTUQ5bG0xTmd4UzR1NHNRMVh3SFlBejh4RTJLRjdia2FrWU5z +T1R5RGlUQTFHWjZ2NHdTcnNXb1JWeTVORXdOZmNJV3pFNU9hN0JlUHlSR09EOE9jL1dyV1ZIOUJt +bGs4eDVVN2diakQwZ05CbmI2cm1abGNPRkNMdGZMQVlhM2FZZmZNZWpVMk54NWh3WlBZNnZIV3ow +VUlqK0hlc2djK1hEUTNqdVJRVzJJZUo0MnRkckhFVUo4MWN1RWxCcjNldzFUdTRiU09ibHl3NHRl +Yy9uc21aNFIrKzRuVnIyUjRwbC9XaTIyOUF3c2Y2b0xQR3RnTU9laFdEL1RmV3lENzRlaXZlTUFP +ZDlmSmgwUDg0Szk5aEtuRzAzK3hZL1I3TGg0dXhKSS8vM0Y2TjZqOStIRFpyNyt3K2k0WHp2RkJ0 +emp5Mlo0YzhNZTVDeTU5NkdFWnJkMllQOS9IL0JtTzVzLzVjLzZjUHoyVGMvNmNQK2ZQK2ZNaitm +TWxtVUlWSWxuQkswNWpBZkhjbXdhSjNCV2dvWUloUjdNQXNNNnZIUGhzTUtMRUFCQllEY1pFcnBq +b21tRlBpTDVCSkFHS2lKdFRBSXp5QVU1UWpBR3ZQSEo0cm9IV0k2MEhDc0tRcWo0a0lzUmErOVFw +aHpVRUswZUgyT29TVDIwNHNBNGY5b2tGYnc2aFc0Y1hhL1A4NGo4NEQrWjcwMkRXNEQvLy9xK2xE +bzF6d1dtUCtMNEgvMkhFbDdyZ3c1K2Nuc25qZVkwcFptc1RTMStJd01DMWcwOCt1TDJKRVV1dDVU +c0h4SEhKTEhxaDFuTGlyZzl5ZEYyTm9ENjh1c01EbjloRTEvN29CN3o2Q2ErcnNlM0RjVXg4TmNP +aW5oNEE5c0NwSnRqa2NJZEhYODJyU2N3Y2xPKzQxSnpma2FlWDA2bWE3WlBQWjgvTHF6ZFE5aHR3 +TUM4dThheG1PSm1KT2RVRGl6cnRVUmZPcmJkWExTNzQxS01QWFdQNExHZTVWaXRPbzhlckhhNzZw +UG54MS8vWXpKL3o1L3c1Zjg2Zjg2Zlk5dUZZcldxR1pmNmNQOFBIclowLzU4K1A0cytYMzVsV2RJ +TW9Vck1VK0gvODhUOTlDTGpBeVBkTUlJMnN5UVN6MXpQRUFlZW5Zakg4bis4aVVySGlBR1VOTVhv +bWZreitYZ1FDL0xFeU03c1RsNFpiYi80Ly8vQmZKSTgzTGYvcEIvL3dNZncxUjhFRUk2Y0MvWjQw +d2cyWWM1aGNiZjVZdW9jSElTUEhRU1FtRG1KU3pYNXZPTks5MFhGZ2lZdDhHRjJJMWp4TmxCOW1l +YXlwY0FoRG5lSXpCWXhpLzRjLytQdjVDOXZFZ2s5N3ZNR1FBeTdQQ2RRbC80LyszZDlKZkh6alN4 +emZpY3grNjRtbHpZYkg0VUR3OEZrblZ0WWNKaHlvU1U1Q3dLTTNOWDZYUGp3Y0h0K3Q4MWtOaG5W +aXFMOW1jUWdRcjVyeFphMWZEK2dhL1plais4M2h3enIxaW1mSVZaUFFsS0ZlNjhSV0MyN01XYWN1 +eitSWGsrOWk5THZQT1FUUEpMaXlEeDQxMGhKK3JLT0o5c3Q4RG9xTFpSL2NlS01oUE5DUXVPclFm +M2UvV21PTnRiVDl2WC84MzMvMzczLzlmL2xxT252MVM4Mnc0Y3gzV3JGZVBvY0tmZEF0YmRzbmov +bjVjLzdFNC93NWY4NmY4K2Y4T1gvT24vT256NS9Cbnk5L2NWaHlvQzNXYkFXNWJQUlRMTUNNSnpE +Q3JWVXcweEtGZ09aY25nUFBZR0s3YTViOUNQVVpNUUQvK1BmK1hvQllMeC9pRWFnNHdsU01rY1Bo +Q3ZxUC8vNGZCS09pSTNDSHh4MEc0dGxqdlRqaWVpTmdJQjJCaWxXUGZBWXNtbStQdk1pTFFlOHd5 +bHVPR3dSQnFKb29welZwbG5WTWZHdkZxV2prRUI4bkx2RnhKWVlHcU4zNHM5Ly8xZUEzSnhhTURo +dzhHbnFBTi91WTY0Lyt6ZDlPWFFRcmg3azJ1czFzYjZ3VGp3Qmo5dXVmZzFBTnhDOEdRUktDdjFB +UFA0T0o1N0JTbjVqNHh5dmVjVzQ0WUsxVmx6azFtNmNQbU9XVDI4SGMrdHRmaHhuUjk5Q21CM1cw +SDNDcFZWeWZEYmw4SjM2NTFjSWsxc25wUVBBZm1Hb1J2L2lzUnMzN0RMOTk2bGNqakRVWS9QaGc1 +cTZCMDd5WSt1YkN1MWd1dWRVS0UxN2hzZDhjamNLa2ZyMFFYMHdlcTBmVWJVNGU2MmhlTCt4MXR3 +WjM4TXlmOCtmOE9YL09uL1BuL0RsL2lqTi96cCtmeFo4dllySEJUK3MxS1hFREJKZ0FMY1lHSUNR +VDNGb0UyMnU5SmlPZWVBRVQyMXB4M0FtYUFCQ3FDTUlSSDVGaWEwamlIOG5lQmhHb29UR0kvZUgz +ZnZrcjJkNnlJSllRODlQOVBaUERPajlweTIvQXBqNHhHVlhoaG5yc0kwYVh1VGF1dzJma1I3Z1hy +eWFFdjIrSENOdytiNmZVcng0Q3c0czk5aEtsZjRZYnZ3NFU1dEFNaDVUY01Nb2pycUZlZUdBVzkv +dS84WE1SSDg0MU5nZkZ4VmF2bUJXbVorYWZnK0Q1LzVKU3AvdzRzQll1SERDZ1BoTWFUbnNnd2lN +dVBITGJVOE1RWWMxSmRISlo3L216NWkwWUhJUjQ4Qm5IZW9Rbm5OR0V2akswZVhFTU5jdUhreHJF +SG5IRVVJZDY3TE5PVHYySEUwWm1nY2Q2TVQyelhtM1ZwejZwazhab1RWdzFpeU91TlhDb1JXNEc5 +U2JOWlUvMUQ1dGExS1VXUGJaUDd2WUdUKzdNcHEvMndxMVBhblJYSjZ4NkRRZDlNNmkrMktPRytY +UCtuRC9uVC9mNWMvNmNQK2ZQK1hQKzlPd3orRE8vZ3F0STR2T0F3U1JSakUzQUFnU1k1bHFEQ090 +Y0FDREFNNThCQTE0OGJ6WVlrcG5NZVM0MlVINnlkaUVRR1FDTERaaFljQ21RMldFd1I5akVJNFkv +Nm9hSktPQW9MdUw0dy8vOWZ3MEpCdElWakR6em1tRWdIdzV6Uklab21NVjd4bk5IcW9ORWZCalZD +bU5GaHh1OElKa0p4SGZCNlZjQXhCWUR4eFd4T3NTRXA0ZWhOZUlZNGhNRlBPNyt5QnRXYjByMHhC +N2N3SVJYejNEQW1QaDBNUjVzOW1tNi9MRExVYnlQa1ovZmVmZEg3WG9GQzh4OTg2Rm1kUnFwNjc3 +WHBNM05IQXdqcCsvZWRyVWYxc0dCTC9XYkUxcy81SGw2ZUFZOWc4Q2tOc0xWZHdiQ2hkclV6bkQ2 +bG41ZVhIWEtKNWQ1ejJHcktlbE5ERGw4aDExOXJoNDI5akNYUFRpVkN4YTVySUZWYitUakNkejFZ +TGJHSEMzaVdUeHJ4UEFaTmh3azV1SEFMODdWN2JsRFIxeUhCdTM4NlcvL1V0NFc0VUU4YStmUCta +Tkc1cy81Yy82Y1ArZlArWFArbkQ4L2l6OWZoS2xBQ3dXdGFDc3czNzNaY1VlVzV3Mk9BRVVMVlBB +eHdLMjN6cHNKVGZ6UjcveEsxaW1vVGZFR0NCZzVRKzRWaWppTkV3OHhDalEwREhsaVZzeitpTDhD +UTREbmN0V2d5RERFOEliS25McHFVS0x4VXpzRHRNbnFoZjBaL3NucUx4RXA4YXBQRGRiaG9tTFJM +SEVjR29pRkJWNXIxVThZOGpKbGM4UG8wUEtjZWVRM0w0WWh2cHh5dS83d1gvK3QxRXJnVEtzMmR6 +bGNPTzJCNms1c01EQU9YZ2pBR3RoZ3NFYXR6eUgySld2ZzBGZDR5NWszSHpDcTBkQXJlOVNyMTdq +SS9Ja1ZGMHdsaHJkaGVtd2VGbHpMNDdPNDZsY1A3UEtab3kxOVZTK0RNTHM1ZUdsQ0REalVKRVpx +UGh5dzJrdERlcXcyZVdHRnkrZnFrb2JFZEJlUGhtTytpNDl6TWQxOU54Z1VWbG9VRDE1emRPL0Fr +Wk94NE9FZk5Wc0hJMFBTQTMvcG0yZTR3N1U4WXNGQVJ6aVR3Ni8wOElTWWFxUDMrWFArdEdiK25E +L256L2x6L3B3LzU4LzVFOWJQNE0rWDRFaTJFWUV1UWhHRWVDdEd4Wmp6M1VaSkVXYWR4SUI3Ymsy +TWU1OFZyeEVTSThwelJDbE1JOHlMUTB3MUNlRVZzTmlHM0lpM0RnRUVTZ1NJTjVBQnYvMUU1WTk2 +eFRQY2lROVI1b2cwemI3bTJRK0RodG1QQ3lMcmdCY08vSWhkUWFuUk92VzcxSzQ1OExyRHF3WnZ1 +ZURHb3orYUp5UkR6RC80VjM4em1EVFJQSXc0STFhY3dHSU9IMy84YjM4aE5iYzNZbnR1ZjBVSmx6 +NjQ3Q2N1Vjgybnh0WmdqL2crRy9ZeU1hNzBxN2lzRXkrYzNNR0ZlMnZWQ0NzUndZRlBBdlQyQ3ph +SGl6WFd3dEw5ZUpGRDcrQnl3VzVPWC9EbjZoN3g5Wnhtdk1YeHZOcWhRM2h3aHhmNTNmVkNuK0Zv +REJjODZ1OS9aTVFWanhrOHN3OWVtb0FGSm11OHlhTVJHTVNVbDNZY0lITFNoaGptN1JYSFllV1Fn +UlB2K3NCRE9aUXVOMTdWN3BtcjJ2dno3Ly9hMXplMjF1SmkvcHcvN1owLzU4LzVjLzZjUCtmUCtY +UCtoS014UHJJLzh5dTRFaUFPTUdJSHhDYlAvZVJMcUlocEF5VzFWbUpGK1k0QWpVVUcwUklVTVhu +dWJZNTRDQk1mTVVSS0ZPSjdwaml4Z0pQWFQ4b2g2b1k4TWRMRnRJNW9yRk5vWWg0Wk1NUG56UWRC +KzJ6WVl5MXNTTENXQ2RTRWJFSzBWazVrWnhEVURVMVFBNEhMSlFlQ05kUWJBbzFRSzB5RUtnNlJ3 +RXM0L3VnYXlacjhyVUhGOU5iTUd2SEVKMW9pTkFnRGJudmxkZURnM1hNODJHOWVUVVJISFBMNkRD +TUJNWTNmQXhkRGpkWXlwRFdFalg5NDFhaEhxZjlpVzJPOTNzQ21iMkxoakpsd3BtZnc2RFVjZUhn +TStsMzI2cDA0ZEFHenZUaWxvY3ovNlBrZGVqRnd4OWk0d2FHYytrNjQ0dXVYNzNMVVZIcXNCblhM +My8rQXFGMDhlVjFNSmc4TzhlT2lNVnpnbXU3VWpCTjV2ajFVcW5sditlRHl6RjE4K2ZSZHpYcWhq +L0NZczQ0MjVNSnA0MWt2UGo3YmgveWUvdVhWSXpYSlJTczFLSjNQbi9Qbi9EbC96cC96NS93NWY4 +NmY4K2RuOHVmTEE4MVFoQ0lGVnB4aUNlY2g4RVJ6d1d6VUhFa1FLamdDWEJJSzZLNFppQVBjT3ZI +RjBlaUk2K0lqaGtDQWw3Y2tpZ0djZzRNWURNWDVsUVpGYUpvQ3hRNjJid3dLbitaNyt5S09JUWZq +aVduL0UvUDVDOHhJTFJrVjZEUDhlc0xiMTRNR0Z2RTAxdmNjUW1jQ0RkUWtmTmpiT2Mrc2hjVXpm +Mm5jcnlROFluMSs3ejV2ZmQ2RnhMeitaVE43KzJaQ2ZjRitheDF3eklFbnVjVDJGb1RZUGNjYnNm +VVFrWitnaWNQYkdwd1RoL2o2MDRQSld2eDVKZytleWkyeHlPLzdnL3N0OGNWb3ovRUdDMXo0TU1T +RnpaemFIVlpxa2tjY2VxTXJCbEZMZTB3SFlubG1IWkhURG5QaXVRYTF6MEVydjlqdGhYNDJIdHlw +N1FZVHExMThHbTU5TklNZkdNM0w0WERBaFJ6eTIrTlhSNnBUTVdtR1BuSTRYMTU3Nk50ekZ5N29Y +RjdHRTQrT1hPYjFxeHpLS1M2dUhKZ09ObitIUUMrczhYeituRDg5azJmK25EL256L2x6L3B3LzU4 +LzUwL2pvL3N5ZmdCS0FKQ0hnR2gwVEhYQjNoU0FYR0dKQ21JSThiMUdJY2RmQUd0UlAwd1FxaWNZ +VGtaKzZLMlNHY0FHb0VRQmF3M1NLSXhBTk13aU9RUkZsTGFMOGZ3ZkpoUXlrZUZiai85NC8veHZC +YUlqaG1iVnk5TUF4Tk1JelpGaWpjWVQ0akxmRUZaUG83SWRiWFJxcmdUaUpTSTUwYTd3Umt0Y3pY +T0RVbXg5L3lSckhudHREUkV4cGpmdy8rTTJmenhvY0dzU0dNL3Z0OHpaSjNScXNKdzRMWFBpc0hy +dzVPT0VTVTE5YzV2SElNTmJvWFVYaXNIcEUvUHlPUEJ4aWlxRVdmSXNscm40eUdjR2wxeWZ5SGdq +MkVENCtETGc5VTZQZTEydzFxSU5LYm5IRklWUUQxN2hWRzU3MFZRNjR6Y25oT1czQVNqZEVyM2Vl +VzZjMlBCRzYyZzN6OE5UbzR1dXBtT1o4VnBkNDlzUHNzNkVHdjE1anYyZndWVmN4M2J0QkhSajI2 +Z1djYXExQnphdmJXdk55cWhsZUpvZUp0dWhVdi9GRGkvSjRQbi9Pbi9Qbi9BblAvRGwvenAvejUv +dzVmNnJkK09qK3pOOEIxVVIzZ3JWUkVZQVNaTUVLcW1qTnRWSEJRR29tNElBb0ZnRGtla2F3R3Vh +enRUSG9mUmZmbkVZUlkwMG5kZzhBLzR5ekFnM0ZXZXR0anJWeUtRSUpCckxoOTV4b2Z2dC8rKzlD +bUFGZjVrNE1Qc01YRXg2SjltbW1PWGRDemlESTk3Y0E1dUFnWEhYZ0FEZHFNYThoNWh3MlJHMDkw +dFhnOFBNMlJLME1xRGFET1AyTFVCVUlFL3R1cjRFL1BLbEZuZDRhaUtrMzFoQ08rRGlMNFM1T1A3 +c1RMRk1RbHBwZ3N3ZGZPQllYTG4wMHJNT1J2b2pyTzc3VnhTVE1DWk82Y1l4SHNTcDJCNmcxcnVh +RFNTNmF3aFZPYWNBYm92WURyemtBcmg5aXdBTWJYdUZYTS8zQjRCa01hb1N0NG9hbnZZQkhuZTZ3 +R3pCWVE3OTRVWnMxMXZ1UGpEbFlmUGJjQVpTYTdiMGFISkNkMTVQR2xiT2NxRmtkc0RHamVxd1hT +d3c0cmNjdFBOYUtRenR3d0lCSEI0L0RXRHh4OFlPcitYUCt4SkcrekovenAvano1L3c1Zjg2ZjhG +VVBQc00zZjg2ZjFuOEVmMzc5QWRSUHB4V0J4Y2d5aDJCRGNJSGFGRUFrOVZiRlQ3ZUlsWnlCaUp1 +NUNaTVEwb1Jya0NZUXVPZCs3NXZ3TkUxY01ab1RlR0pYb0NFUEF5bkFUOWt1Y3dnMUZBbUhncGpv +ZC8vWi8vVFZ2T0xaQjdmbWxHQWtxcFhwMWVhdThaMGpOcjgvaml6MWlrdDQ0bWhZQmFUcHlOUThI +RDZIeXBlUTd3MFozR0w3TlFYTk1nYy9ROHFMRDM4TTdsOEN3NWtocGoxNHc2bDVZaUFjQm1RUWVK +aFhMTWJ2V24zQVY4UjNPTVd4Vm14cjVTUlcyTktYcXhNdkRoTUMwejkxK3RVR3RlcC94SHgzOGZW +SVROemgwbmZZREhjYzZubHoyWWZIY21KT2JIdnhGZ3czMUN5ZmZZL0luL1VNcWo1OGk0VmZkVHRj +NWFzQkhPSTBwTFp2NDlJQkh1VHp6QjJINHFwVlR1dmxWeE1Pbm9QMExjOTV3L29lam8rdTNsSVRu +c1hyZnl4Z2h3djI1RDFkeTBIMzFuNDl0SzQzc0tqVGY2QndxQWY2eWhkMEpMZG44K2Y4T1gvT24v +UG4vRGwvenAvejUvejViZHlQN3M4WDhUS01POUNTRUkxR0U3MW55RUVBUXlqSUhMSVFJMmlGUzho +QUFDT0JSSzRXd0tRVkRRTjZNeUt1UmdJdVBwS3Q4WVpJSG9OQS9Fb0NFeWlPNE94OURvUG5kOWty +WEFMNS9YLzVQMmVkSVpiOHNLbExuZzZDRnhOWjV1SDY2WGplV2pDNGZZeXF4dFlwcDNuNE5RSW0y +RFdzbUh5M1ZuNzE0a1R6aU1EdnlNc0xqM21IVmV2Rkt5eDZJVGJ6bEg5cm1NMXpzY1VnU2pIRjEy +dzgyNHNQY2F5MXhuN3pEamY1MUcvVW9OYTVmTzgrSnRCUG1KaEREbHpvR1c1N2lNcHBuWnpCZGZu +TTRTampEZ0ltWUZDWHVzMWJoeTg2VTVmdlJFNjRUS0VPYTMzMlhDL2tFSXN1cktjdCtGTG44ZUdP +SnlZeUw2NDllaU1uclRPSGVMNkxUOC9xa2UvaDVVdTQ0ZzI5VmJmZXc4RVBlSUZSVEppWU9udmZE +NlFhV2wxdzRzYThIdEFtL3V3Um83NmkzZjVLaExqNG5qL256L2x6L3B3LzU4LzVjLzZjUCtkUGR6 +eDlCbisrQUVlWWh2aUpYTEVsWG1LRng2QW5PSVdZWXd3RUtaTEE3ZGNVNndVR1huSGVRaWpRLzI4 +Uk1FYkZ3TGplbmlCRFRvVzRJNWpZR0pSd002NjVmbEt2U1J3YzVtUDZJNUo0eFVXOHVlLy94czk5 +elljVXRXbVlOUTZRWjd3bEZ4d2xYeTJFSnFaQlhISjZqancxSWMxaElDZSszRDFUUDI2SVFmMmFa +SSs5WXZpZGVIL2NMYWJtNWYvRStQWTQzTlR5bzkvNWxRakJnTmtCbExnWFQxMGFxYW5sMlhQMWxM +T0tBNTk2bzMvMnExdDkxdWhOVFVnSWhHR0lwVStORGErM09PcFVoOTdMSTVZODFoRytPSHFsUHdT +SU4vdlVUQnYwd2dTNFRwempDaWM0NzJGUndiczcwUFROWHZXcVJjMHV2TkdpM0xCV2d6QVF2TnJo +MDEvcjFRcVBkZmJRUlRuUWErdjFyK1pqSXZ1c2VRNzM1eTJudDNQdW5xc1pKbGpnYzZrUlo1NnJD +WFo3ZllmSDl4clVmbmh3NmE1V3VyY1dGM3hFQi9RS00yeno1L3dwMXZ3NWZ4cno1L3c1Zjg2Zjgr +ZjhDYzlIOStmcnEyR3VBR1JhckdqUFBSTkFjS0FrMUhnSkZPZXFBSzBIMUJwRU1oY0RFaTFDZ1Jj +SE9ZcjJoc0piRXpHc0J4Wm9uOTN0SVJCRGdmSjRXOFAwY0xrVGkyR1AvQzZpOUlZSW9RYlNjNEFj +eVFwWFc0YzFCQU1URTZpRG1CNlQzdTN3SnVmVmdTek5WNCtZbW9EOEhrWVZoN285TXp3WEUrbmVj +S201OWZxamFERmhOdTlYRnRSbzRGZDlCT3NPUTVwK29wSmZEc1oySjJ5WHVnaGRiUGcwMkZyOXlz +RjY4L2hVSzZIOCtmZC9MZmdNM01BbVB6dzQwU05yR1lzUTlhOEhtVnIxeTNwWUdkUTZGeTNaVHpm +TVMwLzQ3RHh6NHRvKy9iUE9mQTFLZzdSbUxRUENxazh3OU9EcGZueVVBN3pDNVZJRHJJWTlOR0VQ +ek9VRUxudHdnZ2M1OFFSek1CM2ZZc0ZyRDAzaXdGMU1QWFl4dkY1NGJpLytEZmowZ05ubG9TVjlz +UVlPdGNNdGxqc2NNUHJYNHZRYnp5NjF6Si96NS93NWY4NmY4K2Y4T1gvT24vUG5aL0huQzhsRXdG +eUNON0RGZnRvWHpHQVN6d0ZDbUNJOFl4WWkxR1FGS2Q0YkJZMWlRT3QvOHBmUFg0NEdVckgyRUlR +M0k4QWpVMndFS05wYW1Hb21HTXd6R0x6aWlhOEFvOExWR0h2OEpXMTVqY2F5ajFGcmVrT3R5UENz +Y1EzaWlGRlBlUGJnQWJsaWF6YVNyVkYvRzZ2SjVwak9tNUFhR0M1L01ibDhhSVo1QnNXNUdoalVH +NXZteHkvK2ljK2JKWFViK1BITVBtdmxFayt0YW9kSER5dGl0ZUdOeU5Yb3MzM0VBZE1qNHJlc2xV +ZGNkYnFyR1hieDlFME05ZU5Kclo3ck8wd1paekxQZW5pSWpSZjlyVUh4U2ZENmozdDdHZEp6UFZT +UE85N2t0QmNlVjQyTEwvaTZYMTM2WWI2SGtjUDkwYzd6RiszVjU4Q3h4enl1NkpkNTlNeGFuNlB0 +cTFNZDlub09xL1ZxaGtNTS9hTlRod2FPSEVqaTJwOTZqbE1IQUd3eDZOVmoyRytkT2JxRVFTN1BI +RTR1ZmFFNWNYUGd6NS96NS93NWY4NmY4K2Y4bVhYejUvdzVmMzRPZjc0QTkwZm5KZ1VIUkJPSXdr +SkprY3QwaUVsQnQ1a3dBRVM0Um1rODhwQmt2UVlqaW1rSnhYT2paZ2JXcngwUUkxS1FES2pQNGpB +VkxBWlN4Q2R5UmZ1Y3QwOXB3bk40d0NVMnZINGxvdmtVcVVIbS9MRS9zanZFcllnOXR5YmpCUFdN +dDNCQWNFUU5sN1d3eTRrdmx4cGhqUW11dGpUNFJHY09SdnR4ckNZMWl1OHZiWXRIbEg1ZGdVbkZ4 +cDBHMnFNUDNoaUk2KzBQdkxqM1hSd204R1pDNzlTclIzREJnMzg5dE00bEY5enV1TmIzaDZPMzFL +NFh6SVYvT05UdHJzL0VxRDV4WFo3RDJEN0N4bERlZHNDQ2IzWExhMTM0ZkRjcHZtR1R4OTc4QitE +bUdOUGVIdjV5ZW9aM2NXaE5mZnFMRzNqVXJyOWlNRE5jNHRJSHJ1VHozSGZDRjAvdDhqQ09QUGo4 +V1lQS0E1UHZzT0liTHJuVUF3dDgrbXNPejlabS91SzA5N2pDbjgrdytHdy92T0paQzVOOTBjN2hw +M0hheHEzbjgrZjhPWC9Pbi9Qbi9LbTIrWFArbkQvbno4L2l6L3dBQ29SSkJBUHJNd0xhK0JLb1VF +WUF4bmRKaVpyd2dGR3NPVDlCVzZ0UjlpdWc0dGNnbCtUZWloQm9nTCtUVnlLWUNXZ0QyVVJrSDlH +Sy9mWHdPSEVvR0dacjdQWDJTZk1OMzgzNUtkNXpJdXdRTjJJNjNHcHdvRHpqN2YzK0pZVGhpQm5V +M21ZeG9RYTc1RldIUEE2eHZpR3FvZFR1VndMd0hGekhwMXppcXNPL0VNYkE0aGhFb2xabTh2K2Jv +MTZpOFVaRURweUs0ek1zOWxYb2NNR2d5VEJsenpVYkRuY0hxcml3NEJ2dnVJSEYyeEQ5Rnc4dTlY +cTdRWXllVjh6cWdVRjgrZnZHeEIxdVhESVdqR3FoRTZKbFl2bGgrenAvKzgwUkx0eWU0VlJmM1pr +d2I2RnVqZTg0VlFkOXdtWTkvYWliY2ZWSmo2M0RoejNXcW9jaDRKZkhQT05ZcnhiNTNlVmp3UFpC +cmRaVW4zaFdwelg4a2o0Y0htdXk3djFBRWtjZU1XRHJJUWVyUTBQOTZoTERNekg0elZ0RVdIRXQ5 +dnc1Zjg2ZjgrZjhPWC9Pbi9Qbi9EbC9maVovdnZ6a1RJUVdLRmpURVMrQVo0eEhzUDU0dU9ZVVdM +R1NDcW9oa2x2TEVKSVNzdUlRb2lsRW9DZ3hpZEdjQXNSUkdEQStFeUlTQ0VaTVF3NFltVk1jcGlM +d05QZEdtNllvTWIwaDhsYkJnTWt6VFNNTTlYWFVDT3BDU3MwYlFSbEh0T2ZNRlFOZURpS3dUNDN3 +YWdnOHhDMjJ1alFFWjNDcHpid1lGV1lONmp0OGZsM0I1NStjRVJsYnc5VEpLSXlFQjd5b3lUTUhu +amo0SUFRY0U1QisyQ3NmdnZSRjdmZ3JkcC94NXE2UDRxZ05IdlVUS1FPclF5OWhKU2lmYTFDZjFX +MjlYakVUem1ERVZ3L2dDUDg0VXBNTFBuSGxJdlljeU1lbHdVaHcwQUVkeW11dmVxeXpWeTVjdUtM +QncrQVNReDNxeG1jRWZqZ05lOVRxRFppMTRzSGdMcWQ5MXBaSGQ3MDFZT0VQZlpGUFBiREFwMllH +aDBVZmNNTWJlbTQvanZHdFo5Ymh4ek1ZcklGWmZYM1RTRHQ2Nno4aStvWS82K2ZQK1hQK25EL256 +L2x6L3B3LzU4LzU4elA1ODhVOExwTVNtN0JZWUtMVExHUWhuekFBc3NZOElPWmJBSkFJWWtReEVX +V2UyQWhOREFTSVF6anV3SVhrQXlPMkloVk1zRDRiQ2lXNGloM2hpckRQVUNqTTJYdWk4WCtLaTBn +RFRnWkV2cGhJN3JCR1hIbmhWdS9QRGoreCt6VUJlSHM0SU5qYkRxYnd1ZWJHQ3k0OE0vQWpuejBF +NGlJSXpSTFRyeVdJeTZEV0ZKczFPVlNPQjd5cHl3R3BCaUtXdzJjOXc2ZkxHcndUSk14cUViT2NX +dXRTSi83TWU5NSs0MFlONWFxSEFLeDQwdHZ5QktjYXhTTnkyQWpXdlhtdDEzc2NlYnNqRHM3RVpW +QTlneHRlUXh5NFlvQTdFR2lPTm55SFVXeW1FdGZoRHllOU9neHB4NTRlK25Lb3dhQVJlUDFId1Ry +R3dHdk1jdGpvdzN4NHVFTXN4cms5NmxHL25xbEo3VDFBMU9IQWtSK2U4cHlZdHhlbStPRXd5TkhE +MDd3YTVHanQzamFadDE4ZkhBanFrTnVhK1hQK25EL256L2x6L3B3LzU4LzVjLzZVUXczR1IvZm5T +NkVldUFzdU1BS1FXNE1abXU4NVFDNW1RUXBDQUFkR2tmWnJpT2RNYWo5Q1hJQUFiMTFNZHFKUU1K +SmNRQUpHQk13Z2h3RzBuNXcxdFFLQ1RSeEVpY3ZFaEV2UXYvTlAvb2VzODVaR1BMbVI3OWNleE81 +QUdER3JTenc1Q2VucnVEckVVWXY0Q0NSaW9tRStlUHlFVHhEaDRIaFJGN0hCaFh3aWhrbDhOZUJI +ODVuejkvNzUzd2cyKzgwWG04Wm91amxpeG8xUjR4Q011SEl5aWo1b1BFeXdpZVV0RFg1ejJCNVA1 +bjNXRDNzanhxdGZUTS9oZHRGQ0QrM25BSHhMWDRpWW9YRWxubHoyNFI0SDlLRzN6WTMzaXRiQmpB +OG1nMGtmL3I4TXFuL3lNQ2FEcWszdGRDQ0c1ejZyV3gyK013a004c092SHR5cFVRdzQ1WkZYTHVh +d1RoL2toSzhteGJmdjR1bVIvUFRodWM5aXEwc3VsM3Jnd1pVWThyaVkwWGVYT3VYMHpPZGd1Qnc1 +Q0M2UHV2UUdIblhoaHNacTBQbHovcHcvNTA4WFBQUG4vRGwvenAvejUvejVHZno1SWlyZ2dmYkFa +czFEQUNGb2dpR0I1Z05wajJZUkJuQXh6VDNURUdzUW95aWlWb0RZaElWUUZ3QkVMdzl3OXJtc0Zk +OTZacEhEOE1mN3hHMk5QQXIzcklLdVFUV0Z3QmhVc1VoV3FCck1JVnY4RHZWNlE2Uld3b0xudnhs +WEIwRjZTNkRHSENZblR2bEt2Q1lScEh6Rmh4ZEdKdzRjeXFGZWI3WHdXNFArNERkL1B1YkZvY2Fv +MzZFQXMvckY5VnhjUmxFdmJuQ0xHenlhazZkTmwxOHRmc1VENStwVk8yR0U4K1BDUHJuVUFYZjVV +VnZqRW5rTjZqbU81TWV6NzR3aFhnOEdnMmpWaXE4S2xCNTY2RG13MU9yUVlRNmlqMEdQWjV6Qklq +OU81YTF3eFJIYlFlQXpyUEJiNTFBZ2NoZGU5QlQrOXQ5ek1XZ2NQL1NuRHJIVm9LYzRVcE1ldytX +NXVOSDM5VThzV09rRDMrb1ExenF4OEMwSHZ2UU9UblZicXkvbTlEV0h5ZWxHTEZoOEY2dDYwdHNl +Q0hERmI5ZlArWFArbkQvbnovbHovcHcvNTgvNWMvNXMveis2UDE5RTR5RkJJQXZRL3FTdXdSSHRC +V2RRNE0zYkxDbUNnS3Q0QmZZWmVJMGdicURGY3dFRGxMaU1wTm5tQWV4UDZJU2h3ZDRLYVlZQm4z +L1Nta2pFMTBReGtHekFKVDZpM1JuVVowM1ZUQmZocWhIK0RqWGtEY3c5RXc5QjN3NE5sZzgyT1RS +RHM1Q0xLL2sxZzhuVUkxN0ZoRE9ORU5lOE9HcXpseUR4aG50ejdpNTcvZkUzN0xpQnpSK3QyMnVQ +aHNPaUo1cVhlQ2NBWXZJZEwvcWdYZ2JGbDNtQ0ZadWgzUEhzVGhUNEVoODNFZW85OTB3TWZUUThs +MU04dUNMRzk3MXFsRis5K3B1Nnpnemg0bDByREJxVFhnM2lWbXN1aHJHWERuRExlQXh2dlpyd0pR +WXNudU5BRE0vRTg3em1wVGtHY2JEQjJUbDdZSUtmMXRTQ0t3WlVXM2xxYnZYMGdoVmY2clJPTExs +eXFMd2JWTjRhSGw3ejZtOE8ycUVsYThSUnEzeWV3Mmk5T1JxaWZYZjF1ZWJQK1ZQOCtYUCtuRC9u +VDNIbnovblQ1L2x6L3Z6by9ueHBzbWI2U1JoNGdBa1YyQW9MZ1lTRFRBUUE0VktBZmZaSWlCUkZJ +SVV3SmZBWjhVUml2UmkrdDVFS3NOZGJDaUsxMzFyN3pSbmVrakFzd3lNclpyNDFHbWdvTG0rempu +ajMzLzJuLzJOaXdxeFp4WUpJQXV6d1RFNXhrQ0wrTTU1L0pReHhzQ0lhUHB6SUxRYnhFWmhEU2gw +dW5EZ0lQRE44VjQvOG5oUDBnL2t0ei95Zjkyb0lFYWlSOEFoVGJEazAzeHNYencxNGZWWWpBWWdY +bzEzZnhQVVpKampWeFpBRUpZNzFQak1XbnVTd0J3Nzd4SVJWTCtHeEJuK0d6L0xTZ2p1K3hiUFg1 +d2p3Y090ditmWmNYSHFoQVpkRFh2OWdpNDZ1UitWS0RlckJzNzZIaDlzUFkwVlBPM29CZ3prbThW +d005eHBVZnJYTFNidlcwckw2OEtNT2NaZ1JCajIxVDF6OE1BMFRNVndNZWxyN1dZT3FCV1pjOHhB +KzFRd3JNNG9wWGd4NTZ6eFRsNzIrdTN5WHkxVy8rUStSdXpnNG5qL25UL3ZFaEhYK25EL256L2x6 +L3B3LzU4LzU4NlA3OHdVQUVTQU8rSkpxQS9FUWx1Q2FCYlI1WUt4QkdQQkErMDdnTFFCWkFQb3Vv +ZmlLa3NObmhwQWJ1UnByTDNDSU0rL1FzTllnZnY5Y3MrZU1MYTU1VFRBVUk0Y0RRbk8rL3hzL0Y1 +eGlhN29haU5zYkY1ODdQQk1IUm5IVGRPTWFiNmpEZWdLM2huaDhoOHRld3RSa3Z6NmhGcy9NYTZ6 +aE0zNllYR3hZY0loUHRmaG51bkdzd2Q0RW1UZkVxVm53YUw5QlRPcXpqbkN0Z1UyRDdYSGh3aHIx +TUJZTWNoT0tlVTJIU1g5ejROMW5mNnlQUzZLU1EyOXdyRGFIaWJwYnV6MkVxTi8ydU50dldPOXRY +ZzhRNjhWVEw4UHBoeDdoQ2s0MTlCQ09abTZmL3pqUWd5R0dOY1JNTzliWXB5Nzk4TXorR2hTbjVu +RUNyemRXQmd4aTB5Mys4U0N1UEhwcExkUENwb2JtTWVjd0VkZGVmY0FMNDR2OTdYOXN4SXRCTHhl +dndOQURzaHpBS2FaNjdLVWZ1UndLdU5kci95R1NVNy9zbXovbnovbHovaFIvL3B3LzU4LzVjLzZj +UHorTFAxOUVhd0tJSkRoZ0pqUVEyZTZJcUxrMFJNT3RrYWh2a3dBaEtJVW9uRWdVQTZqQ05aYVE3 +QU1HS01USVI5QTFuZGpJL0xQZi85WGdNUWptRC83VjMwd3U0alNQRkhFSVNMSGVUQkNiNXZsbnFz +WFdqQjRnMWpxSTRPbVFXNDNtL0t0ZFlxcXpBMGw0VVJjUmltMHRYQm9xTCt3SXJXbUlTak1Ranl2 +Y01KTDFlTFpIRG8xMXFEQ21ldFJJT0lZWStGS0RPWGtOZURYZDg0clVIampsY2NFZ3JuanB4L0VK +dTVqd3dBK0hPWHpDWmg3L3ZvdnB3TUJUdVRBbmoxanlWcnpOajJkOXdJVURCK2ZtMVdCdDN0Uzh2 +ODJCbng1Z3J2bjhCVzdHZ011ZUdoVFAxdUFzSnI4TFZqaGg2WDgweEdVWXZNQ0VhL1UvdUo1Zllh +RkhjNTZaVng4TnlsRmU5RkJNUnJKSEx2NFFOLy9odUxyZ1Y2Y0R4MTBjR2hFMy9uazNZdzlQZk9G +Q1REWFdvRENyVXo2SGpON2hSVDQ2YmMvbXovblQvUHc1Zjg2ZjgrZjhPWC9Pbi9QblovSG5pMEFy +TUpzbEVMaEZNaHh5a1Z3UU5ncXFDSUN0MTRpS3hMcUt4RHpoYXpwQ1hCcE9vSEtLQjdpM0JuREk2 +NmZrYncwcWhyL1VUS3lFSlo0LzBuK2ErN3laUUFRYzNrcjg2Vy8vVXBxQmxHSkFtUGp3ZFlndmxo +cmxkMEJFVE8ramd0STg1TUd0ZHMwaUpNTEJtVGRZeEdVZUJ0ellhMDVzdGNJaFBreisyTnd6dFpa +NzhYRnZ1RGVIT1lLMEIzZnFoOGZlSGdndTlZbmhNOEhJaHkvcjlLbEdnc2RjNC90ZWdlWXdPdjcw +Q2s5RWFIaE9NUGJZYXcwc2N1RlpmR3NKamtIVmhodDNHQm1kZ1hDaUZyZzhkK21kQVJzVHdabG43 +L1hLSzViOTRzRGFkWExpaWxITTR4NXVOZFUwaVgwNjBWKzU4YTl1cHRNbitNdWhQV3FRWDAzMGdY +OTFONithK0lHeHJKRlRYUHZsRHE0ektEMzZiRC84WXJyRVZCY3VYQTRuZGRSdmZFUGI3ZTM4T1gv +T24vUG4vRGwvenAvejUvdzVmNnJwcy9nemZ3S0tWTUd6K1lxcENJakhUK0pFaXdSa1NDWXh3QWdn +REExalVIZVhOZDdZOUsyTk8vRVFyRGNETmF3aWFpVGsrTW5Zc3ovNXJWL01yeVMwZVdMODhiLzlo +WkRxWWw1dmREUkNJNjNUZE9TSjQ2MkxPYzhWN2xtZnk5ZUJCRGlzUVFweXZNVjZ4dHZYcHFzVmZr +MzJHU2VhakJPY01hZ2E1YzhCY0pnMEVDZjJxMGtlLzdJWkFXaWdkV0xDaEc5M2F3MjRpQUt1MUh6 +N3hleUI2THQ1RGNTbmZ1Z0RvY1IwaDA5ODY4enBKYXo2NWJONS9NQXJKeFBBWUsrN2ZUMHMxSUU3 +ZGZjUXNGZlA1TUFuelJBWmZBNGhCeFVOaUtlV2NIbTgwcEg4RHJNS01EMCt3Y09tWHZFWXNjOW9S +bHhpOWh6Zk9JREhzQWJQZU1VSjdQSmFZeSt1NWFBdFBZaEczdC8waU9XeUh5ZlZxRnoybU5Oek1l +WFJDeHcvaC9oYjZ2YWRzZXhYbTdyTXd3Q2pQQTRDV29JUnJ3enE4MlBRNzhJbFhPTHduSnc5T09i +UCtYUCtuRC9uei9sei9wdy81OC81VTk3UDRzOFhZZnJMd2hxajJSNHlHVkIrOXh0b3hDQzhvbENF +SkQ0RDdia21WeFFBYWFxOUJNQlFQN21mbElsRUlRZ0NSbTRDcXBHSTNEUC9SN3NFNjduQzVCRkRM +Q1Q3NTUydGdkbEFpSHdhYjUrM1NTVWVCckZoMHlSN2lNbEFGakhCa04vQlAxeWQ4NU83T2pWQWZm +QTdCRFRCUHZuVXJDR01iVTRlM0JFRjNCVzd1enpFcXliOGlJZURQL28zZnp0NUhaTHlHRGlxME53 +MTJkQlVlQmhGWGdMUkIzWENKWThMVHhwc3Y5cmtzODR6b3NHWFhHcVFxK1lnRXJHdHEzaXM5Y3g2 +SE5vRGo5enlXc3RBaGo3NXZmOGFWRHo1RFFZdEovb012d3NmeEk3cjhIZnpQY1R3N0NMZ2N0cWU0 +Tm0rL0lmamNNZ25QNnpxeDV2bkRoaDdlNGlxQXkvdXVLaU8xV2FmZU5XZE9UMlN6K2Y0NHZUK1lM +N0Q0WjdwdTloNEZBOWVXSHA0aWNsWGRCV0RIaWI3eEU5ZDl4dzNPSlBIZnpEcENUN2N6Wi96NS93 +NWY5TGovRGwvd2pkL3pwL3o1L3pwK1VmMzU4dmJHcVpBdkVZU25XWUxUTndWcmVBSXRRa0lUWlc0 +UUJDRFlIc0JWSmcvMHJYSHIwRXdqMzMySTgvY243Ly9meDBCNnEyRGRmQUF5WUNhWWRqbjF3NnNR +UkM4dmo5TnVqYmRBUUsvNys0T0ZqSGhnNGxaWVRLbkdSMndFUnY4RGdDMWFQcHpmUmMrS2hxWTFh +UzVNY1BsRXJPNFljYUJ0VEg1WC85VjFvZ3ZyL3I5OUc4T1huR1lWQjBFcm5IeVI4aDMyU09leHVr +RFRQWlpRd2hFSXI4YTRMZmVaL05FNDd2NCtDVUlYUHFNQzBOKzliU2ZjakNVQzI0Q01naThoeFR1 +NU1LRi92ZFE5dGtReXdGcFB3UERZSjhoVHZITEs2ZDRYOFYrNjgzMTdaTTNZdGJJd1dBNHNrYU50 +RU1IaG9QRU9seVlyd2Ixd1ZwYzRnNG5lcERENy9hYWQ5bG5QNXp5MDR5WUxsemh3M280cklkUEhU +RGFxNy9lNk5ncnJ6NnFTWS93aEM4NDFHTjlEb1hqQzc4ODVXNDlqbDAwUWt0aTJUdC96cC80blQv +blQvdm16L2x6L3B3LzU4LzUwOXFQN3MvOEs3aE1nY3lBdlFTS0JCZ3dBQVJSUE9JMTJUeHlyUWZN +dW9LUkZIaGthTHFtSWRSUDBjQXAybnI1NUJVVGdjemdEWTUxMXZzMUJXUjRVMk1mSVlzRmp6Y3Rm +L2l2LzFaeUdncTBCNG4ybUNjSXdrUXdRVmpMMkJXTUlWNUY3SzJUQm1TY3VReHg0Vk92ZGJEQnpq +aklKQnBFK29uZW15dGN3RXBRZUZBamc4bU5DMnZFeG8rNHZqdWtyR1VZdFprbkV1dmxsVTh1UEJC +TnhlZE5CdjcxZ2NCZ1VudTVkWG5yMUw1YVR5ajZLRmJYOUdEQUgwemlFN0ZjT2FoT1dHTGE2M2tO +SUI3VDI5K0RWQnc1MWFMUDRxbEZIQWVUbnVEZm9lKzVtUHB1WGd4eDdVdnU0eUcxWFh6ZmMrZ2Ri +bm54K1dqak1YWVBJYkgwRzBaYWhGZVA5TU1lZWNQZjRjS3B1LzF5VzI5Zm5wMkI5RVE5L0tGUE5U +SE53TVA0M3A3Wncvemk2Z0VONG93T21rOGRhcFFMSnJGb3F6Rmd0OWJGQTN6UnczNytuRC9uei9s +ei9wdy81OC81Yy82Y1AySDhMUDU4RVlJSllKRGpqbUFFU0d4ZWdZSWhBa0JKRUN3SkFXbzA4RzJX +U3hLQ3E2bThPUUFlT0RFUjVDNldJYjUxRlppM0xyQm9pa2I2SFhsN3JZZlhXeUI0REdRU21IVUlV +YVRDNFZHb3Bva0ZFendkUHN1SFRMOXoveGpoeHJ0QkdjbCtOWnJUS1BudGNRQVJ2b1l5YjBXc2Ry +a0p5UitKdzlxYVlNU2xCc1BldkdJeHVXWWIrbUN0UHRncnZ3YUxLNGRhY0MrSGVzVWlRUHR4aWtk +WS9hcUd2WGlYaTFEa0p5RDVIRGppcVNrSDhzWEFGWjRmRHQ1aUt2RThnMHN1NjNOZ240RHNKMmhy +aWR6YmpmS05PM3ZrOUZZRmZubkV3eE5NRE5SRFNUeWMwb0k5K2xlZDZZWG5QWFRGTWNxVk9QNURv +aDg0Z1V0ODhjU25aOXpZWjQ4YTFNTFVlSlJiaitGV201elc2Q0hkMkdPTitQSVk3bmoyandwNExo +NmVZTGRYUHM4ZWZwN0RSSDVZZW5BNGVHREVpVjdwaVp4eTRWT00rWFArMUwvNWMvNmNQK2RQOGVi +UCtYUCtuRDgvdWo5ZkpseUFXVWkwR20zU1Q4ZEFJNVlKRkM2WnpSSXB6RjRFYVFaQzdVTWtFcjhh +L1FUSVVHTExvU2pDRThlZENBQkZSSVhzVndhWVdtT1E0M2ZKclZXMC96OGw0cFBEMEFodmZ5TFVX +eU0rUWNobmpaanVJZkp5ZHNEbXVkcjkwYkM4aE5haFZ2VWdGdUhteFZJUEhMRGh4SE9jcVZlRDVO +WjRCMXp5TSttSjM5dVQxSHFOcVdFSTJqcUdnZDlnS092eHIxbmkyaWNmd1ZhMExublY3RURUQzds +Z3cyV0ZZMTROM3hyVUdudndZUTh1Q0FSZWZld2hwZmM5bVBCQkQvWTRaTlFBcTloNGc5bmJPWGM4 +aUdNUDNPSzR4SUhUYzNob3lGN0dnMVZOTWVqcFFXeENsb3NacktVZjY5cEhQT08vUnBkWDMxM2kw +NjFCUXc1dXVvVlhMTDEwZCtram5zU0Z4VE5yOVVaZnhGRTd2c01oZnU0U2cwN3hwalo1NEhWQTZG +ODhjL2xxUlBHdGtVTk5Mckgxd0p4ODl0bVB2L2x6L3B3LzU4LzVjLzZjUCtkUGErYlArZk96K1BQ +bEFlQWFxbGdCQ1FJb2dDVDFsa1pBVFVTRU9hSkd1RUFJczRlWWtTK2hlUG5WZ0N1YXlBaFFiT1I3 +bzJJUE1oVklmSWI5OWpDZnZ6ak8xRENKOThQdi9YSklJQ0x6Zm1YQlhvTmdHY3ljdTN3R1FoQ3Er +QkxwZXdjTThDS21CZ3Y1NzhOek9UVmFiSUoyRUltSFJLVGpSNjE5TG9ZYU5jVm5ZclVHWDhUTHZP +ckZHd0U0Yk1TRkdhOWl5Z2ViWi9yak02N1ZnelB6QkU1RU1PSU5CdjBTQXhaNWNlbXpOY1J1bi9n +dzZJV2V3R2EvT3ZXS3NPVlFnMEZBbnNPUFp6ejVyQ2YwSUNaY1RDYTN3d3ZlNW5Obk9qbGRhblk0 +ZUk0RGNReHZqSHFRcUl2NEk5TERZazBONnJQODdTTXpxSlUrOWM1ZFBXS3A3Y0gyYU1RQmlZc1k4 +TjNZNGxtalZ6aFJnM2w0eE5FRHZmZmRYVzI0VVl1ZWVHYU4vV0l6bFpqdStJZFZyMnBROGRVa0J3 +N1VoU2RZNE1XZnZ0alB5UFBuL0duLy9EbC96cC96NS93NWY4cG40R1ArbkQrTmorclBGN01RbTBZ +YVFBb3FJU0lWVDFCSUZrQlFqVUdlaElwQWxxUUFNZ0tBakFLTWhudVRvQWp6d09RTjBjV1ZnekFV +NmhDSTRJNEFielljR29RTHBDSVlsSm5nWkZEL0doWHNodHhpeXUzTmttTDdIRW1lMTd5d2Q5UlV4 +UTN2ZytVWjV0c1VjZUNDbnhIa0kwcGMxZmpXNGMvQWdjL3F4QkZNOXVjd092R1ZTODFnWEQwUUl5 +SjdOemRzTU1BbEhsSEpMNTdQTlpvMWpHeWR1TERKMTRQSmN6MTR4UHI4NjJkcWRnQ3F6eDR4d3VI +dFVSTkJHUld2L1hMMUVDQTYrM0RzRFJBdXhJUkRUQmoxbmpIMHI0TEdrVnJ0WmRZSDAzY1JyRHhx +cHpWRFRqSEV0dzRITGpFZWd6Ny9CNy93ZXU3TkZ3M0puZis0WEsveFpCMGRlUnVuQnZGeUlGeHZZ +TEtIb2VYTElYRlkvSWZJV3I4eWdCTjZqMVl1dC83cG83cG8wT0VJajcxcVltQzkxUU9jK0t5dndY +N2Yyd3UxeVc4djdzM3hpWGpXd0R4L3pwLzJpQkVPYjgvOE9YL09uL1BuL0RsL3pwL3o1MGYyNTBz +aGZpb2xmZzhBMXdBa0U2WG5DdktUdUlJVmI0M1BpbEFrSTB2V0JpR1o4RFE5Yng1dWpRdGdBbk9Y +ZzNqTXA4RkhMdEJpaUVmME1kUTFFaUgrMG5iaitYVUZKa1VJSVJHTFBiRDZuWE9OdHNmekVIZTVr +V1FOb2p2c1I3cEd3S3RtT0RyVVo1L2E4S1RCOE5nRGh4eUlaRHg3Q1VXc21za2FQRmtqTjI0SWxh +alVTUmoyTVFvaDRLUUd0UWYvOWxsckQrR0U1eE9KSGxockQwenc0MU4vZk1heFpxc2ZKckVJeHdV +bllUa0ExWVVIZDdrOHI4Z010VEFVSStJT2x6SG9ZYkhPYzBMRm0rOXk0OTl6dGVzQkVhcTdab2ZO +MnN4Zm5Cek94NWU0REYxVDJldXoydWxQUHhnREIvYmkzM1A3UEtjRnVjeXBWOTFQL09lZ29TZjE0 +VjFmbVNJSHlOWGFYdUZXSHMvRTlhc3ZPT1FOL09ES1VJK2EyMWY5VUJOdThTRnVUSGZ4Y0FFZmJM +Qzc4S28rdGNGSjJ6RFRnWmpxRm5QK25EL256L2x6L3B3LzU4LzVjLzZjUDgxOUZuKytQUEEyeGxz +S29JbUQwRFU3elQvUXdKalRJRmVCQ09LdUFKZDl5RWVLLzkraENzeHpkMkprVUFYNTdKSW54VjVE +RktVZ01menJXWnF0R0kxZ0Roajk5T3hmQ0dOZyt3MS9uRnN4eXFzSnlQQWM4UWp3V2EwK2R4Q2Jm +ZW8wQnk4aU94Qkx1RVJsRGsvNGtEZUh3RFVJWDMzRGcyUm1JaHFIanIzbGg2a2RoT0pyUGlQaEQ3 +NytSVzJtVkd1NWhzMGxwcVpiajN0N3JGRmpjOEpVVVlyRmZOMnJQbGdxWWpYZ0dJZHdCZC8xQVIv +MkU2TWF2UFVoSUh6Q2hnLzFPQkNJUjJ4cm4vNDlmWURCR3J5b3dUcllZUlZMUCtXeEp1dE9wSGdV +aDA3a3dGMXdYcDRhQ0MvMlYrVGx5bHJyUEhkZ3FURUh5UmxKbmRhSnhYUjA1N2s0OE9raDQ0cVIr +bTlPVGZRTGx6eDRxbm5oMVRkN29wUExqMnM2VmJzNTYrQ3RRZlhyV3c3VXFYZjRoY05uY2VsZWor +aElUK3lwWnVmUCtYUCtuRDlwYVA2Y1ArZlArUk1YbnMrZjgrZEg5dWZMMnduaUI5NURHelJiVTRG +MWFVQkJLNTdaQlBSVHJ1Q0lSUVRpSlVFR2dZamp1NmI3N0c0OVlSQVBRaFNCSUdKUWxIbE5abENt +YVNOZzlCMHV2NjdnTStFd3RxSVZoa2hrSVFvWkdnZGJhMk51OVhRUXQxbzBRb1BFKzlhZ2Ntc1Ez +UGJDb0g3cmtDaUhabm56SUkvRFIwNUNRVDRCcUZ1ZFBzT3NVUzdQSXFUakRCOXdWRXpxZ2NsZXVS +aElURTNGRDZ4NkFXdDZkTDFRQ3k3ZFlVdWpiNTN2R3AwRDVYaUVXVHo4ZTdObExRdzQxeXV4ck5k +YjQydU91NGpQUE5GWjQ3T2VkUzNNWXNLTUM3eXAwUnNaK0hHbE4zQTFUdmZUSFE3a2NCRHBxMmVN +bFBpSDNaMFc3ZEZQYTZ6Rk9aeTRjOGtwdHYxcXRVZnUxbGNkcTkzbjlPTTRvQzNQNWJRZTEvUmty +Z2ExaHI3bFU1ZWVlb3VrL3psZ2poZDZkdENveDNmcjljNEZ1Ky9XNklXYThFUVA4dlFRVVI4TzU4 +LzVFNGI1Yy82Y1ArZlArWFArbkQvbno4L2l6NWZnL3ZLcVlNQ1dlQWtzVklTaENLS3pCc0VLOFV6 +ak5CZnhERUpVaXZPVHM3MUVuWGdYMXg3cnZNVXhwNkdhQ2JRaFByQ0VCcE9yb3ZVV3hhOGxJSUZC +cmJIV3NLWUVJVlJ6a0pIQzM0dEdNSnlhMmFFK2Nkd0psZ2xqMFAvNi9KcENTUmJQT212Z0ozcUNW +ejgrSEhKaXE3RUdyWmswMUg3aUVFTjhqZkZXQ1YrNDhlWUpYejRUazd6RVphLzhudHVISHpGeFps +NSsrV0FpUkZ5YjB3TlllK2pnVnd5Q0pnNXpMbmlzdFE1K05ZbG5mZDdLWkx4RktHbzJoMU1ZNUxR +WFZvZTNIdW1EbnVHN2V0RGpjTXBNSjJnNDVEV2Z2Y2RmQkgzY2lRMmZDNGN4Nk0zN2pqT1k1UEZk +VHRqYzRiRWVkKzc0RnAvUmVyaVowd00xMGlkanFNbGhLTDlhN1BGZGZGanB3dUZwSGUzU09qMlpT +Kzh2dDRNUWQ1N2pVVTNpOXdEQVNmaTg5VFNoVG9kS2pZb2JkY09KTHowVXl6cmN6Wi96NS93NWY0 +bzdmODZmY01oclBudm56OVE5Zjg2ZjgrZkg4K2VMb0wvL0d6K1hDUVJJVGhnQ2E2NTVQNTFMQ2p4 +UVNKWkFrWXBnRE9BQlNnRUhBSENpODRiRk9nQ1lCRWd4SzA2a0FhcUJpa0FVbzdnSW56Z1F6Snkv +OTgvL1JrZ1EwenljaGtacU9oenFxQWxoSWhaWTRMUUh2ZzdOa2c4MmNZbjJNZWVYekNNYkJ3aDBq +NGt1SmtIWUE2L21PdVM4S1NBTWx4am0xSWRIZURSYURyWGd3NytDNWp0TzhDRjJjTi9lR2tKZW5N +cEptRVRXZzBiY21sa3pjYUErZkZxZlJsL05MbzBtUGtQZnZMMFRRMnoxeXdVL2JzUmxDbjE2eGx2 +NnJoWjMyTVR6WFIvazF6LzRHbHRkUHNNaEhuTzVjSVc3MWdxemkwalY1UmxUaVlrbm4xM2l3eE9q +SHo4MENJZlAzcVRneVR6ZHVldU4zdHI3MVAzOEszZld3WTR2MytXbWVRY2ZubnBvaUVQdmV1NHYv +ZXNwRE9acHpEenV4ZGM3UE5BaVBoblRYbm5zeDVQYVlLY0p6eCs5Zi9jWS9mTGJXeStJNXp0cytQ +QjkvcHcvNTgvNWMvNmNQK2ZQK1RNNnZ3SFQvRGwvZm1SLzVnZFEvK1F6Z1NzT2VBa1JhWVBuQ2hU +TUJzK1p5enJQTkVOanpSRzRQZUpvQWhGSWhnQ0ZFUUp4ZWlNaWhzSUJVN0FHVzhkUS9TTi85emFM +UVgvd216Ly9WY3hNQVlzbUVRRVR5dTk1eFZqU3ZHWFJIUEZnNnJEZkd2dUppY0JoaDRVdzFXZ2U0 +ZW9pWXMzM1dTMUloNTBaNERaSHhKNjcxQWFMQm1rMDNHb1JuMEZyYUxqaHQ4NjhHRG5BRGhNZU5G +VThJc2VIZWlMUU96aGhiMTlnc0Y0L3hMVGZaN0hFd0pYWTZoUWpoK2p0Z2RGNmV3a2JKblYxRUlz +MTlycWJsNU9vY2FOdnpHZVBlUEl4cU56V0UrYTNoNy84OERzSTRNZTU3OWJLaFhmODA0cjRMbnR4 +WThpVGVrNlg4clFYY0lqbE8zUGdFd2JQbUVKL1BCT2ZrV0xpTTZxOTF0R0kydUNTRTMvK0dYSGN3 +bVlOanVSUmo4T0IzbWpLV29jZGp1eUZEOGV3cUExK0hJbkQyTDdMVzlQS1lhMCt3QWtqTFBQbi9B +bWo5ZmJPbi9Qbi9EbC96cC96NS93NWY4TGcyVWYxNTR0b2lkNENTUzFtVUpNMk1LQ2ZoZ1ZEbnFL +SVhnREJyZlVNQ1JVRFlpVXlwMmhOc01kbklQM3h2S0xFUUpqNHhJTnNSY0VEcE1NaHpUb2hhbXF4 +dXJ1SUJDN2syTWNvM3NUWXE4RnFVQmRjOHRnRFg0ZDVna0tzR1BZanIwSkFKak5xSWl4eU00aFlh +c2FYWm5pVFphK21Fd0l1ektsWGZqbjlpZ1hSR09iOUhRQmlsZGRudjF0ZUU4R0ZMN25kSzJKTjFR +TWljY2ViV3NYUkszVXdUWVhrZ1BCZER0emlpb0QwUlZ3SFRybTJ4enF4WUZaWGgxeWVxVjNkNGhL +ZDlYcXRweTZmbVo1bWFod2N3cTZITmFpRENnN2Y5ZGQ4QmVsWmN0OTZCc0NuWjJMRGIxai9HUFQ1 +ZXhWcWxGK2ZySWRSalhLWlkyVDdmWllEZHZuVW9UYjU3TUZ6ekhRSEFCUDY3bGN1OUJFV2Q3cVcz +MkVKRnc1eHJjN1dMYWJhYVJ4dnNGcnJPYzdFZHdqQVl4ODl1YXpWRi8yZ3BXcDIvcHcvNTgvNWMv +NmNQK2ZQK1hQK25EOC9pei96ZDBBUlJneEVaYk1GZ3JvRVFJemlCUVdTNERRTFNBMldDT0ZBYWJR +WVloSTFrc1R3V1VKRUVTT3ppSUdrTlBBRzBzUkFydWNFRGFoR2VPYXRDc0tJQUc2QzlMWWxCWjVa +N2ZIMnlYUERjM1VoQ2daL2ZJNGd3ejdORUVzOWNQcXNFZW95eEdOZ0dPQlhrenkrMXpRYXFVa09D +SHMxRzE5TXJ1bTRnY2ZCSVJZRGl1L3ZDWWdwaDdjUU9JR1hNRDNMM3VOUzArV3JJUFFCUis1eVdB +dUxTeDY5TSs5QUl5YWY5UkZPdUFpbzVtOU5jbGlQQTduMGhkQmh3YjErNHMzaFZhN2RDZE16QnpW +dHdCZURYdnlmTmFqRHhGb2lyQlpvUnMvVjRUbjhOV1hmR01KTU03NjdER1prTk1iREF3N01XWWQv +SE9RUXVycWVRL0V0dWZFcGoxcGM2cFZMUEJxcGNjV3dYbjEweGxpKzQ5RXo2K1ZUTjk1cWZIUGlW +Y2R5MjZNMzl2QU9iUGpzd0JtKzFGQk9jMWplZHhqbnovbHovcHcvMjVQNWMvNnNGdWJQK1hQK25E +OC9zajlmQkk4WUJRbXVZT1NtOFBkQ2dKR2d6Vkc0TlVqM1diTVZvV0JDOVJ3NEYwQUFWOEJJOTda +RVljQURBcVRoTTdBTUk0LzlHcStCOHZnLzU0Vlhnd25ldkNFT1FtQWxPQVVhaE1ZVTRpRUgyUWpv +OEF3dXRidkVqV0RlaFFDRFp4b25sMWdhaVMrNWFsQTVQYk1PRDBUbHNMTkhQVVFxdDVvOXQ4ZXZM +SGl6QkRkek91aUlCM2RNRWlFZEgvQ3F6M1BDa1lNWThBK24yT3JFSVd6NDFvLzJoZEVaUVY0Q0pn +NzhxRVg4SGxxd3dTSzJmT0g5blFmN0ttWjcxZFVZNXNTR1FYOGR2SEtMZ1hlOE9GeUlHWDdQNUZK +akRIcjc4TlZhNWNkaERIQnhHUU5mRGd2ckRQR3k1blJCcDJyMnpMb2VBbm9ySnB6aXdPYVpmRGl4 +eGwyLzdZTVRwK2JGVUplZWVQTm5MNTdGazRzWEhMUmRvMTRjeXFVUDRsaXZIbm5VU1N2eSs2NEgr +T2loVlUzcExVd09WbmNjelovenAxcm16L2x6L3B3LzU4LzVFMDhHcnVmUCtWT2NqK3JQRjdQNENS +ZHBnRWdxR1JESTBqUkRRR1FnWG1NRVE3aG1hVjVGUy9BS0JnUmdSQUpNeFBZcmdGanRBMEFjelZD +QW42VHRreE1SOXNpaEVJMUZSdCswS0FvV0E4a0tVb2M1ZVEzRU1KNTRqRWFNMW5USTU3bGMxbm03 +QVpNM0R3YURpbGVqbWlkNnoyQ0RTejNlU21tR3ZUZ2tGdlV3cDMxNHdiTTlSS1pCOWpod3pETXIv +alhWUEo1Z2Rva2h2NWoyNllsR1c0TTNIT0JYdjJEQWl6enFzYzlhL2JGZmIyRlVidzhtdmZDTXlD +b2dkekdmTjBUUFc0d2NCTWV6K24wV1U1NzJUcC8wbXFCeExEZnNNTmx2bmJ0OWNqa0FhbEJHVTZz +NWU4MkpDVzgxb3lmV3VadXpsMGtJbkhHWTBKenZPSkVYQnBmYzhJa3ZOZzdweGVVNTduQWlsN285 +Y3hqcGR3OFRXdEZIT3Y4MmwzcnhZTDk1ZXoyM0J3YTYxYmR5NkQ5eS9DR25mT0wySUlYVkh2cFNF +Mi9Nbi9PblovUG4vRm11NTgvNWMvNmNQK2ZQK2RQMWtmMzVJaFRDQjhRR3BFaG1JYklzUkV3TjVC +bmlyUU5Hb3hraTYrOHVqdUlRN20yQmdzVkwvQU5Pd0hJcVNyRklBMTU4Y3dwUUZLSDVYWHFGYUlS +bjRvakozUEpxcmtFWWlsTWtiT0orL1dQcHcwdFlubnNUZzZRTzlZcnJHVHcvblgvTHBWNjVOTlN2 +SWFpQmdPMlJuNERGWUh6UFlJQ0p5VFNCQVZ6eWl3OERMdFhwT1lQSytTZS85WXVKcjZuRTVJNUwy +TVhWS1B3UWwrL3F4TEZuY2hLcmZ1aUJac01vSDZISzdSQW94K3JUbS9hTEdPQmlHbm5GVXJlK3FR +TVBZb3NqUHo3Z1VhZTRZbFlMOXBZdnNkUUlMNTdFZ2xtL1lSUURUaGh4QW92UDloTzBtQzVjaVkw +WDYycDBPdkVkLzR6aGMvcHhHR0ZUVTdrd0J6LzkwSW9EQ0lld2llK2dyWGxkelE4TEErSUx6L2FK +UVkrR2RYakdnend3MFJ4ODh0QWhYbXRHT1hIanN6dzBBcGY0ZW0zT2VqRTlrMi8rbkQvaG1qL256 +L2x6L3B3LzU4LzVjLzVVMDJmdzU4c1h3bU1JYjRFVUJ3ekFMb21SQ3d6U0JaSElkMkFZT0taOWI3 +REFtZ1ljNFFKaXZmaUlCcHpKL0RFc2doNURmRWtPWU9YVUxFWDd5OHVJUWpBY1RPRU5rWWE0VzJz +VUYvTGtnc2VRajBpc2g0Y2hGSjJobVllZlFOWHYzcjg0L1l6bi93Q1lrR0hVS0FlWmVtc1NqVldm +T3NVWDIrVzV0d2c0SVJMN3pjdEJZSEI1N25mai9jcUZOMFRlMUtnZkx1dkZUeTNIQmM0MDJWME1t +RFNaaWVSaktQMkFDLy9pNE50K2EvQmhYbTQxaVNrZmJ2RHN3SVNWYWRRamo3czZpSkhnSXF4N0pr +ZjV4aHVCMDQwK2llMnRpdmp3eWF0T2I2ZDZZUG9PV3dVdmxubFlmVFl2bjJkcUZqTUdQUTNrd0Rn +OGNsVm44b2hEN09JN0JCakZQSjdFVkRkZDB3RSs4S1hQY3Zrc3R2cXRFVTljL2RPei9ycUF2cnZU +bUZ6cXNZNS82Tkxob2laWTdhMUJtVnc5TGpuS1kveHp1WHkzRmw2NGNTWW1EWmliUCtmUCtYUCtu +RC9uei9sei9wUnIvcHcvUDRzLzh5ZWdKbjBSQkNFMlNHWWhZeGpJRXNCM1lKR2tPTTBBSEJnRklr +Vmp4UUJPb2dxY2NPenhtZEFVS3hZQ05SbUI0dFZRM2hBaFF6RmlhZXdmL1p1L25aLytDUnRtQTli +aXFDZ01STmtIczd1OUJQbU01ODJIbUFoR0hxTnBkdWVKVHk3UDhBU1ArdUNIcnlMeWxvZllpUUZI +bnVPU0NPelY1QnFCOFRVSVRqVjQ1bkxncU1POE9PSmJJei9PNU1HZlF3SldZaVFLT2ZCZ0x6NnR0 +OWRuZGVtdHo5YkQxSjdKeVV4cWRzY2Ruc1N5eG5xSEJVTVFkUTd1ZHdIQko2ODRjT2xkRHFzVEhE +N3hiMzhQNzBmUXp4czVmZmMyREY1NXhDVnF1ZW5FSHJ6TG5acmY1MzJQUWE4djhxblJYV3o3N0pI +VE16VlgwNTYzVDNxa1RubmwwUnVZOE9KNURIcTlxZkVkUmc0aFBKcFhsN3Njc0ZqblB6WnFVbytZ +dElvTFhQVUFGSlBHWWRaRG1PMFZ3ek5yWWVVOXRmUmcwVjk5bWovblQ5ek5uL09uUGZQbi9BbXZQ +UFBuL0RsL3pwOWlmVVIvdmhCS1FNUkswSDdDWmk2TENscVRQQmRjSUdLM1htS05zbzZwa0tFaDRr +Z3F1ZlVLRlM4L3ZWOGgzazRRZVF4Nit5S0V1eFJvSFFFNkhCU3ZNQVhMSWEvL1Q2ZGkwVlJrdWV6 +UkZEa1JrM0V4NFlIRjVRMFFNUnJpMWtRdU9KRHBNK0xFSkV5QzkrYUVrSmtJUHZIbElSelljT2g1 +QmVhTmd6bVkxSzdtNGlJV2NUV0FRWW5WNzg4M3R5RWYzQXlBSTk4SmpMQUlSaTRDZ3JuY3kyMGQw +ZG5uTzF6eVdxTlhCSXd6KzRzWkJwL3RjWm5YUnlMQ2tZc0dlbkRMRllIZkhkNkk3K3JGbHpYcWhk +MW5hL0NPSS9oeElxY2V5QU5yQlN1bS9QU0NHd2NWc2ZzTXR4eTRnNGUrbU1WbmNjV2hDUmZzMVZ2 +NmNSaGdrNXQrOEZsdHlxY204ZVNYeDNmbUVVZnY5VVU5TmJXYTZTWUgwdFhPdk9veHJ5WjV4SkhE +UG5uMG00YzhnMGRkNVVOTU5hdUhJZVdIeStmMmNQNmNQKzF4elovejUvdzVmODZmOCtmOE9YL0M4 +Skg5K1FKWWs0aEh3WUpKNUtmZWt1aTVvRFlqWHZIOVRuekVZcDFFQ2xBd2NmcW5tRDFYaEVNQWdR +QXlwOElCMEFEeEdkNmVFbWVQTlV5bUdJWDR6bVRXRWgwY0NrZW05V3BCU3A3ZllMU1NESnMzUE42 +d1BPTXRSTU12bithcEdiN09JMXBjR0JuRDNnancxc3RURWNzSHUveGZEWDRjV0l0RDJQTm01N0Nv +cDJJVTB6TUhrVU1MUmtOekhEYWFaci82WU1FbE1jaW5UemdRUjA3cjdQTVpoMHlIWnpIa2h4VnZj +cGp6Vml0dmF5NjNHc1drQVhIdzdWSURJYXFmU1hOZ1haMWl5S1UrT0NzNGUrSEROMjFZcDFhQ0Y0 +dkJjQ1N2V3V5MVRnK3R3NFVZVEd2a1FEOVIwNXdZZUlWSExlcXlUMjZ4OUJJKzlhdW4zTGdiOXRP +YmZvdFo3UExDTG9iOUxtdmx0cmVjd05rOWVJU0ZRZFhya0xWR2JOalVXVjNad3gvV3l3R0QySFRB +b0hLMGx3NVluM3VJNEdIK25EL1ZLT2I4T1gvT24vUG4vRGwvR3ZQbi9PbHVmRlIvdmhCT2JJQUFw +aGtDSVV1aEdpRWhnZGdrbWFTU0FLZ0pTRW54TnkrUk9YRTB3cjRLb2o4Uk00T2NBSW1CZEFRenNi +em00TXFoOGVYNUMrSnlJTFAvMmhiQks4VFFRR3ZsdGpmTk02NkJDQ1VZeHBQMytUV0JaeUJURFBX +WUUrTXg2UE92aEtrRGRubmQyM2o4MUtDSUoyNll4SUF6QnYxL25qOUd4NE1jRGtFTk0reTN4M29H +WVZTL0wrODV6T3BSZy9WNHhhRXI0cjQ4NXRwVU9IRHFUaFRoNy9iNXJpNFltQXUvWXFuREJROWVm +TWE3OVF3TmwxdzFIV1BacDQ2YXlMelA5cXRYYktLVm4yRFZBeDh1WGJndzNPMmhCWmp0aFUyUDdj +V3ZaelJtNE45enVYRHQwT3hoSGk1UDVOWmFRNlB3MGlBTTdqUUV1NkVldFlsUDB6NDNMbDNpbGlF +WTBUTTVZQmZETXozQmtUM2x4Y0VwRjkzSXAyYjdXZy91eGJVV2JsNVJyeldHT1BTcFRuZHpQak03 +dnVXY1ArZlArWFArbkQvbnovbHovclIzL3B3L1A0cy9YNElyd2dMRkFpT1E1bnVtSUlLMUdBaW1B +bFF3amJJT2FFRVZoRGdGS2hiaFl2ampmVTBEaUFqbEZOZWNmREhvRWNqUVJGV1JFUkd4SzFnT3VZ +bklPZ1o5c0QzL1NwajF5S3JZTXE2Qk1NRmlYbDc3TzRxZmFCRFp3Nm9HeFllWWN0dXJxZXFVMTNm +N0NjTno5Ukc3K2RZanJ2Z2E1VzBaUGd6aWhFT2QvcytIaTF0OVJFaEE1UUN1dkNHNmcweGVNVDFY +azM0VWl6cXQ4UmtHbU5Lblc2ZlphaUdnSGpTNHhhSGM0dG4va3pzd3JMZk94WGh5aTBkd2NLbUhp +R0R0Z1VYZ1JFZEhEaENZOU5wQjRyUCs2WVVMeDM3VlE1L0ZwS3YraHdFVytoRGZXaHo2RG9NY1JN +N2sxdU0wQitHSm40NThOc1FVR3k1Y1dHY1Vyempxb0NHNWFRZjI2UG40cXpId0JhdDY5QitYNHVJ +RlJ6RFk0ODJlV1BLSkJUdWMrUFFjZm5xb2h1MjN0M1hVMkxEQmxmK0kzVjc3cXJ2NWMvNjBmLzZj +UCtmUCtYUCtuRDh6NXMrc216OC9yajlmR280WWpVT0VKSXAwMlVSTXlCSkVBMndpR29FbHNRNUJu +bWtJWVdnc1l2eEtnT1lTcjBLOC9iRGVkM3VJVUdGSXJsbUl4UnI3N0ZjYzhoSG40UEFtaGVFSld2 +TmhRejZjbnFrbm1HL0FCd2ZpM2VXVCs2c0JMNmQ4aEtrWkdxckdqUGMzTldKcWlFYUpEUU91dkdu +Q0NlTEZNSy8yOEhMMXVPUVRVM085Q1dLSWNIQnJ4TVdKZjZKYUkzR0hlOWh5NE53ZXovQUtaNTVk +SHZzSWhUaGNQZkR3bzMrYWl6ODk5Ynd4OEdRZXh6aUFIMloxdS9EamtrZi9DQVZ2dmhPT3E1L05x +UUhQWWp1UXJZVkZYam5MT1R6Nm9JOE9NOC9VQUljNTNNWWdkKzhCeHh3MXFCejZJQ2Y4MVFKdUhX +YTRvVk1ZeEttR3hISUl5V2VJRHpzczlxdGRMbnBtS2xqRXFtazl3M1A3b2gvcTgxemY5VmNjR3BX +cjY4UmdVUHpvcTV3d3ExKy9yT2tCRGpQdFd2dFZPKytla3N2ZStYUCtuRC9uei9sei9wdy81OC81 +Yy83OFRQNThLVUJSd0pnVXdLVGtmdmMzUCtGTGVKc1VXVUVRTW5FeW1yM1dDV2dld2ZhNk5CdlFp +UE1HVXJ3aHNCNHB2aHZJdFJlNVlzdmhqN0lWcDFueXd5WU9VZ2lCT0dDTDBZNUFUWU9uQmpXblBy +amtjMmtNZ296dUU4YzZRclgyTWZEenI0UVJNMktKUUNOdzhKTXpOSFBCZ3dQa3dpVS83SVRJaU9y +Uk9ERnhKTGYxdUZXN0dseUU1T0R4WEsxeTJBZWJkY1JwempOdmxqekhNZnh5T0RnSXplR2hsb2pq +Y3FXbXd3UzdvUTU4cTBOZWN6SG8reHJ4OFV3NDN2QVFvcnp3dC85cU5pK1g3N0RvSFl4eU16NStD +RS9jOU9LNDBFZjZNb2RUYSswVmg5akYxWFAxTW9uZTlZMGlESFNrUnZ4WkE2c2V3YUt2N3VZOGww +TnV2UG9jVHE5MnVmRG1NMDNpU1k3V3dYeHcrSzczbnRHancwWk9sem5yOUJnbk5JNW4vWVViVHRo +OWhrV3VIazdxc04rOHQwTXdleGJkWHk2OXdZVTFzTUkzZjg2ZlhTUCsvRGwvenAvejUvdzVmNXFi +UCtmUGorelAvQjNRdm5Xd0NRazJBS2hwaWhmUVlvMFNCR2lnck85Mzg0cGpFRC85SW9DWUVDSitp +RHJoaTg5NDl0c3Jsd2JiQTRNNDFzZ3RodUlVZ2h6RjIvZWZmdkFQLzV2REEySDJFSjQ4aXBXckJ3 +aUNIVUxNbzNFRVk0aW5mdmdKU1d6Q0V0TlFqenk0c0k0UkdvTVFzL1p5K080TmtIbTVQWU83M0xp +ckRUYlAxUWdYWG5ERTdEalJUTGh4WVY0dWUrR1h5ek41NFZTekN3Zml0bTl3cW9VQnJjZWJaNGFt +ZTFhanU4UGdHY000YU94bE5yMTFsOTh6ZWRvYklxSUZCbFlYSGhsUXJVd2huams5ZkhweFZuSW9I +dS90SzA3aEVaOXhyY2VmWEhndmgvSzU0S0VsM0RDR2ZlWjlqemJ1enJBNFVJZWFIZVJ5NlNmK1l0 +QTcxTVNSU3h5NTFTSXZzMWJqTUtnWlIxOE4rbzRONzNLYnB3LzlkN2lJQndjenlxVjJkM0hoeHdz +T3pmZHFQanlLQjV0NlBiZFdIZlBuL0RsL3pwL3o1L3c1Zjg2ZjgrZjhxZWJQNE0rWFJubWJvRUdN +SXFDRkZua1dndThuYkQvSmRxTTE1bXBvM3dFbk1HQVZKSzVmSmZDVE1wSVVTbndJNmhzZWdCVXVq +aUtzQVZac2NlREttNFVUQkxFcVhpSDlpOXB5YTdvY0dpQW13Ukd3WElaNHNHc2FrOEtDR0FNWjlo +QzR4akk0b1dSY1hMVnFnR2Z3aUMwR2ZFeVYvSmNuY1ErUHBsc1h6TmQ0dWZGSExPWmh0eDVYWXFn +SlI3aXlGMjlHUkhkaXNOOGF2QkowOGFpbFFoT3poMDlGNUxPM01EN2orT0hqK2QxOG4zRlFZYm1y +WFEveEVKN1BjQTdGaXNvRmIwV3VIemlGU1V4R0pscHo2cEFEVnJFUzcrckJDUjNod2RzMGIwSE1F +YUY0TmFnWWVOQWpKdkJjZjMydmdkVEpETGpFczFqMm1NTTNyT0g5NnNPUHZjMWxyYnZEUkQyNFZU +L05pMi9PZm5saHdiVTFPTkFQYStSeUY1c201TEl1MlBYbEx2SEZFUmVQTU5BM2pPckZyNE9EaHRS +UzdjdHJqY3YzK1hQK25EL256L2x6L3B3LzU4LzVjLzZFOWJQNDg2VUl3UzIwRWVnMnhRSkovT1R0 +c3pVS2xWUUFpWUh5ekRxZmdSV0hRQWhQY1VSUVkyaW10eWtwL0owc2J3TUFKalRORmNObit6VEE1 +UmxSS055dk9IalRFSU5jMHhVbERpTjQvaGpoR1FSRUZPNHdNbUhFY2dNVzlhdEhrL3dUMkhJWXlG +TWowY2lEUUc5eGlBUEcxdGEzVU9ZMUN3YkNJVXF4NExKSHpjRjF4c2NUTTNpcjhNUHYvWEtlaTht +NDh0cmJCdHR2dlRueEhDaHFjWmdTaWppNDBSK2NxZ04zc0JCMmVUTThGOU9oSUxlOTRza05neDZw +bGZnWUR1OXFrMTljZkluRndPN3k2eUc4OE9CUno4VENYYlZpSDg3dE0rOXdnN1Vha0ErWGV1ZTc5 +VFRrY2dqaysySEJ0UmpxRnhlWGRHT041N0FUdTFqNDZOc29jK3J4M0ZwY21oZlhaNzNUWnpYSkx3 +NU55YU1XMy9HUTJxOVdjUndlNXZSVnozM0dVM1Brc0x4TGZHdGh4WGNQRm4xV1QzblNiL3Rod3JX +Y3J2bHovcHcvNTgvNWMvNmNQK2ZQK1hQK0ZPdXorUE9sZ0pwTEVacnF1K0FXcExsbkFrRUFKaGp6 +THVSWVQrUytJMDJ6QWRMNC9pNjhBcENBWkUzMUt3YU1vaGlBQVdWMnBGa1BTMG5UV01iMlZna0pN +UGhqK3ByT1BCTEVJc1lLTHVOdzJ5T1hPVGg4OXR4QXBDYWFFME5jZCtUSkNSdCtOSTBodmRud0RM +WWF5dHNBeE1Oc2pna0lqcmcwMVhlRXkyMmRSdUdRT01VZ1ZuWEE5VFR2TGZ2eDBFYUpZODUzeG5P +STRoSisrUEN2VG5QNmhrZDd4SkVEUnNObmZjS1I5VERZMzBPTlNHb1llL1FrdVErdi9sZlE1dURT +ZTVkMThLalI3L3FyR1VaNHhhU1Q5UDc5alZ6NmNjK3QxemR4NlV3ZDRxb0wvemcwejJCMFk1M2V3 +Q0kyM1RnWXJLOXh5aG5NY3FqUmZzOXhhcTNMYzdXWTAwLzl0VVk5cmMxK3o5U2lKaGg2S0tsWlAv +MkhpSDV3SzZjNWUzbzRNYUE2RERIRXhnWGRpcUZmdEFPUGZUalNQK3RjOCtmOE9YL09uL1BuL0Rs +L3pwL3o1L3dweG1meDV5dWl2MFdLcTBFUnA1RVd0Z0dDS01oUHpvTFpvNUcrSTViSWZBWUtrUW9t +UG5ITUtRSXdlenozcGtCTUY5R2FzeTdrbmhpWkc0NDJ3VC9uN0RzU0dJVkJIMEYvQ1lISXJQaTgv +ZWhRTkx4cTBZaVk1RXpnZ3RjenRiaGdKajQ1bVJnblJLLzU3dGJDVjRHWER5TFhDSHZsc1ZjOVBS +VHN3MUZ4NFFoV0RYUHdXS2Nlc1kwYTFMeDRtdTh1Sjk2SWdPamxkNGlweVJwNTRJQ0pRT0JuUGlJ +MFlQa1dqenZzOGx0TGZNU3IzMnFHUTg1cXdacXNPeEhoSEY3R2NpQmJpeGQ5MGxNYXFQRGt0d1kv +ZW9jSDljRmd2N1Z3cTRNR2ZOWURKcFhEdkNFT1h1SHBtancvckhRUm5iMmJTbDZmNlpkQnFpM2M0 +QzJHdXpyVWFpMysxYVErSEtrWEwzRFdySExDWUkyWTZzQWZQTENyaFlma3Q5OWVIT0RHd1NLbkdQ +VHN3b2U0OE9EWldzL29XajN0K2Z3NWYxbzdmODZmOXN5Zjg2ZjY1cy81Yy82Y1B6K3lQL04vd3hL +RG5VQUZ0MWd4a3JxSXdVK3pOdmxzSGJFcUZNaUtYSENpY0ZjMDRYa1RwRWpnM1JHS1RFWm9ZZmFM +NzVKWERPRCs3UGQvTmFUWm83ZzIzaVUvTVZobkRtYWtJc0JQN0Q0enJxRW0zNUVGQjhGbTdreEU1 +SWhCa3JvUVRxRHFNZzhiZnBCcG44OTQ4Rms5QksxNVJBZVhlUEF6ZjBVaHR4ejJQTG0vaXlDOEpj +RzdOeXJ1M2s1WnJ4NE4xUnpObE05Ykd1TENNNFA2TEQ1QnlHZmVYZnh5YWcwaHlVWG9ocHoyNHc1 +bVBESzJQYzJuUHd5aFp2ekNMejcrNUNOaWRjS0NLODlqbk1PRC94Ly83dDlOTEhod0pMOVk1cTEz +T01PQmI3bnhWNFBDTDFkejlEOE1ldWg3VFF1WDV6VFRneFpQK290RGx6aSt3OGN3OE1qaE94MHpn +cmp0RTAxNkRwUFlQV3pWQko5NnhWUnJUSDhIQmIwazU5VlNiZFJnOGxqdnN6cmd0eDRXT2RYaFdl +UGp5bHF4Mnp0MXpwL3o1L3c1Zjg2ZjgrZjhPWC9Pbi9PblN4emY0ZnZJL3N5djRCSUlJZ0cyT01L +Nlp6WWgxVS8zRnJza0FFWVF4QU5rcjJJWUNqRGk5dnZrZmhkYlFvbVpBRER4R1JjaEd1RzdBc3lK +TFE1Qis0dllpamZNKy8xMXNaSGhzL2dJWnpia3c2UVdBcFFMQmdNUmlDSkM4NDNKQ0xEQjNxWVNq +MmZ5aWVzTkFsSjl0NjVHRkIreGpLUitRb2ZMWnhnSnlSNzFhS3c2M1hOd0hDNjgyb012YjhMc2Ni +QllUM0Q0dHFkR3EyanRnMEc5ZU5NUGUyRnpTSmszWnkxODd1Wng0TUlSYzhyYmc4dzZmQk9tdlFU +SXBNU2pGOHhrSGo5cWQrK0JTQzhWWURSeW54MDQ0ZUlPTC92VldVR0tyVC93eWlXR0dzVlRvMXoy +Nkt2OCtZL0Z4YkpQTDRManZ0dkxUTzF4amFjdk1KakRqZlZ3TUw1OStMRUdML0tJQzd1OTlFUWo1 +dXhwamRiNVREY3dPcVQwbWZiTjJ5dGYxOFpZaDZHOGVzYk11TUkvSEw3TEs0WjE4dGtmM2k0MlR1 +Q29adWZQK1hQK25EL256L2x6L3B3LzU4LzU4N1A0ODBXQWdpZ0NFSVpVTUxIWVRDQitFZ2VzRFNK +MHo1Rm92ZURtTkI0QkJPb3pFNHBqclpnRnBVbnlBVVpvNHRzakh4S0poWERsOGVzTG12c2YvdUR2 +UHdWZWZpSkFxQ2FZVjRnOWFpRmdjVHczN0VVaXNzdzVlQXc1WVNZS21NVjJLQ0FHRmdaV3M1anF0 +TCtZNVNVeTlSQ081K3IwR2NuMkV6a2hsVU8xd0doWXd5amVvcWtMLzk4YUZFZHk2MGViN1ROdXZF +a2lGTHdWaTVyMFFSdzF0WC9FZ2h2NEk2aGJWNUdMNWU2N1hybkxSVHpXRWhTdTdSRkRUTnlJNjY3 +bk1NRFpQYmp5ejFURFNtanFMay80dEU2ZGNzTnBUaDRDVnhNVHlHT2ZQWExKelVnZDhqQjl1VElj +cXJqVjN4cFVIUGZpRXRNYWZKbFR1OXo2NUxuYzRmdm1yUzJ2ZU5GSDgzUmlqb0h3VFRNeDZQSHNB +TzdCNGJMV2ZuVTV2T0FRUzQ5OGRyalRwdSs0d29WNTNLckhNempuei9sei9wdy94WncvNTgvNWMv +NmNQK2RQY1Q2RFAxK0tJakFpc0ZGd2dCRUFKTkRFYkpNTkFBRUdvUFZ0Z2psaWIwUEVKQ1pDMUV6 +N2FsQ0NrRU91eHZjV0FRbUtVcWkzTlpycE9ZSDVsUVZ2bGhUTW5IQXJ6bDRZaUFrcERnVngrdmFB +aU9XQzYrdmNEVmprOFJ5NTFqQVJ3UldQbXF5WDM5MGJMN2xxZUxYZ2lBQUl6anA3Q0pGNGNWaVJP +WlRrTVR3ak1wZGFpRVd0NmpFMFRoeFlyQ1ZvZWRWdWp6ditOUkdING1xczUvWTRLTlRtd2d1YzZs +VWpqbUNWU3c2eDlWNDljTUJPUE9KNnBtNTNJaFMzQjRlY3VQVWRYcGNEUjg5aFMrNkxwM2E1SFpo +dzVKQThQWmlEaGZrSXZyMkFpOWErZldOb2pmajZnbGVhVXF0bmhua3h4WVliTC9SbmpkanFkWG1t +RGp6b0c4NzB6RjcxaUljTEpsZVhQV3JIRTJ5NFVyc2M5dnNYd3NTQ2tTZXNoOTI4OWVLcUgyNTU5 +RUlzZU5YaEdTM2gwVjF1SE1QY3cyTCtuRC9uei9sei9wdy80WmcvNTA5eDVzLzUwNXFQN3MvOENT +amdBR2xPZ3l0T1FtUjc0eUZnUVFQVkJHMnlvb2hOUTVGTHpFd0dKRUlKRUZEZ05GRU9CSW1QR0Ux +a1RvVnFucmMxakp3OTkxTzhQWDcvMmp5emlxZXBzQ2tLa1VoR1d2ZDVneUNIdURBN1FKQmtlR01o +ajlwN1lKUUhlODBqcW9jRW9yMjFraE5uMXNPZCtGY2p2dEs0NDhGK0lyZkdlcGMxOWprNENFLzk4 +UDdvZDM0bE5jRkdQRERyZ3diaFd0TmNZaENDejNDYmMza21EbUhJSVQrQnF0Tzg1KzJWdlRDcHhX +VmV6VDF3R01iYkRMV3JTMXc0OVJRdmhPU2c4Tmtjdk5hcFYzdzljaENadDA2ZDhxbm5qcDNnY2tq +aVczNTF3T2E1UFhMQm9kL2lxWm0rR0ZzUDlKbG8xV2t1eHIvaHJnWTRjUXlubnNzdnZyVnFkd0RU +bTdWdzY1ZTlqSzNQM2hqcEk4N3dpRU00MDcrTEo3ZWE4MWJyN3ZhSUJXUE1mL0hNK1Y2TjJJc2Zt +TVNTdHdlbXRmVkJEeXo0NU5kL2UrZlArWFArbkQvbnovbHovcHcvNTgvNTh6UDU4NFZReVQxQWpB +WEFLUWhaa21vWXdVa01HRkRFcTNqN0VDR1lOd1FTSXRQdnNXdVlXSnBKaE40U0lKbDVnV25CYmJD +aVhISnFJbHdNQ0pQbkRPb1ErSlBmK3NXdlFpRDRObGZqTkVFK21PVVRDMUhJVmc5QkcvS0pyNmtP +R0llSVgzMFFpL0hrUlp3bXFOMCthNUVuanVkcVViL2FVc3ZsY2pHWi9XclVjSTJCVHhQRnRnWkdo +NGw2NUhhWVplL1ZvM2tWdUQ3WWovdUk1aG9KQzc1ZDFvaFZNZGtuVHVmaGxaTUo5RTA4Rnp6MnFz +Tm5NZTJYMzFBWFhqeXpYcjlqa01QUW51SktMclV5TnU3MTFucHJ2c1k4Y3hyMHBhLzRNb2N6dUJ6 +YTh1bVRIdUxiYzNFY0lqN0xyV2R5aW9rSDZ3ellISEI0dEo4QnFsSGEwUy83OUkvZTVLN20xZHNE +VXN3ZUhyaHJyUzY5Z2xjOCsvUkRyZktKaVE5Y3FKRXZ4Qk1YL2g3Mjh0cG5IbCtHbkhwR0p6QXpz +bHA3U015ZjgrZjhPWC9Pbi9Qbi9EbC96cC96NTJmeTU4dnZOR3VTUUlSaHMwMFZsOENLUUk3Z1JL +dWhOdnNKVjBERlNNNUV5RUFjd2JuRUFsNjgvcVNzTU0wZ0VCZXdUSzFZYThVenIrR0tnOGx6Qm9M +WEc2S3ZnajRUeW0wZUxudGdJQXB4eFNjMHo4VVgyNGhCNzdzNXo1RDVwNy85UzJrQVFiczBSTVBV +aVR4cjFlOHVGeUhBNE5jR0NBQ3g1cjBSY2VISVBnM3Z3VVpzeUJlWE9YRUdoM284SjNiOGFScnUy +amg1eFBPc2pmWk1QdWJFcnp6V21oY0xKbUlTVXg5eDJuWHlXdWZRd0pGNDd0Yml0SDIyWG53YzZB +TU10SUxqSGtyMFlaOVkzcll3bGowOW5QQmswSmU2clpOUExESDF2Z1psSW9adGYyTFFkM082dHpi +NW84MGIxc3NsZDdteVYvM1c0RXd1T3RjSGRjS2hGMnFGeHg0WFk0bEZFejBrNWZUY1ozdHc0M05y +eFlQWW5zVVRoNVBlN0RIUHRMQ1lweTgrOEYzZGVGSWovY0lNQTA3RWdHSCtuRDl4TkgvT24vUG4v +RGwvenAvejUveHA3MmZ3NTh0UHlrMmtVTVZMeUVnMnVvaGRZa0dCOWRPeVpOWWpRQ0RmRlNwUkRj +cFFtdXhOa1R2RG1Lc3d4Wk9MR2NSQ3JqZ0V6ekQyeVEwTFV6Q25aeTYvTC85Z2V3dVI0c2x2alRj +RlJJTVl6NGpNR3lVMXFzOWdJREhORVlaZlAvakJiLzU4NmlRa21KQ2xrZUlRbExXZXFVVkRyQ1Vz +V05VQ3B6dEJJdGs2T2VIVUdEeDVyc0dhb1U1NWNTV2VQQTRrK2ZGaUgwNWN2c01ndm5yTkVWMkU5 +VzUrV05WaTNucTlJbXdpdEFaT2VIeldKOElscE1TL1dQREpiNzI2bUlYSThPUzdHcG9YRno2TFph +akxlcjNCdVR4eXVPT1NnZFZGTzNLS1FaeHFwZ3Q0OVJCR1BPcWZXc1RIcDM2SmdUZDcxVWJFOUdG +NDV0Y2Y1UGFaanNSVWp6cnhKaThzNXRSRWMrSjZMcDVjOXNySHBQWTVZTXp4aWUvbWFBaFg5b25U +dzBGZnhjY1RuSGkxUjQzbWZPY1hNZmpBV3MvRWtrTnVtckplRGpqbnova3o4Uy9XL0RsL3pwL3o1 +L3c1Zjg2Zjg2ZVlIOTJmTDhHUjRvNWt3VFVEK1lwTjA0NFlaQkNJSW9oSzhRb1VVQk0xQjJBWFVW +ampWd29BOVV5enhQZlRzZlhFSzdZbWlXL08ycng1T0tDYW8yR0tVQlNDbVkvUjh2dnpGeE41aGps +WTFVSG81Z3c0Q0FZSm5vc25saUd1SEFpR2dWbjg2a01KUWlLaHcycVB6OWJEVEhSdzRJcVk0RGFQ +STgvODVDOStEYUVlRFNCcThSc0Q1dzRyelhXSmdRdTQ3WEhCNXRJc1BkR0R6QjMvN21vWDEzek1k +Si8xejVzTWEvVklUSitKVEY1aVNkMG5IcUsyeDdNWTQ4enAwTk5uNjlXZ0orTG9FWXp3NktmY01C +anFjakNxUTd5dXc3MTYrblpRbjZ3emp4TXhZVlVQVERoK2V2UWxtTlVsTjNOYnErZHl5cTlPZ3ds +eDQ5ZGlZRFpQaitMckE1NWhOUytPV24zWFp6RnBCQy9SNE8wdngvYkRhUzBlUEZPSHZKNVhxOVdY +ZmZMaFRvM3FoN2VHdzdjY0RnV2MwSW45emFjR3RhYi83LzJkUCtmUCtYUCtuRC9uei9sei9wdy81 +MC96OVBnWi9Kay9BZlc3N2NnQkhsa1dTZ3FBWkJwc28wQUVpQnhGSXhKWUFrVUlrUk9KQkJxdmNJ +VEhDTmMwei8za3o1ejJBQytQb29HVkR5a0swMmpZckVlRy9Vand6RnNHbU9FMEZDS2UrUXJLVUlz +NHlEY1BrN1dHdHhMcUVGY2N3dEZBT0Z5d3dnYzdUTDNFSWpSM29tUksrVFFSK1hqU0pLSVJ1d2RB +QkhqZjdjRWJuaHdZOE9JVmQrSXpSM3RBbkhpV3kzN3I1TUc1eitMV0NPSG8zWFI2NXMyUi9XckJy +MzA0RUM5N2I1MW5oS1FIclFYZkJoSEQ1NHJJcmk1MXk1TitYaTc3clRQd0piNTREaUNDeEl0ODNv +YmtrTC85NWgxdWF0VUwrL0RsTTM3c2djTjZhL0NBVndjSG9WdERnK25EOGRSUnplQzE2MkJWdTgv +aSt5ZkJlMERpU0Y3WTRNR1hmVFFrcDczcXc2RWF4WVdIYnF6VEQvWGlFVC9tOGNwRDR1Tk5UblA5 +ajFzUEh6bjFVQng1Y1NWWDE4YUhkN2QyL3B3LzU4LzVFMGZ6NS93NWY4NmY0aGp6NS96NTBmMzVV +aWloSXNhR1RnZ3VTVTNnT1JJVVpvL1BDSGRaNjNrRnJaa0U1MDFOMWw0UlNOSU14RE9vUDA3L3la +bjZxMEh2cDJaN0M5VGJJSHNNSk5salBYTWlFZG5pRXJUYzRzbEg4R0lhbXVGTmxiMHVCd1ppRFFi +NjFnQ2FLeWFNM2c2cG9lUnJqTTlkajFUWUVGdXhhSURudnFzSHlkWllyOEdlaThVd09QTnJDZDRP +T1RSNlFLaWpCdFZrSE9NRWRqWEpJWWJQNXF3Ulc4M3VOWVRQQkFhWE9va2JkbjJXaXppc0U0dGhm +RTd2RHhmc01JZ0JreHJFcWpaZ2hBY2ZGUmRUNjZ0OGhPcVpxeWJHaGY3aVFMNkk5L3BBWjdpMmw0 +YlVXbzdnTGxhQ05zU1JnMkd0aFZkdDFoWWJMdlJMYnZpOGFjS0xPaHpxOHVIQlFRK0RXUDBQanZY +Rjd6dE05b2tyaHBwaHE4blZ3cVRXZEoxNXZhRlYrSHY0NFNHSHoyRjJNTkNadXFLYjI2Zm44dEdk +T2ZkcWEvNmNQK2ZQK1hQK25EL256L2x6L3B3L1A0cy9YeDc2QUJTd0V0dWtLWkw0YmdnbW9hSUlY +VEVJUTB4RnJIR01BaENpZkJmZnZPOUErQW5iY3laRGhuMzlxZHNhRFVBK3N4Q0lvV0JDUnVxUC90 +M2ZDVEV1SWpMZ1lqQVhZeVBOMEF3Ly9jT2dEdGlzTlRSVkxRaUVnV2cwRUk3VWRWalQ2Q1BlWGpY +ME0xeGlpUXVET1Eyd1hueXhZOUFUaWU5NGxVdDlHdWdaZzhycEw2emp3eHplaVJWL3hBQ2JPZm1J +aUtBWkRRYTV4TlJjOWNxUFQrdjFVay9sajRBdnBqMEVJcmM0YXNFelR1MVJOMUV3S1B6NkQ1UFk5 +cGduSEh1SjF6Tmk4NTNnMUlVVGZiSlhmUG5ncUhEZDFkMkRtL25VS3A5NlBWY2ZQY0FzanZqMk5n +Zk8xZmZ0Z2NiODZsY3ZudG9MdWpIVTRMbi9hRGhNNUdwZmFFUXN1V2lhNW1GMjE0OGVkdmFLYWZD +QzJQanBZYWxXOHpoU2crL3VjdEdDT3F6bEYvWEo3UTZyUExnUXAvOXh3TGM2NTgvNTA1NzVjLzZj +UCtmUCtYUCtuRC9uVC94OEJuKytnQ0l3ZHlTRTNDc1VnUnJudTZJMG80MG5aTWtCRmJoQzFIQnZj +SUJRRU1GSXJuRWFBNlJtYUo1bjRoTVo0bDNFUXJUaWljTndCR2FOZUdMNFBYYU50RmR1QTNhNUhC +emVDTUd1Y2NnU1QxTVJRVHoyWlZ4YzY4eXJFWkVhaFV6RTFTUTlmR0NHQXcrYXArSDJFVDY4bnB1 +M3B3WkZPRjd3WnIyL1dPNDVZYW1QMEhHSk56aHdCVGYrbXR1Y2ZITGdyNzBTc3dlUHhwdkRnYlZ5 +cWxmT1o3dzluQjl2YW5ISlo1N0kxUDJ6UmlCTThhelRhOEtCdlZ6SjRWbjNNWlJZMW5zR3J6eUVT +RE0xa1hyRmxWdi9ISVQ0d2gxOE9IQkk2QUdlMU9rNzBjb0hsOXo2aUFjRFpseDRqZzkzT09Vem1F +VnNtaE1McCtVVGRwL3RxMGJjYVFQKzZwNDI4eCtFMHcxc09LZEplL1hFSFFZYzRjdGx2N3RuWWxp +WG5NZUZ1QWFkNGFIemFvbEp6NkIwTUgvT24vUG4vRGwvenA4d3paL3o1L3c1ZjhJcG4vR1IvZmtT +Z0pBQUF0WmlCV2lhZ2hUTG9OWUJqbEJ6aEdDOXdCb0d2QmplZUdnS1FqVEN2S2I1NDNpeE5RTzV6 +S2RveEFDbGNEazBUcE5DNnVYWGVIbmw2SnNoSnZSUFZlZncrTzVMN29nVlUzNHhEY1RBSUo0NllP +cmJJelV4aURyYVZITU9IVTFFSUY1cWlON1ZZSjByYXc2WHhsUWd2ak1Fa3VFV1cxMXl3VWhvT1BU +bVNvM2VhS2tURHV0ekdONjhPUFl4bGpuNWMyZ2NKamcwMmlXdlp3UWlKbTd0RmN0ZVdBaEtUM3V3 +NmhOY2FtMnZ4YlNPZUR4bktyMXc0UmVYRGp4MXdLSlcvYmZPZWowTUw5ZlQ0RDlNY29sTEQyTERD +cU9jYXFjTmZOQ0V6M0l4cGJYaXFjRjNuTWhWVHF0WHBqYk1NUXlkbWNNVEhwNzZ2K1FaYk9hckNS +ajBUNzNXNGtCTnRBV3plWFhpdUQxUGYyN1FwSHpXTWlFdXJWZGZEcHU3MjZ0R2V6d1RCeCs0VW9l +N3V0U1AzeDQ4dEtkMk5hamRudFk3Zjg2ZjgrZjhPWC9Pbi9Qbi9EbC96cDhmM1o4dkNRallGeHNG +SmdqRklWc3lnV3BFd1JBSkVDSVZneXpCRmVyTkI4SjhSb1Q5NGpFT0VNQ0thNTAzQ29TbFlMRmdr +ZGRkY2ZhbHNkY0l3dkFUT1pFVFdvazBGQ01XVE9idE45VER0RERJclZIaUdCcFY4MWp2Z3N0UDV2 +QWoxSHJQeFlITGQ0SlVtNXJseFoyMStKTWpmTjFoZzNoTlY0ZG1lZzZqZXZ6azcxODZFOGNkbjhT +RFk0S0NBUzZYdk9ZY0x2TGd1Znhvdm41WUJ6dE9jR2JPYzcyc1FmR01DNXcxTHVISmpVK20waHZD +WVVUMXdDK3VuRENwM1Q2eHhQY01KdnpnRENkNlhvTzY1R0JRd3NNRGc0cXBKcHlJUVpCeWlPc09n +LzdvbGQ3RDM2djFXcXQvbnNsZGs2aGRUMkN4WDE1cmZXY3lhOVdsRHZYb2t6ZUxZb3J0VjJmVURo +LytjQzJlOWJBd3RQN3FGVTNySVE3VnBGN3g3WWREL3ZwS3J6ekRoWHEvalJOOVhHd1l6UFZ3YXEv +bnovbHovcHcvNTgvNWMvNmNQKzAzNXMvNTg2UDc4K3VmZ0NKVnNRQzFBWXFXd1BNMkIrRVNWYXpX +eGVEWGVFQ0pBTUhJWUNnZ1hVUmdMY0VneTFzUmdsVVlFZ0Z1Ym52TnkwT3djaUlMSHFSb01zSDZD +ZCtBblRCY0RGcmppbW1kbUNVWlJnTnBiYUpuUHNQRFBFZ1dHeDRpRWtmenhjR0hIR0xGOUhkb3Vj +dnBqbGhDaHltNWI1MWE3SWRQclhqd2R3REU4NFpJYnJYS2lXc0drVHQ4M0g3N2NLS0ptbGt4TTRj +ZXdLUWYzc0w5NU4yZ1BWQmhrVk4rdlBWUWNrQmFaNi9QY3NzcnBzOXk2Q1doMmlPUCt1U0hWMC9r +Y0xjUForYTh3WXRXRGgvOGpJWlRkL2xvd2h4aHk0RW5senIxV0F4cmlWUmY5QTMvK3VVL0ZCV3Vl +c3c1M0FsZExuM0JOUzZzZzlQdzNmcjAvbUtrbDVkUDdRNVp2L3BDQi9IQ2NhWituSGdHa3pwaGww +TStuTnFyTCtyUVp6cW1BYmpGOFZ5ZDFSUHVyRkdyUThFNkExYzBobmYzeG9jZkYvUG4vRGwvenAv +ejUvd3AzL3c1Zjg2ZjgrZG44ZWRMWXNBRlFJemlBWElCbEUyM3dVL3lDaVV1alFNQ1NBMFN6S1da +Ukc0dmdONVlXRmR4V0M5V1RhRlpoTmUzSW1JUUlaSXFJT1REUkhqaS8vQjd2NXpuOW90bkhxRit5 +aWRRUC8yTFkyaXNJdUdHd3g2eGpCb1VOaGpjTlJpQlRDSzIrb2tBeWVycFo0MkIwUm94d3NVMUJ4 +Y3VoNUEzQWZiZ1QvemlkM2poMEs5WUVMT0RDNjRhVGpQMXdacHlwN0Z5aXVVNUhKNHhCcTQ5czE4 +ODl4eXNoNGU0OENNblh2eU9QZ3oyKzI0ZnJ1d2pWSmpoZDllSENQbmkwVVpONjFLN2VlS1NIMlpj +NjR0WVluU2Z2dnJ1a0RDdnIvandXU3djaWlPbXkxcjh3NDB6TmVPVHNXZ0hadlV4bURxTTlqbnhU +MmYyNkhucXY5RkRDWWVNQVh2VzNkMTNXT1hXUzJ0Z3dLOFk4dUNJWnFNMzQ3RElUUy8wcm4veTR3 +TFg5b21EaTVwV0RPdHdwVzkwWnFoZFRYcHZINE5hN3puYzFmcjhPWC9Pbi9Qbi9EbC96cC96cHpG +L3pwL0dSL2JuQ3drMktvWXdKSmFNRUFVM2J4T2lGWUJVZjR4THZBcUpPSzRJVjM5UzloTXc4SXlN +QkVMVmJPc0pRVXh4RUNtbW9nQkhRZ1ZmVVdzVVRJM2hwM21GeUNXR0FSdWlQTk9rTms0OUZUZGhp +bDFpS2lqN1lOUWsrY1JTTDhMZ0VRc1BCTzBaL0dyMXpGcTg1ZnMxVDMxUEU1Ky9hQTB2a3NWbER1 +dUlURDZIbHdhcTUxdGgyT3Z3MEZDNXhaY2JmckhzamVtdkxrYXlqcmh3WVExdThGZEJxRmM4M01n +aFJ1ZnN3WUhMR3NiUU81LzExWE1jd0tWMisreUJsL25reHdQQkdYcmtJaTYxNG9DeGNhSy9lS0Fk +WE9OR0xmYkRDeGZkcWRsYVF4NDRDRlpQUEcrdDhyVC96Tyt6L1RpdHNYQkVBK0s0OEFNUFR1R1Fz +OXlJQ3d0Y2pJWnorTVFVaTk3MUhoYWE5TjEvRUhyZzRGT2RzSWdMbjdoNmhWTTFxcjlhZjNUNEZt +N0ZVaWVNZWtDYnRDV09QZlBuL0RsL3pwL1d6NS96NS93NWZ4cno1L3o1MGYzNTBqUk44TUNrSkVB +S0NERGdraW9VQ01HSStxdDRiNzNHQWFJWWMrSjQreU9PT1dSb05qQ2FCN2k5UUlybkoyNmdOSzdG +SWhlaEJzS1JxZ0J2Vkt6VENERU1RbEVIc1hzT2k0RU1jWHgzV01BQmo2SGhtaW9IYkxES0xSWThz +Tm1MSk9LRUtZSzhlc1dTRzJld2FRYXVldG12a2Y2NHZDS0RDNmVhSXE4M1dtSzV5Mkd0UE42V3lX +TVBySGpHcWVaYUo2L0xjNDIySjZhOXl6bzg0ZFFhOWVRdHpRbEVuZXFCd1JxWXJmRmNMQ2FEMlZw +QzFFdHo2b2FGYU9HMVAvWGVaL1dyeFJ3K3JWZUgvZmkwVDF4OTE5L01YKy9sVjQvNnJCVVRaam5G +MHpjSEdlNWc5TjNWdzFITjVzSzE4VzRZLzFHZ0Q5andJVjg1VXFNK2krRXpmY3BsWGt4OTlKM2h4 +QzB2TWVqcGpZYVlHUTUrZ0ZrdTY5VkNmOHh2aUdPL2ZQYnBKeC81M3NNc1JyL0JEejJvMUNDMkhI +TGhjUDZjUDhXYVArZlArWFArbkQvbnovbHovdndzL253aFZFQ1RTRmFjUmlKQVVDUVRqWUFFQUpo +aWlNVzg3ejVYVUV6d3RWblhETW45dEsxSUJXZ21nY3NKckwxRWl5aHhpVW9oOXJnTUlrRUFYSDYz +SENISVFhRG1pT09abk81SU5Sd1U4c3FwV1BFMHZITnFGbHV0UktlK21PUmllcnNCQzRMaGxvc2c4 +ZU9QdDlXdDJlcXpud2pOcFVtM1gvelVmYzBuU09KeFY2dTczL1hYRkh6aFFtN04waUFOWlN5NTRW +YTdXQlVSenNUQW1YeTRid3oxMnV1NS9vaWwyZktZRjFPZjFDMk91RGlEVnp4cm1WUk4rSFRIbndO +TGZiakZDK3pxaHdrSERpVng5TnhoYm8yOVl1R1VmdG9mdUlwRlhIZHhjZXl6L1hwcXZSNHhOM3l3 +aVduT2VuZ01zWDNHcWJjMmVpT1BQdUVUVnJ6UWozdytNeWlleEJYVE0zV0lrY1B0MW43VitmdGRI +dkhvMnhwditlUXpyeC82MTRNLzlUSGU0V0k0ZUYzeXl5bFdjWXZkZnR1dlo3Z1ZjLzZjUDNFRzcv +dzVmODZmOCtmOE9YOGFlSkIzL3B3LzhmSVIvZm5TSUplaVVzeHRBTTRtSUFqUEppWmxac1VJWkoz +R1dZTnc0RnFZbis0Sm5paXRKVkw3QUFVUU9mYjRETFQ0bWdJRDRRRHJyc2thYjY4OTh2dWpmc0NS +YjI4YnBMbGl3bzBnUTJQaEVZL1FZWEx2Z0F1UjhvcGxQZ2E3bk9vaERqVVNtSDBhclc3WTdFRWtm +alNJNk5Wdkh6eUVXWFBXL0dLb0ZVYmZDZEJoSklmdk5iZkRRVHo1eFNnWHZwZHZjUWtWTmh6RDBE +enFsNTlBSENyaTQ0dEo3Y09IdFhDb1czeERQRDFpVkp4WEIySXdoOXg0Vm9jWWNPTEJjOFBCcXlh +OHdDR0dPb2hOWERYSWg4ZldnMC80cmNWRkRXbVBHR3JGSjB4aVdHZk8ydllaWjgzWncxTk45RWUz +ZUcydmNhSUd0ZVV3dmw1N3krTU5IcDZzVTVzNnhMRmVIamhnb0ZON1BQTkcwb1Y3Rnd5dzRvZFcx +U1VXN3ZDdE5tdktKLzNqVjA3NzVldEJoQmNZNXMvNVUzeGovcHcvNTgvNWMvNmNQNDM1Yy83ODZQ +NTgrUjlOQXdReEJLWVFQekVqRS9rTVp5Z0tRUzdCSkNJRWhRSmkzajRpUktDZjBqVlJiQUFWNTNz +UEJKOFZicjFZd0lydGpZYzNEVFdUdFFqMFZzaGZidllkcVRXb29xMlZVenlpTURTVXNYMVhsemN0 +M2dCMHlBVVhzbXNFeENESWVvMkVCVGE1NVdQSThIUHI1ZFg4aWs2ZDloTk1SUzRHYm5DQlo3VnFu +UHJNTTQzOGNvdG40QlBtR3M4KzNQcE9SSGpTYkh3U3E3WDJ3S0RHQ1BkRVJBQk1Kei96d0kxN1BC +R1FYdm1zbGg0TTdTbGVhQU9mbnNzRGk3WDJ3Q0tuWFBMVGlGcjBCUWZscU54WFYrSHE1dFdyamh4 +NmQxbXJSbnhWcFBKWW80OXE5VnpkTUpwek1VVDBjNWpGeHF1ZXVmVGFYampGYjYvcEJ3OXFNQno2 +Y091bGZQaWlHL3pJNTdLZlArU0NRKzl3NnFBeDU0TEJHbHEwdjM2Qm9WeXJpemIweDM4UTFXc3Zq +SEtMcnlaODZkSDhPWCtxWmY2Y1ArZlArVE5jelovejU0MzVjLzc4NlA3TTN3SDFrelRRZnBvdXVZ +Um9RNHVRV0lGdEVoRFdmTHNPNllCb05uQitldFlneEJVY0VXczhvb2pIQVNBMmNjTGdJa2cvNll0 +bklFakJMditzTXd6ZkdsVHpHTUI2ejRqS0VGZkR4SVFESHFSMnFFSDk5aUxTZm1JUnI0UVJKNkg3 +bzJoWTFXdWRlbUhXRktheFJtTTFHZkZpd0tFQjl1RVlCd2FPaU5uYU5oSHVHbFJ6eE1TVldsM3Rr +VG00Y0lKUCtLd2pXcGpFVGFQUE1JUlJ6dUhYUDNua2QrRHBzNE5IL1lTaEh6Vzl0WjdEand0MTY0 +dTllSlBEODNKaXZ6cmd4S0c5MXFxRHBtRFFNM3RobDVzR01uL3I1U04yT2ZBSGk3b1p6V2YxcWFV +ODJWdURFcnM1YSt5M1IwNjl4Z09POUF3bXo4V29mc3o3VEZkcWdVTnRERnpjOUE0TFkrRkhUcno0 +LzdpcWtWMTRxN21xSzNrOXh3MDk2SW0rZTJib21iM3VlSURIQVFDenZQUG4vS24rK1hQK25EL256 +L2x6L3B3LzU4L1A0cytYL3dIR1ppUVRtV0JJSkc0RVNvb0FTWkhpc2dZUVpFamdzd0lmQTd5bGVE +OTVJNUloTlVZYzhUVkNnUUFCcGlHQXQvRXVhK3hqZGxnUXAxai9UTFdmbkFtTTZReDdpUmNoMXRX +Z01DTlFIalZxZ3BvNjVMYSt3ak9uSG5nUUNBY3lrU1duR3UyQjNUcW1OS2VSUkNwdnhIb2tPeHpF +RmIrSGp6cHdvelpDeHBFNjNNdUhRV2oyUkJ5SHk1eTNIV0xBaFZPeGZMWkhIdXZWNkM5OTJ5ZS9m +cnBnZGppWmwwdlBIUXcrT3dqbElBcHJpVjFjUFliZFdqWEpJeWNNbm9tcGZzOXhCTFAxTklJM2R6 +aHdFNXgzZDNqSVNTY3h3TzAxNTNzMVlpLyt4VlNYTmZyTEhHcVVBeDc4MDRNNllaWmZmOVJpcjg5 +Ly92MWZpNkhrVmc5dGlrRzc2cVp0ODNvQUQ3eHdlQzRPcmNLalhyMkZvVHc1bkJ4NjFwc1gwN3c1 +KzhWVWoxNVZDN2l6QmxZMUI5dlZKNFo2V3J2bmFvd3VMelpzOCtmOGFlMzhPWC9Pbi9NbkxQYWFt +ei9uVHpYT24vUG5SL1BuUzJJZmlEM05QSkNhNXlkaUJDaXVCbElnc1NDSnlBQUJ6SGVBeGRFRUFw +V01ZVHhIanVmRUxMRzNBMm5lZ1hNUnJkd0tGVjhjTVpHR0VCaGdJVDUvTVJhTy8vanYvMEZpZUt2 +RmlQYkFiWDhOaWd6cmlja2xocXNES2RhcjNXZHhFUmVCSEZaNzRFV28yQm9tZGtUNkxnUWl0Y1pu +c1dCU2YrdGhSRHlKNTFLcmRlV2c0bkE0OU1CUmoxd01vUS9pUlBDSFJ5eDQ4T3J1bVhsQ3g0R0R4 +Qm8xT0JCeElDNkQ2NGYxYXRRM09Md2RrUU5lUFNZT2NkVm5qZGpFcmo2MVc2dWZZb1NQKzA2RStB +dlcyNmN1aDZHN2ZmaXhIZ2E2YVQvMVhpNm1VVi9mR3VGUGZlb1gxeHIxR0RDMEQvREJUQVBlMUhs +T04vYmI2eS8wRzhVSm0zam1jQTZUdXZGaW56ZFJmL3JidjVSZXcwYkRPZEJ1bjNrNTZnWDVhY3Rl +UEdiK2NCcnEwWGM4eWMyZzFqR3NmamhROEd3ZFBQbVB5SEdDYXoyVHd6NTU1WmsvNTgvNWMvNmNQ +K2ZQK1hQK1ZKTXhmODZmUm5IQ0p0NUg4dWNMY0lVaXhrU05Gc0lQUkVVRnZFQXh3cEdNVFBzUXJl +a0VBaENodFFBSkZOb0NDYzkzY1loRzdCYmdFZ2NoY2dEc3M3Z3dFWjZtYTZSaUdUU0h5alZEMGZB +ajNSN0VHOHdOajhLSjFqNXhPZ2dJTHZsOGRnQkVJTmMwMkh3V0MvRmlXK083ZU9xT2NHOGRBZm9j +M0ZjZkhwRXZuMGJoV0xQVmdrdjdYWVJDRE9iZ1VtZkVkdm5Fc1U4K0F0QmNCd01NYWpGdkhRN3dY +cjVpZ0p2REp4RzRXK01Oa1JqNktvYVlMbTg1N01FaEFicmJweFpYUmFiTzFnMnpHUERxY3pVZ3Z4 +eDRjL0NZVXpNOHNEc0FhY1plQjdabjVzWFdKNzAxMzdyTU03aCtWQ2RpMmc4WEhQYWFoeG1QT1Jn +T2kvMytQb1crd09tWmVuQXVGbTV4aG05NVhmcklkRERRaFJocXNSZFhkRjI5cWRVYktMamhzY2Qr +OGNTbkY3cC9ldnI4eTNDd1B3Zk5XN1RwY0Zjai92UkZESHpJQVJ1czgrZjhPWC9Pbi9Qbi9EbC96 +cC8yekovenAvMmZ3Wjh2LzFQZ2ZqcFZrSVdLOVp3WS9FUXJxRURtTkJSZzR1bGVSQUhOZkFwUW5J +WktwRmdOa1Z3UnpHMGUrZTZNUzlEV2lZOFVPR0NvV09YVWRMLzJnRVJ2R3hUb1Z4Z1VWQkpoc04v +UU5Ma1lCVWFFeGFDcDUvbi9zMEdrWmlHUFdIM09RWEs0ZmJZUHFXSzRZTElQc1RXb0pwUlFCeG55 +TlZOdURSU2JBTXpES3daeG1STVRMM0NJcGNIcUVsdCtzZTNEalRpNE1LOHZET1Z6dVlRQkR4SEs1 +WUhEQmE4M1FmcEJQTmI0N0RsY1BzTUxCODVjdU5JTGw1dzR3UzFNekFFVHZISjUzcHIwU1Z4LzdH +NnR1TENhejJGNWNmV0pXV0dwNXV4TC9LdFJ6ODM1cm40eFlGS0xtUERUbmpXZTZTZVR5aU92V1BM +cEozenc0OWZoMGNNQzcrYnNvOWtjQnNlbFBkYlFlamcrTERESTJ3UE1IdkZ4S2w5OW9BZXdlQ1lm +bm1yUWFsTWQ5T1F1bnhyMFZuNXJ4TzhCajBjOHo1L3o1L3c1Zjg2ZjgrZjhPWC9hYitCZy9wdy80 +ZitvL254SjVDb3hmdHB0MDVDWlpwOW9CTlFzOHdMMlRZREExdmtKRnlCR3JHaTZ4ajYva3h4Qlh4 +RUl0RjZEUFFOS293ZzZPYTRCQkFCRGdRTHZZazdFZU9NaGQ0WFlPdXpOb1hLRDRPQ1IyeHdCK1V2 +SkNEYmtxRWxnWlhyMWxUejUxR3E5dGZackJEeml3UThmOGVKT1RmYjI3WXdtcVZYTkdvNG5od25j +TUdtNjNHcTJGaGZxRVZkK1BmRU1sM3Fpd1JxcTZTNXgxVXBRK2xRamlFc1VjTU9sQnJYQlFrenlp +OWMzRzJxdjJXc0d6L1JUVHJIVjJkcXJEZmxna005Nmw1aWU0MTB2WVBDOSt2Qk1UUE93Nkx1REpX +K1VMbzRjTU51amZubXR3WEhlSWw1OGZManM5OHlReDFwNzlFbzlqQWU3UGNGMnVmSGpjL2szY3BE +ZXdZMUhlK1JXbjFyeGpIUDdITDYwaWx2cnZDSENCUnc0eHAvaFdibnpUTS9OcXkwSHl2dUFVWC8x +dm50NjJLUWZ4NjI4NmVuOE9YOWV6dmx6L3B3LzU4LzVjLzZjUCtmUGorN1BseUNBQXllNVpEWm9u +RTNBU2tBMG5rbm91VFhJUUpTN2ZVQURvakFrdWJMbkVya3pqRGlBMklkUVFEUkFEbkVSZ3ppaytt +Nk5JZ2pLeFZCeU1hbjVGT3BndVJxWWdLbmtNK3hWbitjd01uVi9iOXFBRHhZNTdXdERZWUhMSHFJ +bVdqa0pHOG5GcUJZTmhBOHY1dE9RSTVpZ3JHRnE0bXlERFBzSnhIZng3Y01QSVJqd2l3MmZad1Jz +dmdMejNCcFlmY2QzaFlzVHBuZW9xZ00rdmZMckY4MmhManphUjZ3RTJwek1xUjk0TVljLytWMmV5 +ZGMrcVpzKzFDQy9PVng3em5Cd2V0dUJTL3haSjdlNjlkSXorZXp6VmtWOTV1QXdKei84MXNnakRo +emlxeDIvZXU5UVU2dDFqSVpiKy9TemE2czVHb2VEdWZRRlBsajlpMS80a1ZPTzlQSndxQXRQZU10 +aHdFQzBldldMVVIzWTZ3NkxaK3FUVS82dVY1OFk5RVFmNmxZdmp1MlhNMTQ0L2RDUnZmUG4vRGwv +enAvdTgrZjhLYmU2NTgvNWMvNmNQN3UybXZ0by9uelphQkd3Tml2Q1lxQThEd0UzRUtSNFFJbE5J +UldGOVpJRFpZL0VHaUVoNFRPL3Bvb2hxZlVhNExudlNFS3VOZGJMNFRuZ01BSExYSDcvbVRIRlJ5 +cU1SZzBLRTJ3YVlqZ1FDTVpsajd6Mkk5RWdJQmhoTVNlbTNDSG9CQnVEWFRNUjdETjgxdnFzVG04 +TXJKTS9ncm1hRVErenV3WVJpZld3eVdkWTV3MkozTXlqWnBjZUdHMk9lWHR3ckZZNXZvcjQ4dUpQ +RDJwVy9PSk1ySXlyMDNQMUUyMkZwZzYxdGo2R2xsTWR4RVBFc09PeUJyWFBldkhrbDBNK0Z3MkpB +WnU0WXRtclRvSnpVTFpuOHNYZ2Q0a3JsN2oyd3llZnZxa1pCclY1cTRKUEdzR1JuTXhxVFFYTk9O +WTVlSENMTjc4R1lRL011SFRoQ3paYWx0OWU2OVVEWS8rZUJEMFVoNTZyUDl4Y2IrMFJRM3o3OUFG +dWh3SWNlS0JYdGRLSTlRNXFkV2IvNlVsc0hPZy9QbkVJbnpyNVFmL3hOMy9Pbi9Qbi9BbUxHUFBu +L0NtL2EvNmNQK2ZQK1JObVhMbyttajlmU0xBSUNJRUFFc2hpaHJOUVVuZEZBU0VZQWhDbCtBb2NJ +UGVLQ0FERmltK3Y1d28xSjQ4WTVnR09RUyt1N3hxS1NQa1Y1TE9DZmUvL3J3OGlJOFFUb2FJVTQ3 +czNEVDRieFl3OGhNb3JqcGhHRzRjOE1Ra0lSazNYREExcms0bE9YUFhCRFpNMUJJUnN1ZXlGQlNi +TkY1Tmc3SVBCSGZseXFrTWNhL1NnamJhWCtOb2dtSEhpTTNIS0s1WjUzL0ZJQlByaXM5aHkrMk4w +dVdEeUJzaXZLTWpSbkg3Vnc2SFJRMDBNZGJxN1lHSnMrWHNRd05IKzk0RFdlM0g5YnJuUDhsa3ZI +eXlHdURRRlIzaTR6K0xESXBkMXZydXIwM3A1clBYV1JSME9EM2Y2VWFkNTYybldaVTQvUEtNZmNm +WFZNemhwVXc3Y3drOUxNZHdkRUE1UldzQ3B6L0JYbXppV1MyM1cwd2I5MEpuRFhseHJjV0EvSE9v +VG4vNlQ0OWJqbkhia2d4ZEc2KzNIcy8zd3FkMDhYZlhaL0RsL2lxRk9keGRNOCtmOGFYNytuRDk5 +ZDFlbjlmTE1uL09uT3VmUCtmUC9yLzU4bVNSQ1lOcFFnU3dtNWpUN1JFT0VrZ0xyRG9RRUNuTUpC +blFMQTA1UkNBWk9Ic1VTUFlJQVFKeUcxS0JpMnN2NDFoT1JnbnhIQmx5ZUlaNEFZRFNZaDVBcU9B +MDA3R01RK2RRRm41b01PV3MyQjRnMWNxcTNUZEFvcGxXN3hzSm1qYlZxSVJyMXVmZFFrZE9iQ25Y +aVJBNzFNNEZjOGhLUlA5NFdCeWI1NGNadHVjQXRnYnJFa3R2bHMvM3d5RVd3N2pXS3k1eGZVVkFI +Zm5GWE1Za0JrMzlCQ3lieDVTZG9OYnNUbGVmNDFVYzUxU2F2UE9MSWcrY2F6N3dhaXNkYkZ4eUY2 +eE9jZU1TdlpyMXFUOVFxcmdzLzdwNkpZUjFPMUlGbkE4L204Q04zZnhXRGR2MkhCQVoxd1dNLy9j +Q2tCekQ3TGpmTjA2RGV3V3FmZWYyd1Ztd1hURERnQUMrOElCY2RxRnNPbkpqSE95elZvanhxY1dq +RFpJL3Y2c0d6K3ZVSHorV1VkbUxRMHpRdTdKay81OC81Yy82Y1ArZFBOZXRWZTZKV2NWM3o1L3c1 +Zjg2ZmNuOFVmNzRJMGorbDdJdkVpQ1JNQ2Z2SHRkNDJDRndoQTJaZWNFbkVBS0J2UTJwWXo1RXB1 +UXNoM2VNUzIzZnhGYUhoNHNKaVB4SVZvaW4raUYyQmhBMnY3N0FZbW9jVSs5cDRnN0Y4bDBkTlNQ +UEd5Q0JHOFhwSUVLM3ZtaUduQ3hhMVdndURkV0tvcFdhVTI1eDlHbXFQUnNKRDNPcHdLREE2Ymdu +S1htSTFKNmNEeDBWMDV1VW1Ldm5zc1Y0TjZuT1hVMjdQemZ2TURIRFpKd1loRVl5NjhPalNIL013 +K3JzQ2VtWmViam1KRlhZaWdRMStOVlVQaEtpM09MQ3YvY0tSR0E1dThmVlpMdmd5empod09sd0oz +aDR4OUltZzNmRkZxR0o2aGsvMXd1T2lQelhCcVZiYTZnRkFQM0xBcjA0Nk5TK1BQZkRqV1UzMnE4 +c0I3emt6T0x6Z2t4L25lSEdQbm00dHZ1R0N3M3A2c0ViTmFwVExXanJnb1JyT1pROXQwNkU5dnR1 +dkhudXNwY2tlQnMyaGR6Z1JlLzZjUCtXY1ArZlA3bkhObi9Qbi9EbC96cC96NTBmMTUwc3gvVCs5 +MVJoa0tVd0R2aVZaa29vSkNSb0J0Q0QyMUtCSUF5REZYbExOcWhBWVJwT1JyeWd4WXFaM01jRmlY +MFI1enpWY3dmYjVTN1J5Ly9CN3Y1eWY0QjBlYWNBTnhDQkF3WjRsNWczRndpY25ZU1BNWG9NSU5N +eDhuOHRublZyVklDYnlySVhYWnhqc2c1MVlOVStOOWhHZC9BaXVZV3FRTmt4Y2VEekRpNXBkNG9w +RGJIQTNKaTQwam1COGQrbUpXbjJXajdIaDhZem9jRWF3RWNZSlRDNkhtcmNmMXNIbTBPamJFQmdK +aHNEMXdkc0pjekRoUUh3WVlPbmJ1QjR3TkdDTjd3NGhzV29JK0lMbGF0SnpwbEJ6ZFVRZjN0amdW +dC9GODdtSHRXZDZTNWZlcWpoNGVoQ3FRWHpQOE8yUWt0Yy9ZNDVQMzlXdWR6QjdDeVJlRCswYVZE +L01xUTNIZXF5T2FzWWVPVjNXcXNkZG5mcU5UN25zeFIwOG5zT0F3eG9TWm5YakdWNDFxZEVhbC96 +dU9ZaXVkL1dOT1BQbi9EbC96cC96NS93NWY4NmY4K2Y4K1ZuOCtmTFEyd0xON1NiQUFFQWlvQkpv +bnNRYXFnaWdBTkk0YXhRQ0ZORks0THNZdmhNSHNoU2pFSGZrdU10WEVobmNPdkhsZ2sweDFzRm96 +bUZpemxzSlRkSzRIaDRhVEx4aUc4aUVvODloaDdGejRvbWxZUkhOR1FnZStOWHN1VHplS3FpUmdU +MXJEVERqeFYxOHVXQXl4QkdUbUZQLzdaTWJYdng2MHlVMmNhZ1RSbUowV01XZ1ozdzU4VnR6UklR +bllQdDlOeTl2cjg0VHZmcjBSMDBFNTQyYS9HTHBOZFBLaTJ1eDB1TmI3eTYzV21zbUJsV2JpMUJo +TmRlOGF2T1doZWs5eDQyOGNxa0huM1RRbVBwTVcvcUVEM2c4RjBkdStCUERvWFhjNHY4eDZIY1Jy +cHp1NnMrNEhPbkJIY3hxeGIyRHl5RnVEaWNPRVBGeFJKZXd3aVMyUFhvS0I5ejRZR1JZNE1DTHZu +NXJMbHpnVTMvdHc0bStHWFNsSG5xejF4NHhQVmViQXdzdS9GZ0hqLzFkajY5eVBuL09uL1BuL0Ft +Yld1ZlArWFArbkQrTitYUCsvT2orZkNsV0lvc0UwbnpFaGZnTFNQd1ZyQTNXRVRVeUZBZTRQV0lR +bkwxRUs3RTV4R2tNQWhCbHY0S3NRMUFNZllDSjB4NXhrUVNzbUVoQmt0L3BKcXErQVJCVFhvMkQy +MC9nR2lDRzV3YnM0bWcwWVJFYlEvZ3Bub0RGOEt4Q1J4SmNHa0FBNmplUGVJU0pxeWJyZlBZY0J6 +SHByZGNFK1BHbFRsaHdCck5Hd3FFeDh1SEtHaWFSQnljeDZPMFZsMERhaDlSMmErUlFoOXh5NGRN +bHB6WG1ZdERyRlY2c2w0Y0ExWXBYNjhSa1RDTDFsNnVKR1IveTQxTHU0RG8rN1NGNCtQU1FhTlVp +anMvbTZNRWJGem5nNmpOWW52RWx2RG1vNE1HcGRjeWlWckhzaFZQTnNQZUFJMmg5aE1sUWsxajZr +UVBBT0U3MVFCMXE4dHc4N0E1L09PU0dxZnY4aTNQeTU2QytPdVh6bkxib1RJLzExbnE1OWN5ZEhs +MHc0TVFldkxoN2puc3g1S0k5ZUp1REZtTEF3K3NTd3hvNHhmZGQvVEJiWi8zOE9YL09uL1BuL0Rs +L3pwL3o1L3c1ZjNyK1dmejUwbmhDVXB3SnlTeVdNQW5PWUFoR0pJRUJicjBtSVVWQWdTVkJBc01S +aWdZclR0UGxzTTU2Ky9MOVNMUDJhZUpibXEwb09PUWxha1VSclBoK3Z4cHg3dFpwQklMU254TVYw +UkVxY2lvTVJ1dUI4cTJRT29nQ0ZqVVRERUxVYmgreWk5WG5ObFlPNitSR1BDSmhkeGZIcFVuaTVQ +dUpqL0RLQ2NHWnc2T2NhdlVaVG10d0lYL2lYbk1kVHZJeEpSeHlpV21mL2VvMjV4bWVjQW9YVG54 +MklIbDdBME9GS0tibi9xVXljL2Jwc1h6d2RWOE1lajFYdHh4cWhnRUh1TlJUOGZURkd5OTQxR0lP +SHYybUtUWHBxOE5BUGxwaFBuMkMxUnhOZVhNa3R0cmxVaVBkNGJNR1pTcnI1WlVMYmhxQlhYNzlo +UnVYK00vY2FkaUJKSy9uNm9WRFRQaG94aHNrbUszekh3TnJHVlFlT2UyQkMxNEhpRnpXMHdnY2V1 +bzUvdFRtMG05YzY2ZnZlTkUvMzEzeXd3cW51OHN6T2VvYjEvdzVmODZmOCtmOE9YL09uL1BuL0Rs +L2ZoWi92dnd4cmtzVEpOQWNQNUVqMThhS1haRUVMWkZtVzQ4ODRKRURyRjhmQUZRUzY4UVFUd005 +VDh4YnB4bWEyd3VKY2lPSFVKSFJIQVlSd21ndFVZbkJxQnBsSUVWTTY4VkFTcDhUdHdZb1hzUHQ2 +L0NNOE9TRTAzNTdDUUxXR2tBY2pYSlpZNzExMXRTOGlCZUR3SmpEV25XNDR3MlA3dGJEclQ2eFlh +dW94U0E0WE9BUXJ3UmxEaFo5Q2RmWEY4MkhYdzVZNUlVTkZzMDM1TVNibWhrTkYvWmFqd3NIS3FF +K0I5Mlg3RldUWGpFbzhSRnFqU2MrWE81NmtIcXZCakc4Y1lGSERySGxxYm1Zd0o0ZUNPWnhvUlpE +RDlSanJ6cUkwMzdmbWNSb1RmYkFSM2YyR2VLM1IwVHR1VGoyRTdzOWRFRWpPZVR1dTlyeDR6dE0x +c0x2czdkWWFxZDVNZFVwbjdYeXdFVGZPRkNYWE5IYzZkaWMydkN0Smxqb1FRL0ZmT3A1eXpONzdN +ZHYrNmwyL2E5WDVzLzVjLzZjUCtmUCtYUCtuRC9uei9uek0vbno1YWZ5Z3ZYSHBreWdVVUJKSW9D +QmJLQ0lVeEdBRXd5QldPdTVuNndCRXJ4Z3pQbjliQ0xRWEEydEllUk1reTZ2QnNSSVI3QzlNQUZ1 +QU9vbmZ3VTFscmNOR3FVQlJFQkFpQ0w4R2hjK0FwTlA4ZUo3czBTTUxybnNFUit1eG9oQi8rL245 +Ly9WOTlXc055ZDJEeGljbUdNaUZ6enFNbUMxMXFYNTV1U3pCMGZxSUVLSEYxN0ZUL091eVVUUW1F +UWluNnVteFQyT3hQWTVJam44NHNHdDdtSmdIRy9iNEpXbmg0V2FtZGRmZmcvUC9wai85c3FuVGx3 +VnV6dk1hcE1QVHo2clhTdzZ3WHZYaWdlTDN1U1A0NjhtUERvbzlNMGMvSFJHcUdJemhSckV3NUVZ +TG5qRW9RK1h2TlpiaDFQRGM2SW1ibGlxTC9jZW9ESE5hUUhIYXBBUGwvYlNGbjcwQWg4MXVmckVZ +VTVjVm12eTJFK0hNTktXbUdweHlNcmpFUExXNlRuOG52OFk0UFE1aEkrU2kxWE5pZUVPZ3hndUdv +Qi8vcHcvdy9QOE9YL09uL1BuL0pudjgrZjhPWDkrZkgrK05ONVBweTBXZVJZaXRHWWdacVFwWGpB +R0JWZ0I5dm1zT2VJQW9BQS9FU05SZ1lRaGp1ZStFNGVDN0VWZWZpSStjZFNjTUNGQVRELzlFd3lD +UFJOTFhHOGJ4UERUdHYyS3RRNHBQaHN3K3c2elhPS0pZeUJhUE1UQjR5NHZnY2xaa1dnVU10MnR3 +dy95NVM1dUl0SkUyUEdtNlpydnUzcHhVWTdNd2VJQUt2ZjJXSnZtbmFCaFkwNHhQWVBUZXV2a0Zj +dGREdXMwRjcvd0pmZmhNZkRoVndlWVFtN1ljYXd1dWRXUlh3c0lYdytQK2lFT2tYbE9KUFRnczd5 +NGQ0Zk5aUTdITkdHZmV0VlNnNHBKUC9oek9MckM0MTMwWUI0LzN1REFLN1lEQmo0SGp6VTQxZzhI +bERoNnBWNmZNNDR6NjN5bldmUDJxazh2eXoxY3VOVmpuTUtLTDhiVUV6anNVYlA5Y09CS2pTN2ZH +WnpwOGFnZitIZUhKMXE0bm9qaG9qVThNQnh6ZXV1bUJuajF0WnFTcXhwbHpGeUhzMzJhUCtmUCtY +UCtuRC9uei9sei9wdy81OC9QNHM4WFVwRHJMcENBTnBuMFU3OG1hQjV3YWRxdGt3aGdZQVZWdURu +UHJVYzZVWWpoZTR4d0JTRlJBUzFhMHpVOG9qeFFpQ0UyaENyUU9nVExRVWlJNzZGQkZBKzJRM2Nr +STFzOFFuNEU5MTNJazF1Y05sWXoxT2x3VWJmWWJYYnJJeXhrT3d6c01jUnZITmlzUXlMY2VFQzR2 +SExDb3o3ck5hOEhqMGJFUU1jQmJuRnREYk1SUnd4NkE5ZHE4OTBhK3hqSHN6NlgzMmM1NGNlZHo4 +eEp6SG9HZXcycXA3QVFxcnFJeDExK21QVkFYcDl4WXAzZTZJZmE1YWd4eFZLZlhNU3VKOTVDNGNW +YWNlM1hPK2JFTjN6MGdXTzRjSUpqSEtwZFAvdDJCMzVyeElESkd2akV3YU01SE1qZG9XZHEwZC93 +Y3Z5MDE3N0xsMzNIUGZ3d3kyOWY4TndGR3l6aXVIQXVIeDU4cGhsNDdNR0xDNmN1TWNXamNiakZZ +VXByMUlOdmVwTS9lam51Y0loWE9YQW1CajE1bzJhTlEwb2Q4K2Y4T1gvT24vUG4vRGwvenAvdXh2 +dzVmMzUwZitaZndTVmlJcEZZWVlKS2dqUkJERW1RSjVpZjhoSGtwMTFyQlFaQWcvejBMTGcvanRa +bzVFZ3VMdkNLOEF4QURRZFlYdnVJVVR6N0dBMnBobWVhaXlSdlZCUXZab1IxQTJuMnlJRjRnalpn +dEU0TjVqV0JNWWdHQ1FTaGJ2czAyeUdBTUNiU0RIbGN2ak5tbTRWNCt3Z25ocnM4bmtlMDk1a28x +R2FQdklnV1J5UHhhSTRZN09uaDVjSUpZK0dEMEsySHAvandqVVA4MVRENkp3ZEJXVzhlSDh5aFhz +Ynhxd2p5VzFmeEVDK3V1Z2NmZXFNdWZiQUdCdHlyUTQvbHM5WjMyT0dWVi93Ly9lMWZ5aHA3MUFh +Zk9oeldPQkxISC8vVEZCM0JwaDd6K2xyOTZKZm4xc0NCTDV3RzR3MzFWaU13ZEJBMi92WFFXbGpo +MGV2aUYxdWR2di94di8yRjZFY1B2WjJTUzEveEl6Yk80VkNIUE9MaFZTMXlXV2M5UFhhUFdEME04 +QUtMT1RXcXAyODFhU3VhT2J6MWxQVnE0d001OU0vZStYUCtqUGJuei9uemVKcy81OC81Yy80MFlK +dy81OC9pLzRqK2ZQa2Z2eU9zS0lHQVI0Qk5oQldEWGxGRVIvaEV6WHpBYW9ZMVFGa1BzT1JpYVFU +emxWQkMwTkNLMlY2a3VBT2p1T2EwSHpGRWFTQ1dZYzNYdUVUUWVVWEpBN3RhNURNY0ZCcVBCQVRD +YnQ1Nk5ZbGpuemptbU5jNkJNRmdIMzdnVTd0bVdDOG0zRFdvUnR1blJwL0Z4NXNtYTRqWUdtQVAw +Y0xwd01FYm80aXI3aHBVWEhGd1FNajJ3K3FaUGZLSWkxL0NrUXMrOFlsS2I4U3doM2tJVUh6N2Nl +bFF3Q1ZEd01pWTZyQVhOblgzWUxOT256elhON1dwRlE3OEVqNmorMTE4ODU3RGkxTzE0dGtsdHpk +VmFsV1RISVJPVi9iZzNxRWdUN1dnZm12Z2l3SGVUVWhuN3UwL0U4TGxPMU5ZaXhlNTVGWWp2ZGpY +ZFE0VWVRMzEwWXhld3lXdnVucFk0VmxNaHdWc2NGaEhCemcyaDN2K3dRRmU3WmRQSFhpeUhrL1Zq +Ujd4Z3J6bXJGZDdEdGZqeEFYei9EbC96cC96NS93NWY4NmY4K2Y4T1g5YSsxbjgrVEtoS0NBazFu +d0oreHdSaHNJRVU3UkV3Q2djQ09RVElHQ1NLc1JuZXhWYm9jbEJoRUQ2TEI3Qk13QkFpbVZBeElu +ekNQYTd4RWFHZk9hWnY3OXJiY0FDczN3YWpSVERZVUdnMWxzTFErYzBEZ0V3aU04d0RoNVlZU0VL +RFNOc1EzeXhOU3k4WENQYUxHdHhvS0UrRXlhc0xySEZKRDZOWWtEMXk0ZHJCb1ZCSHFJaU5uSHhn +eHQ0Sy9nY2NEZzhidVR4WFF5TnRkWXozR215SHNpcEQvWVRBU3c0MERPZjFTZStTeDNpaVFVUGc4 +cUpON1czVjdEaHVRZTZ1SGhUVDlhOGEwSTlxZmQ0cGgyOFdRZVBmT2IxeHhCTFgrSEJnUnpXd1E5 +WGY0WEZldlU2Y0hDb2YzcGxxRmNPY2ZBRHN6N1pCeThkaUljamZmTG03TkgyVzNxZzNsNXl5SVZy +UE1FbkgxN1ZwTDlxZ0VOdjlkNmVIdXpsVzQwNDlBYk1kOTRSRTIvaWlBY25QTzRPMlhLR0Evdm56 +L25UcFk3NWMvNmNQK2RQK2N6UG4vUG4vRGwvd3Z0Ui9mbVN3QWRKZ05jUTRyTzRQOUVhZ2dLbHda +cUJCR3VZaWlnVWp6QjdrYVZKa2dKdWpjSUJ0VTlNbjhWRGtvSVZwaWtJaGtGaG5obUtreGRXY2Mx +NzJ5QytRUURtRlNxR2VVT3h2bGZvMXNCc2FLeTZpVXA4NWlNZ1RWWXJMc3haVThPSnBSNE5SYlFZ +TlFNTzVJWFpmalhLNjRKYjAzeDJHTG1MSlFlT0NkTUZmN0RkR3Z1WnBiV0xMWWM5Y2hLUTlYZ3lU +OWoyd1ljUDg3aXlYMStJU1MxNGtFdHRlZ0tiU3kyd3c0WURNYTN4R1JhZjhVMkVldVp0a3M5dytk +MXZzVjA1V0k0cmVZbU1BZkJCNkg1Zm4ybkVNZjlvNjB1d3d5b25IZG9EcDl5NE45emwxRDlhVTd2 +MWFqWGMxVUUvNHNJbGw5eDRwRmtIdFRuODZXSDE0elBlNUtNRC96R0FnN254b1Y0WFBlbTF1dUd3 +anVuMFZyNzIzWE9hbHg4bSs5Um9YZnVEYS9YZ1JTeFk0SVJOL2ZxaFB2dnhPbi9Pbi9Qbi9EbC96 +cC9pbUgrME5YK0tCY3Y4T1gvT254L0xuL2tCVkRCQk5GWHhUR2FEcGo1dmFkNGlES1FDTExHRUdw +RW1IamgvN0Exc1JRWU1FVXJxa3FjR1ZYd1BBWGtWckhBeHhFYVVJb2xQQStVaE1nVExBUk5SSU0x +UXNMVUVUSHcxb1pnd21QTk0zTTVwdUpod3FVV3RmbjFCM2VMQnFGNzErWW5kOHg0Njl0a2pCdXp5 +eW9Ic0I5TWpPczJ3SDZldUovZnpoa2d0K0NacW9tanoyaHo4NEZJK25CT3N6L2pUV0ZkejRPUVIr +M2ZaUXlpTW9pWTFtSmRIVFREQWlRdjF3TjJlNFVzT3o4d1RpUFZpTTdOMURnR3hZV2ZBR3RTOEdu +dHcyRS9nTWRqeEo2WjFzTUNzSG5ITWVRYUwzdFdnZUlOVEwvQWhscHJ4SzFmNU5tZklvNWQwb3g5 +dy80Yy8rUHNSZkxnNFhkRTU3ZEFocmJucmxYN2lGV2JQeGU5aHFBL1d5V3NOdmNLQ1Y3bmt3UzI4 +NXVESHV4anFnbE1jbk1nakRuUGl5ZlB3ZkxqdzN0N2xMNlFmeitxYlArZlA5bXorbkQvbnovbHov +cHcvamZsei92em8vc3cvUWxSaUpmQWRFVXloS0o4TlFZa2RzWDQ5QUJuQTJhdEpBUG9wM0hmREhO +Rkp3cUNBS2xBY3BNbW5VUnFnMGU2YUZMS09VQVVpb2MzMWs3OVlpdFpJelNaUUJOc3ZsZ0poVkxU +QlBBalBzOE9JQ0RXMXFYS1ZYSGR2aUdDRkQ5SHlxRmNPOGVGekYwY3Vnc1laSEhoemh4MGVEY1po +VFM0T3poQnZyZVpxUHF3YUNpTnUxQXUzL0lTUlh0ejZmditXUThZUkF5NzVEUE5pK0s1Vy9aQ0wy +YXpYRjdqVWE1LzlOVGZjNnNLQmRmWllhdzM4MWhHbk5mRG9JZndPTnB6WXd3RDJ1S3hWaStGQThv +YklPaHpLL3h6KzMrVVpydlhYTXh5Wmw3UDdEWUxHQWE3d0NoOHhHL2JBZzB2NENCNGVYTURvVnhS +b2dFRmcvMWFQZENDMy9vaExlL0NieHo5TSttb3RqbHptYWJIK3NaKzVYRENJcWUrdzJDc2YzR0wy +c0FsL2gwbnQ4TkpQREhwNlV6ZmNqVDkvenA5d3EydituRC9uei9sei9wdy81OC81OHlQNzh5WDUv +OHZlSGU3S2xoUlhBcTVYOXZ3WXo4ajJDR09NMUFnd0dGdXl4elllakJFMERkMklCc3h0WkhCNzVG +RS9RUCtlRnhocGZvNU1sZEdkK0NKcm5TN3pCSzZqbGRMdXZTc3pNbUxGaWxoNWRmYXRlNW9CWTVk +aVJLQUV0Z1dmb1NGU2NNa3FGcUo5bGpSNzYwRDRhVjFBQ1FPT1NFRDRlbW0waVd0OUN6S0M0WitO +bUNtZ3ZZWkUvVlcrdTZaU09IRTBra0hjNHJ1c1p4OGlDTlFjdnhyMWx4OThiVVZsS0lUbWtJdkMr +S3Rxei96aEFRYllRNVlEU0d6a0V3RC9jSXZMYm9zKytaaVhKM3o4NG5jNUdCL0l4NDA1UEdnaStP +QThYRiszU1B6RHN2Nm5DVHk3N05Fb2N1QWY5M2dVejREZHNCOUhzTHJMM1Y1TndGNXU4c2VWaHRQ +STdtTElad1Y2RnhNdTdDTVFOajdyRlhuQ1JxQjRnVVVNK2ZGekRzTGIrcFkvZ1c0ZkRFZDQyWHhI +WU9iZ0N5ZkV4aDRPOFJ5RUx2bUt3MTR2NEh1eHp4QUxSb2Vzbm9RQnAzRHlvMy9rQXErNGV4OGNl +dFViT256cGV6SHN0MGNPRVNaY09NNmhMRGE4ZU1xaEFJTjF2T3EzWUxRUFpuK280RTVNUHZDWlh0 +WWJNRzM5aGkrK3dsWDFXWDFXbjlWbjlWbDlwdWVxeitxeitxdytuMTJmRjRFWkFjRTVCNERiSUVI +emhya0lWRU90elRnQVVtR0FZUytZWVMwRjlpd0dVQkpHbXVURjBJQklkdWZMbXdUMjRrak9RQUp4 +QkJNN2pXZmVrS2ltUXlweXhOb3hpZnJ0Vld4REp0OEVwM0UwcDRMQUNEZGJOZ2lVSC96czRYT0hq +ZTgwRnpMVE9QWm5uc0RnZ1RjNUtSZ2ZodVpaMitGQ1VUUWpUalhZRm1jYU5NMGtON2IycUFVYmVZ +cHJYZXg5QzdIaUhubFBiQ0kzeDZmR0Z4c0g5cnR3NzdQR1VuLzVya0R2Z2t1VHd1dXplUENJa2NN +SExySFVYbTNrQ1NOLzlwaTN6cmQ4SEFnT1FIMml4M0MvaDhyRTVkOWJSNWowaU9ZTVg3aTNuM2pV +UTA3MjRrU3Q0RWplWXZoVjBOYnQ5Vmw4ZVBpUENPU25qdkRxUmFMMkdTNVk3SmV2SEZZUGs3ZGF3 +YUJQWVJSZkwvSWhwejJ3Qnl0K1lIVGwwTWE1bUhqaXc2SEVqOWh3eXR0Nk9OT2JjTXUzK3F3K3E4 +L3FVNHpxcy9xc1BxdFBzWFpVbjlYbmsrdnp3akZuQUNCR0E5aWt3QXE0RG9kRWRsdm91ZndENllq +WVQ5NFJoMEErODJPZmRlQ0JSSXltU3FKYnlOa0RLTUNJME9RT0FmdVF4QzlmOWlQZVp3M0hEdm1T +a29naThlV3plUFpuRUllL1NyYjI1dnRmZXZ2aDMvM2hTMUdKV1k1SVRnT0pnV2o0RUdRZjBwR05Z +SVdVaXp4d2dIekVLaGplekJOdW1pUUM5UmxHdzUxL1FvRlhYQmdWVEw0YWtrODF3SldZOEhrV20z +amtZRC9zR2gyM3hvcHRCdi9zK0hiQmJ5OHU1V1JPclRSZjltZ09qYW54N1ZWck9PeURYenh4OUFn +c0RqclAzaGpLazErY21ITlpsd3UvOHZPR1JJL0FJRzhZeGNhVHV1S2JmL3lyTVU1eGp4UGM0Tlor +MkhEQ0h0ZUdmZFpnd2E5OENZaVk1T1B0bE5yS1FYeTF4NXM4Q0ZUZjJTZGY5bkpoYTkxZGJIakZj +L0V2Wi9QOHFSVU1jalp2WFY3NDFPTnNvcWZ0bWVsM3RaT2pmUGFRSFY5eUpWQzgyVjk5VnAvVlov +VlpmVmFmMVdmMVdYMVduL2g5TGZxOEtCQ0hpaUFaeEJLalJRNVRlQTNCbVdielY5S2NJeDFSRXBD +WVBacVlINWVFRVdFZm9Fdnl4SE1IVWt5eEpKV2lzWTBBelRrYzNCR3NJSWlFUVVINWxJaUNJdlR4 +eW1EcjdZQzlpbWUvZUFhOFlzRXBSZ1Nwb1pDbEtlRkRHbXorS3RzZWM0cXJHUlhMM1RvZUVjOC9Q +dXlYUDZ6NGtxK1JYSEdCUDV3Umc1Z2ExcVVaTnNiWXNPY0h6NXBNUWEzenh5LzdMZXdjZEtmQWJ4 +ZUhmQWp4TVQ1ZXQ0SHZhemtFRGJqWk9PQ0NTK1BvQ1hPd0VBeU8rSmM3WHc1UDRyQUg1c1FLei96 +RFR6QnMxRTFPZWtyOXpNbEpVL09wb2VHMHgyZjFNQ2NXdkxCYnQwOS9HR3prK2ViZGQzWmRMUDJK +VS9OczdWVXJjZFZtZTNHZS9RSEZ0OXJLZ3owZjV0aklKM214ZHkyUGt5TmM4bkhoSnVLMXBxYmlo +aWQ5aUR1KzFITGpqYzJuMDRPZWNiMmNUUjNWMUY3eHE4L3EwNENiVGZWWmZWYWYxYWVlcWo2cnor +cXorbVQ3alBxOHJPR1E3MDVVZmpyV1hPNFM5T3duVmcwUmh3UktTQkpKOHdoc3pSNmtFU0cvaUpj +MHdCcGJ3c1FKSkgvMk93U0FRWXJHbFVqRUlMYmtIUlppaWtGa211dGdPOStGNThlbENPSm1zRjFm +TTJjUHdXcUtLZWtTQ2JzaWlndW5tTWlCVXp3eEZOODZEUGJBZ3krRmlxMjltZ0xKZk11VllQblhB +TEFkWE5mMWF3NFB5WWRmUG5QZ2VKUGc0a2R4MVlOdmVIREpIODQyM2hSMHIzdXpHdWJGSmg2eDFF +R0RpUTIvdTN3MGFvYTRpL05lRjdVVUZ5Njh3Z01mM3hyZE0zejhzK1hUWi83RndwdDgzVFcwWEdH +UFAvbmpEMC80YytrRlRZOFBlM3lHUzQzdFlRczNMdldjK1IyVE8zdi8veVA0Mk9LS0wyL0M3Rm5j +VTJ1OHBIOWdzSTVMUE9QSlFRNEwvOVlkUmprUStWTjM4VmZrd3dPZjlwbUhRNi9ZNjRDREYzNjhp +Q25HNWpNWGJtSEJTMFI5RHJYREdmKzRFc3U5K3F3K0YyZjF1VmlxeitveitWU2YxV2YxV1gyS1hY +MCtqejR2SEFFSENPS1FxemdjSVI0QUF3aDJpdWNyQ2dUbXpjSCtkRHRnQWZCWDBKclVrQlJTSkFv +d1cvNDFNMS9pU2tvc2J4REVaYXZJRXZHTWNFQVJLMW1KOEU5a1B2Tm5LQ0wvNHZEcHl1QnY4NW5r +N1ZOQVRXRTRFT0NYS3p1K3hYQzR5Q2RGZ29FTkVuT0FiYkVuRjBYeE9RM29XWkhrQXdmN0xmNWRW +SVo1c1RTeEdCR3d1QW9rbnhSZUhIYmk0NVNOWEdIWGhEaGdyNmliMThUR3AzM2lhajY0K1Zkd09O +WE9mZ2VzL0RMeUprTTljV21QOWVRaVBuekJFVjc0Y1FqaVVDUEQ1TDRIeGh3YzlsampBL1k5bkNm +R052VGdOaCtSUjVENlRQUENKQis1eVltTnk3cTdkWU5RK2ZDSGg3ckFKWloxZFdFcnJyejhXd2g0 +SERoNDlodm80RkU3Nnc1TTNPSlBydmJGcDF6OFFXWWRuMnJEai8zbTRjZWJ6L0pTZDNuQ290ZHdp +VHQ0N1dkRHhPSkdvQTQrQndITTFXZjFtVkY5VnAvVlovVlpmVmFmR2Z4dFB0Vm45Zm1FK3J4d3hN +QkdTUU1ocUtKcUhtUWhFZWtJa29UdkZDdUFRQm9RTUlrUXFHSXBIQitlRVNFWkFKRmhqenN3aVd2 +d3JTQklRWmo0RWdLWXJhOG9FTHlHMHd5d0lRMDI4WkNpUUJwUEhobmlzNVVYVVJCb0JsdWlaVzh2 +MzBpQlVYNElkR2tPR0I0SjE3Z3Jvcms4aXdFZmZ0akxLdzN1TW85RDVGdGJNUTludUlGRDNueHBS +bnlLcnpIWU9DendZNS9QL01rcnRaTC9EdUljL3k0SHJqM2V0c2xSRExicURRZHVZZUNMdmFGcDRH +UmpUWHl4MDNBT0RESE1hNkFWMHN6ampMMWNOT3MyOU5pdTMvdUJvZFk0bHFlZTBaRG5ZRGwxY0hE +QWhGdjJlRGNQazdlRTZtd2ZPM3Y1WVErSHdaZSswWnRpNnplNGNhbStiT1Ztbno3QUhkN3c1UCtu +eEFhbmV0WmxYVDNsaEM4KzVRVWZUTmJoZ1ZOTjlBVTdPTGZuaGcvNFljQUpQTjQ4eFM0YXdZa0R3 +Yng2NVdEVDJ6QlhuOVZuOVZsOVZwL1ZaL1ZaZmVLaytxdytYNHMrTDRJUUJxQittcFlBWjVLSitH +d3dKN0Ixalp4Q0FnZzRNQUR3cFhBU2tvU2dDTlVJU09SdlJUcnI5ckxobjIva0t5UVJpYVBwSmN3 +SDBvZ1d1ZXpZODZGNEJNRUdoajFZaHB3enJtc0xGd3lFL2RFUHZ2TFMwUExqRDRuOGFUVDdyU1BJ +WGpid3dXb2RvUkdiNXNDYkhHQ1ZPeTRjS3NSa2ovM2h3VDVyc0pqWEZHS0l6OGE2R3VDUE1NUndk +MmtDZG5oa2kzdEZkZGZRUjJUbi94bUY0elNwcGxWb1RRQVBqTENJNjhLei9RWnNZbWxLdHR1WXcw +TU9HODJUeHRKMDZSZTFrUk1iZklodHIzWDJHbHBNT2J2NGQwK3VPSFg0d1dtdmVxcURYaUNJL2Zy +RjVBWUhHN2pESjd5Ry9PV3F2dnpqU1E3MjQwaFAySk5hRTU5WTl1UEdHanY4OEcrZUxSdTV5ZDBG +cy82RlJSMnM0ZGwrdFUrdjRNVWREcHl3Y1hpd3p5R1BveHdlN05pc1FDZFh2dkNLcCtxeitqU3F6 +K3F6K3F3K3E4L3E4NHpxcy9wOGJuMWVJandrMm9Tb2JhcHhvcUczbVljZ1FDV2lFUWxJQWk1N05R +K3lKTzB6Z2hWV2d2d2dHbW5teGJBVzRseUtwT2o3ZVFDTEM2RENXWk84aE5NTS9JWHdMYzQwbUFU +aFp1T05TQVlmdi9qaFYxOGFnTy9Uek9kckZQekJtWFdraWlrK1FmTEpubCsrY0tBd3lIWGd1T0J3 +T05oclh6QkhCSXF1TUhMRWdXY0ZscXU0T09JekFzZ0JnVlAxZ1FGR09lSWNibk9lOFVvczZtY1Ft +VGROL0xOeG1NSW1GL2pZcTZXdmVNaEhYSHNNZWVZZ0VEOTU4cytmUm1QaldaTnFWcmw1QzhVZkxt +TXZsK1AzL2orZG5WelZOM1hFUmNUTGh3dEcrYkwzR1RkdytJdzM4ZldYSE56eGVONFEzUmFMMnVS +NzZQeUlCeWMveEJzeHlBOEc4Y1hSMDNyS1lRMERHejdZcVMvYmZSczR2Tm1qZDlXQ1h6MlBEeml0 +eXdjK2wvaDhPRER4NGVzVDV2VUluanp6clpmVjZrWGtNOWlyWGZWWmZSNGRWWi9WWi9WWmZWYWYx +V2YxV1gyK0RuMWVCT1lvQVNUQjJFL29FcGE0QVpTQ1NrWWd6YUVRQWl5WTJVY1lpRUdtZTVvTG1a +NVRWTUhGQXhJUkJBYWNSTjBWV3FNZ1M4SnMrSFpISW1MYzdWY2NOb3FsRWEySm1ZRjRBalZuelJz +RVA4VWJtb1NBNUcrdklyb1RtTnpTRFBDNXkxM3gyQkNQNHJGVkxEN1l3OFNlNk5ndloxTUEvSVI4 +OWdySEo5elc4SzBXc01sSE00cUJFMDBVWWRvVDNtQVVseTF1TS9nUmcyOE5CTFBjMWNxOGRaand5 +Nzk2aWVzdUJsdjQySXBqemdXRGZOMzFpaHBxSWdlMmZ3ek92enJiQnhzZUNKcUk1QUNyT3VDR25a +aWFWNzJEeDE3N2d0ZTZHdS9oTVh6QWhTKzh5aS85Q1R1KzlRVTdkUmVIdVBseGlJZ3RENXpnZ0cr +ODZ3ODExV1A2Z3g4OWFnMjNmRzNQVHM3aTJldXd3NnNEWDE1cVFpTnc4R1d2UzJ6Mjlqbzg5Snhh +cFFiNmovOXdqVnREUEd2cytLOCtxOC9xcy9xc1BxdlA2clA2ektnK3EwOStubFdmK3plZ1FHc0tB +VFFBRWpRYndBQXVPV09EUU9RQUpTQlFpRTh4T2RTRWtwQ0FnSmxQUXdQSHhnWHNFZG5iTFJnL0xv +Q3RTWkF2Uk9jbmZOL0RWeHh4NGZVR1FnTWd4ZHdXWnVKbXNGVUFSQ0hlcnpHV2s2S0xMUmQ1MmFP +NGlxNUIyT0JBekFoT0lUU093aUE0YnpqWVJaQWhXRDdtMDNqMkl0M0FoWU5JdytBSk5qNVh3TDg1 +djZKWTgvQUZGMHh5MVBUNE5NKy96OWI0dFM0blRZOHpObmgzbU1MTURrZGkyZ2VYdHpvd0VwMmht +ZVVCNzlyZm13elc0RmRER0ZKRGphOG1ZcWtaMzh2SDF1RGdVWFBjSmwvcmZPS1Z5UEVtZi8ybS93 +eHgxYzY2d1paL1hLa0pQNmQzcnJ1T04vbjRCOWl3RWoyY2VDUU04em5jOUJLOGZKdjc4RnVmMjc0 +VGZ3L1V3YVBmNVdDL1dHemxtenJ3ZzgrMW5YcjZ6QWFIN1BXaHV3dHZMdmdjaUJHeXZmSVVHdzY1 +cFgvYzVWQjlWcC9WWi9WWmZWYWYxV2YxV1gxV242OUpueGZGa3pESEF0dWdPQUw0U2ZvVS92elBa +UlVPeWI1empuUkpTQWc0Z1JVQjhRckRubDlrSWQ5OC9ucFhjd01Ba0gxaUU3elBmTUdrQVJFZ0VY +dVJyS204dGVLYkdOUHdtZ1p1bU4zRjNPYVk0c2tCWG04eXZNWHdmTVoxOS9QcDBwaml3aUJuaGRG +SWZBV0RmRmQwUSt6eU1zM25yZ2p5c1g5Rk5rT3g0RndlQjdOYzVLcVIrTkpzR29oL3VTZ2UvM0Ro +QTcvdzRWRHh4SEZvc0xjWFA0dC9zSWlEVndlcHZPRmxwd25WQndlYXdET000dUtEdU5UMEhIU2ZD +VlM5MlBPeHpUbTEyZnZrUlB6V3pLbWp1c1N2ZWJtOUNIRHdFTDlEVFA3bThPTXVwKzJGOFNPZVMw +T0tTWXg4NGtSUDRreXM3YWxaWDJ6REN6OHdpNk8zSExiRWw4UFNYVzk0NWx1ZHhaTzdQb0FOYm0r +RmlBd0czT0JFcmNUQnVaNklLR0hHQWY3WndtQk43UTZQdDhXbzduSjBxWk1yQWsxL2lZMVhmdlN0 +UHNDeHdUKzhiS3JQNnRPb1BxdlByVW4xV1gxV254dTcrcXcrcTgvbjFlY0ZtYTc4Sk8vWkpuZUNr +K0RMaGtrVU1YN2FscHlnYkFBWFJOTEFHUnFXTFZJbDdwNWlBYmNOUEFRcUdKSVJKSzRFRkJNcENO +SnNpdU9uZC80Y0hocFUwMFVNaUlITlB2R1Jjc1oxMzE0UUp0K0s1eC9LWnNqWGdXT1BPeHN4NWJ6 +Tk9wak53MkErUlpNelRQR2hPUERqVEFNczVya3JuaHhoVGtOcHZoUXdXRFdQTnprcjBHbEdEYkFj +VFF3NXdZVlBuUEZqajNnK0t6S01hbUh2anFrbC91RG5rdzNPY0NpdTU3d1oweFI4RzVvY1JqYm1Y +UENuT1RVclh2U0MvTVJVYzd3U0tsdzRnTkc2V3FzNUFlRUFKaHlLd1ViZCtCUVBScUpraDMrY3FC +MC80VUl1cVJONy9NRnN3S1V1RGgyOWFBMUg2cVA1OFNoZi9qVCtMei80MnZaUCtzb3pmUHhIeU9M +WUx4YXUyZmhNWVBLd0R6OTcwTXk2TmR6TFR5NzV3eTVjNGdvMjlXVnZEVjk0RTV2OStoaVJzM093 +VzNkVm45Vm45Vmw5VnAvVlovVlpmWjVSZlZhZno2M1BDeUNhVHFMSVlxd0FpdWdOd3dwMENxOFlS +Q0FCeGQwZ3MzZkZOeUFsbzBoQUc1cERVSVVGRm1qRlFpUndhV3JQbWxaTW9CSVhLZmFJdzlZY2U0 +VENJZTRLZEFqUkNOdndFenROSWtrREtmazF4SHordzNmLytPWFFRWnBjTkpnOWZOaWZOdzd5TXU4 +WmVYalNyQTRIZVJud2F4NjVzdlZNb0xEWkx4L2MySThUdm1FaERBWENwelVjcmNqbVVMRS9SV2ZI +cnozdzhtWGZpbVRXN1RVSDF4blhyVmNPRTJzdWVOUkhvNFpIblBLMVBObzVlVnJETCs3aGtRUCs1 +ZUZPTEJ0NzhzU0IvMmNXL2doRGZ2Z1RsODJLWityampsczVFZ2tNL09rSE9lVVEwcERzMk12WndR +cUhwazNQd0l0cmZQQ3hNZTRIRS84T1lEaXM1K0FpT0h2RnNGZWZyUWpIcDVqYnQvZEQwMXIyd1Nv +MmZHdzk2eDI1dzgxT0hMbml3cnI2ZXRhZmFnTXZuYmh3WkE4TjdCOEF3NjJZOE5yanJ0LzVnRk1k +cXMvcXMvcXNQcXZQNnJQNnJENnJ6K29URHV2UjJUUHJjLzgvb0J4cGNvRVJMQm5FS0tDQ0lGa1JF +U0NBZVo5ZDlnUHJydWdjRyt6OGxJOUVkd0FpVVB2TWF5TFBLOEpwREFERlJSNHkrU1ZlaVJBU0g5 +NGFlSDU4UTZRNTJiamtZbitLeHRaMzVJa1RKdnV6SnBZaXNIZVhIMEpoSkJMRUV3amlWeVNUWXdx +dU1JajB0c0Fjb3VYa2tNdGVPWmh6aDB2eEhSeDg4U3QvbUJVQ2Iyd05mZ25VeFRiNytXUm56ck80 +bW9jdkRXQ2YyRVFxbGxyS0d6YnJEaXVOSTQ1bThWZm05a2ZjOHNaNTZzeS9PUHBCYmZoSXJXQlRE +LzlBbXg5Tm5tWlRGOXpDd21jRXB3L0VrN2U5NXZtR0NaL2l5ay9Qc2ZkVmd2UU5ubk1RcXBGYWlL +VnY5MUNheXhzaW9sYURjT3ZaSFA3c2xSZHNPQ00yZGM5WEZPQVRGM2R5eDUwOFgwUTJJb1VOWDNE +eHArZjVFOGU2bk9GVTArM0p3WWhUZmh4bTV2VzBZVTJNNUFLRFBJMGNKdGFxeityVHFENnJ6K3F6 +K3F3K3E4L3FzL3JFN2JQcjgrSW5aRUUwTFNlTUZZVnpoQUdEWkVRaVY2SUV5aGxBSEd0Z2Q2SmhZ +eUJJNFRTSkdQWWpHcUhpcElpZUpXVy9DN0EwVnNDeUpUU05wY2swQXArbjRkOHVPZG1maHN5UWc3 +Y0dpTEhtYmNhT3lSZkpHaU1GazVkaWFCeUhrdnpNeVUvZWlndUxIRmRNWTJPdlFzc0Q3ajAweGpl +L09CVEQzWHhFeEE3NWZHZ1FtTXg1czdMUXBvQWFnZC9NdTBlZ2ZHc2crT0NWRzF1K004enp6MTRN +enhyZTRhSjJhcVdoTlNUK0RIbDd4bDhPSS9oaHg2M1BCQXFIZlBVR24zeHBidnlLeTA1VDhxZUdj +TUhCVGg3VzlZZTZpd2NUSExpV3EzV2Y0Y1laUCtIRFBweTdxejg4TzZZV2VQRDFHWmpsd0ljWSt0 +dGJ3aHlLYXFrMzVhQS80QkpINzduNGhaK2R1TEFUSFI1V2dNUDcxbUU0Z2hjR09iS0JFWDQrK2NM +N0NuUjZTQi9xbmZ3QllCMW1OaTV4MmFrLy9QQlduOVZuOVZsOVZwL1ZKenQ1V0s4K3EwK2orcXcr +bjFtZmx4VFNKTUlWbjVoY2pBVDAwN2NnaUVIaXAvTVRQV0tCWjdOTk9BVkV0TStHcG1Sbmo3Y0hh +VTRGNDVNL3NmaFJnRFN5U3hINWdndnhTUFVab2Q1c1NJcnZDSlRQM1h2SG9GRmdOaFFwelNOaGI0 +amthb2hwTGdkTUdvekF4SVZSQWZtM3BpZ3d1U3VXZkRTN2EzME1CcndaaXVlWkQzdmtud2EzcHZp +S0t3LzhzRnR4ejFBOEhPSFhvUUNiWEI4eHhoOC9NTExkdkdDZmdVTzQ1QTh2UHhwVlBINDB2MXJC +Y0dyOGRnOVB6K3J5ZUZBbkwzemhRajZlTlNUczNoTHBDM2p3dWJISDNvR2N3MDU4ZFJCWC9jUkkz +YzFyWElldXZYSVJXeTNWSG1mNnhHVWZ6T2JoRXVPTTYzTHg1dnRmMm5VNTR3d25ST3N0WVRnMmJ4 +MStuRGdnYy9odTduUGhibnZqZnZEQ3REMDgvc1RHYXdSb0hrYjVFNTluMlBteEpvNDVmN2lJZzJj +ajlRL1BmRzRkcDYvMkQ4TzVxcy9xcy9xc1BqZTM2clA2ckQ2cnorcXorbndsK3J3SWhuU2JBRlE4 +YzVMZ2pMR1JoZ01xVFEzUU5zNDRGOGdiQitzRzRJckJsNElBb2ZFa2dDamcyQ0lDdWRiWmFqNU42 +R0pqd09VbmJqRWxMbEhGRk5kZitmUHJJSUFYOFFnOUFyMjl4RTh6YWdiemhsdzE5eDRPODR3Z1Bt +RTBIRmdhRHovdS9JcmhMcDVpSWQrY1FzaUZML09lMmVDQlBmNTg1cHM5OHMzaHoxOWR5eHN2Qkdh +L1FtbEkrUEdVdXNqRGZud0Z0NXc5N3lEUXVkUkR2bUo0NWw4enloOGUrZVNRMElTR1ppZFEzRzlP +azVzOFhnUTVPZXFUYmNyLzg3KzMvdDYwZVBzQ2wzVTFoODFlK1BtVVR3NUtmc1dHUjQ2cHJaNnlO +M2h3cFc3aTg2UDI3R0dIR3hmaXBEOE52b2tSWDJ4T2I5MVduTDZHZ0F2KzhjVTNYR0o3eTJXUEdH +b0FDOS9zeE9STEQ4aFgvdklrUHIyWTJsaVhjMnJIaDMxc3MwOGM5ZFpYQmoxdEg4OStuT29KUWpa +Z3haYzhxOC9xMDZnK3E4L3FzL3FzUHF2UDZyUDZQTDMxM1BxOFdOQ1VmbHEzTVlWNEZDZ0JjQUtB +ZVk3WVdrZWtSRFM2WUJ3YjVzMjVzM2RYZk9BbEwrWVdVdEduaUlnUlF5RVZqMC8rODRaSThSQ09H +Rzk1TkFZTThDa29YR0x3SzZjTUJZRkRBOEhobVUvREhpUnJFbnMwSnNMUCttMzl3cU1aN1lNbkRT +S1drWmdLWkYweENBUlhjSXR2bnArSVNCN1dmSlk3QWNHM2U0M1pMelp1K01ZSFhuRWtQcDdzZDdj +R3o0dEFIVXl6M3p4c3hLRFFiSEJHcEhMQmhUbDhacTg2YTZUVUFDK3dXdC9uYVNpNDBnc3V0Zmpa +ZDc2d3ZMSERwenp3b2RrMExEN3RVVVA1eUpXOXcwcmZFUW5CQkxPODhZWVgzTU9rMW1MakFYYjVy +d0R2dFZRdjNNaVYvNGdMdjN6bnphSmFxTE9ZMW5IQ0g3enc4OCt2WFBXSGVSekNBWmZEd2gxditq +MjlBS00xT2ZIakt6V3doRHZ6dUplL3VIazdSclN3aXNjblAzcmFzL3lyeitvemU2dlA2clA2ckQ2 +cnorb3pvL3FzUHA5Wm4vc0Q2RGJsakFVNEN4eUdDQWtpbVJPYk5ZazFQMDBEYmcreU9BTUt5Wm9F +RVpMbkd6SDIyUzloeFpLWUlpaXllVEVSb2psV1RMT09jTVFqQUpiZ1FxN0c0Tk5Bak1TVEpGeVBJ +b3hQaExrUVlNZ2RibkhZMmVjbit4M1Q1SEJaeTJHVUpsMUNOY2NNOTlnc2hwMi83bWYyc0p2alg1 +SGw0N08xUGFBbWY3bHE3UGcwTkRuNzhDSXZlUEdMNzhVeTlxbVRwbmtaazdzOTF0WEFCYk5jdmNY +RFgzektLM0VKbEtpczRRd0dHTlZZUHZobUk3NW5sNy9lOTFZR0RuVVZBelppVW1zY3V0UWFUdkUw +Tmw3MGxVTWI1L0tYR3p2N1lWTmpmSXVwcVYzNktUWERoOGJQVUdOdkQrR1hMNXhpK0JYZ0x2WXVt +UG1BenovZ1hnRk9IYlpYQndmZk9QRXNaalNSUTRsZmg1dzhZTWFEUE5TTDhIekdzN3pVaFZiVUpI +K0FzWVhibW9PRG5UekZ4WnMxUE1FRGIvVlpmZTYyNnJQNnJENzNzN1hxcy9xc1BxdlA2dk41OVhu +UjNFalJPSW9nU1F2dWtqR3ZhQUJKeUZ3RWFnNFJDcWlnbWw4eW1sc3dQeEh6WlExNGZvQkRJSUl5 +THluMi9JcHJIM0o4TmhRVEVSS3lWMEsrbTYwaERMallXSWNIRVJFb2Y4amkzejVyRVNnU3JNRzhh +OU80MmNzR1VXTHduNFlVZ3oweEdva05GNUk5eXhFLytOVGtFYSs5L0Nxd2l6RDVoemxOc21QNHcw +c3V0bUxDS3c5K05KRFA1dm5SUUdlY3Iyend5MFkrR3BKL2x6Y1NlVk5ETVB3bEYzV0FteWo1aFZ1 +OTVhWlI1ZWFlWE9FZ2Z0K1JqN2kyMlNhdVJzT0RtaE5vK29NLytlSlNQR3Y2eUpWRFcwK0t3VGNN +K01Bem4zcENIcHZmWUVndERYOFErTHJISG5xVG16NGlVTitieDRNL2JPVEFMLzlpNDBZUDREZ1lj +K0RoU1h5NTdXRTZ0WWl3SEFSeXhaTjV2dGltSC9TU0hsY2p0WUVqZWtsdjJnY3ZPL0hWQXIrcG56 +eXJ6K3F6K3F3K1lhaytxOC9xcy9xc1BxdlAxNkxQQ3pJRnRJbHpmMzJMSkFacEdFUnJkQTRKVGtF +Vk1tSmlHeUhZQzR5a0VNRmVVY1RnRC9saUltT1RtM2xGWk9kemZDYU9rZjBPQWtYMFV6VmlZWkFN +M0JLRVR3RVJrVUhvM2x3cGtEY09taWxEUEpqNVZ4VE42ek11RkZNREtTVC9TSVpubTNkaWFEQURC +cVFxRm15ZTdYWGZuT2ZTV0FwRnJJWTh6TnVqS0FvQkh4NXdaL0FySDdad0txelk4T1N3eUdjKzVF +K2NtaGtuNXMzSnkyR1dBOUhiRVFYRkRBQUEvL1JKUkVGVUhQYjJXdE5NOGpma3EwYjJ5VEVIQzN0 +NDVRQVgrejJzQnA5YStRNDZjZWlEUFhSbWoveFhuTU1GUEdKdjQ4OGUyTVMxcnVIVjNENTVpbzF2 +ZmFUSllkaWVHVEZhMDFmc0k1aEJ2ZGdOdmF1RzRZUlBlUFVManBmTGUzKzQyeTlHNHVPWUtNV1Fx +ejMyNjBVNGNnanV2cW1YSE1ReER6T2NPVnpzNGNjRk53SHJMYkU5NnpIenYvclIxM2NPWjJMaVN3 +OGtmdlZaZlZhZjFlZjJhdlc1ZTZyUDZyUDZQS1A2ckQ2ZldaK1hGRUJoZ1dGTVFBaHdjZVlDSGlB +YkZWR3pwZmpiNEpPZ2hqdEozTmFlTUpCdXptV2ZvcmlIYU1RcmtwaVM0aU52RXRJNENzOGZZaFRh +WHpkckRFMmorQVFnV1Uzekl0REpCdzcrTkk5NHYvemdhL3UySUVNVEtBai85dm5yYXA4VlhiUENK +V2U4S0M2dTRHV2JZaW1JTlZqdFZVajdZWktUUy9IZ1dGRVBMcmdWVVo3NGxKdDFUYjBOTjhYak4z +SGxqQ3N4MmNyRlBCdHg4U2IvbHpINytYWFo2MjBHZ2RyclZ5WExDUzQxUzE2R3R5MXFveDlTTi81 +aFplY3VMMzFodnpsK0hYb09RcDl4RTRIQ3A5NHV6Y2FYdzE3OGJkcHBSbXR3Mk1NSGpPYlVoTERF +aEdOOUR5YTFaQ2NPSFAvdURkSE15OVZkYmNYQXRYOUV2bCtqbVAyNElpNjh3T2laYnhqd3hUOGg4 +YThtK0xDUHJaejFCSHhxTFQ0Nys5VTR1bURQWDU3aGtMYy9JQndJN0F4ODBNL1dZL29LN3VnTmZn +ZE85Vmw5VnAvVnA4cyt1YmxYbjlWbjlWbDk4bGQ5VnAvUHFzLzlHMUNHSmdWaGhCUk5ZQ05BbWc1 +eFNMTlIwa0N4OVJiSWZnVlFyRzNDYVJEMndBREtEM3ZGSlNoQ0N4SFdOS1Y5bWhBcEJDYk9IaHgz +c1p0VGNIZHZoOFNXbEhXTjdGa3VNQ0xmdk9JUkFUR3pmKzh2L3ZPS0d3WkRET3R3aTJ2Tk14SFo2 +eWQxV09IbU53ME44K1k1dUhHVytJcXRLSVo5Q3JIWDhDVzNVNWpyK3RDUS9NR2d5UGhUK01UVkVO +c1U0NWVkK0lxc0tUeWJGd01XUE1sOXhUMkRqNGhNWHQ2YTRFREI1YWhKeFRUSDV2QmxuSVBGdWhp +RXdiOStnTjBjN0RDSEF6NC9ldS9MZTVjN1cvUDZDYjQwZFBBc0h6UFBYbjU2UmEvWnA1ZndSTGg4 +YUY0eGZYWVJrSDE2QjJaOTl5aFFlNjNoMWo0NDFab2Z2UWdUL3ZRQ1BENzdpZ0I3ZnVFUVF5M2tp +VHUxbFN2dTZVQXU5dkhIVGd3WDNsYWtVMnUrMVV5K2V0Yys4M2tibE1OZFhJY1FIWWlkZUREN2JI +LzFXWDFXbjlVbisrcXorcXcrcTgvcXMvcTBEODVuMStkRklvcUZBSXZBUzBLUlZnQkRvQURtZ0di +TE1YSzlJVXFSN2RPZzdEUTQ4cEFJUEQrU1RMTnJKc210eURUV3hQQlROSklRdElmQnhGQTBReHhK +OEVsQTRzREhsd2FBVzlMV3hZUG5qTnZhdzJqLyszL3pleS9rd0NnV2pQekQ0L0o1aXo3cjIyUkRy +S0xBZ2tCNWlJTVRkdFkxcGptTnNKek55RDVGazRzWTlzQUVneUtLWjk1ZStZaG5qOWg4T3BCV29H +T0RKOXdxbkJ6NVZJL3dCcGY5UnBxWkhjeDQxaGg0NFF1LzRzT2JSampqdG8zb000ekV4QzlzYWdj +THJzWDE3STR2WDFGUUUvRjhqa0QzRUx2ekNBOC9jcFdYUHNLTmVGdXorU3dQZWFtclhOVk9mbUx5 +WVQrYjVNQlhCS3FwdllHUmszVUNjZWRML25MeTFzLzg5dGYwUGIvZUtNSEdqbThpWDM1bm5SMGJj +ZTFWUDMxdlRyNE9BenpoUWg5R3hId1FzRnpzVlR0Y09RelN4NFljdlVWMWdFWlhlNGdQWjhtMStx +dytIL21xUHF2UDZyUDZyRDZyeitxeitueDJmVjRVeUFZSjJzQUJZdGZ4UGJCbTA2RG0yQXNJREFm +K3FsYlNHZ0FoRXZPbUlZM0R0NlRzRjl6bFdYS1M0RXRzOW9peDVqSy9CUjd4d3JWaUhqd2FDd0Zp +OGFWQVNHR0xMRW1LbThFdWI1USsvdkFiYjMvNjkzKzBCUEFMRzMvdUdndFI4ck1tQjdtTFNUUnBW +SGY0RkZUVHdMWU5PM2h4d1g3ZkxBMW5PQ0lXbVBqVjVQeGFFMDhlc0NxMnZlTGtjT0J6QlRwKzhj +dUhPWHRodHQ4eklXc2tjUmYzWWovL3NKMzljajI1T3d6RXNFK050b2ttcnZqOEdYdElUMTQrcTRm +OW5qVzMrWWdsYSs1RWxLOEY4SWtqZU9TdXJtcVBEekVqVW52bHMvVWRQbUN5WDYzMGdIcmlsZC9r +VFNEcXBNL2c5c3ptY1BwMjR4Q25Bd2tPc1RROU8vT2U1YkI1VDkrSjVZQWdFREhnaGMzbHJhRWMx +di8wR1A1eDR6TWZ1R1dEVy94dmpnN3J3ZUxabWw1U0UzSFk2VTF2NTJEeFdaM1VsdDY4R1lPQlgz +YjQwaTgwVW4xV24vd1oxV2YxV1gxV24vYktwL3FzUHRsVm45WG5zK3B6L3cwbzhqZ1d6QWFGTjVm +a09XS3NVQkxkUWcxUW54WFlPbUxzOVd3b3JJWkFxRGtrSTBUVDhjMmZQUUFSQXAvSVFheDFZQ1dv +eU9ZVlRqeU41dG5GaGsva0tiWVk1ajhUNkhXZmtlS25jMzlGN0c3QWdTams4RW5FMy8yejM5azdn +YXhRaGtSY3VPUUFsN3U0R2dZMm54Vk9mRVhsMTE3WTkzbklabU5ONDhIRTFoNk5DUzhNY0JLOGRY +dmtnbi8zSElyRWdYZSs4Y1dISEh5RzZRei9FK0x6cTczdHdhZllhdU5OR2Q3VlJzNkpHNEhLVngr +WXc3WFk5bXRxc1dHRktmSEU1OGViR2ZhSkp5OE5Mdzg1ZVdhdjE1S0x6L3BOVFBXRjBWMCtlT1Bi +Z2NyZVBubkRvLzhjQ3FsRmVwU1E0TkJUOE1NQUMzOEVMVy84MnljZmZ1R3l4ZzRXUXVRZkR1c3dx +Yk83dzFNOC9ZWXZmTXFCdmY2QVR5L2pMd2N1SC9wZlBqalRmOFFyRHpVeTU0QlFCOXppY0RtWk5U +RTJ6K3F6K3F3K3E4L0JWSDFXbjhuRjUrcXordlJjZlZhZno2clBpeUFCQWdRSEF2ckpXdEtHWnov +QlM1YnRncG8xZXlXdVdkd0pRZk1aa2dRT1dLUUFoWkFVeTAvT2JDUkZZT1lVMjRWa3lZbXJ5R0l1 +R2ZjRFFjRWtKQmJjQkk0WXZwRE54aUFVdUwxcFFDangvZVRibjUrVjY0cVFyZTkzdzRGOFJNbURQ +K3ZzTk1BZVdCTVRCcGprQXBmbUV0T2NodUpQTGdieUZZYUl3NU1tTitER0gwejh5aGxIbWxsc1Z3 +NkZOQ1ZmcVl2TE16K3c0NFdmTTg2L0RWRGdGTjErYjBXOHRYREhMVjQwaDZaa2x3R3ZPYmxvdXRS +RmI0Z3JCeldVcjdvVFBRNEpnZ0RNODRjSEhMblk2eDExRVovUDVYVXVPY1BxZ3RQZS9HSGhqWXA1 +SE9QQnMzeGhaeWNXSGd5Ky9HTnNQT3N0dU5RQVI1NEpoajFPemFVK2VsWmVNT2w5dWNJaHByNkRS +VzIySmxNem4vSEQzL0o3dDhXTFhIQWdIbDl3NkdVOXlvZGZFb0FuY1lNWkh6UWlOMzdQSWY1MjdX +Q0RxL3FzUGpPcXorclR2dXF6K3F3K3E4L3FzL3A4Wm4xZUVPZlNqSXBnZzg4TXprK3JwOWxDaW5t +Z0VBTVlFVW9RK2NpUmZIN0NsYXg5Z0NKR29SU09mejc0U3JOck1QdjVzMGRqOFc5SXpoNCtOWmNp +aVhlS2MvK0gwdmRtRXN2K0hTTXlkZ2dUNjlQNWlkdHZDZFBBQmx0TmtLYjE1b0FkZjNKd3dTQW5E +WXNIdU9VaTNoWnRTRTdoMmRqTFArNFVOTGhnVWx4cjhVa3djc29Cd2g4dTJPRUtyM3pqUW15Q2NK +bkRsL1hVU3Z6SGdTTytyWHZXVUg0TG1wZ2FBWjQwMlJIMzVEdDhxVThFcXBFaVVQbHMzU2RmSXBh +RFN5TjY2MllQYkh5SnFaZmtvemsxTHd6NGR5akFwZTd5M0FOaDZtQy9peWp3cHJZUmovM21ZSWFG +dlo0UUt6MmFBMDlzZGNRcHJQRDdqQjgreGJZWGRtTElJY0NuWFBZUG00bkQzbDcrWWNDTi9mYm9V +OXpCcXk3bTVLdTJFVDFmNHNnSHAvajB4a3N0NVNOditjdEhmNXJYZzZlbnB6Zkh0ejlRNU9PcVBx +dlA2clA2ckQ2clQ3aXF6K3F6K3F3K24xMmZGOFZWSkkxaDBVK3JBQW9pUWNPOG4zUkRQbERXQUVC +NFNCRE1tbVRZS2lpQktBd0NGY3RQMFdKYS8welE1eCtzbWsvajJQdDRRRmlYcEVKTEJBR0tyTEVr +bTJaQ05KR2JsMU0rd3lZSFRab0JNM0xjK2N5YktZM2w3WTlCS0lxSlFQR3NLeUp5RVErL1hEWmY1 +SThnSTFCcnVNT1ZmWHpaSng5RjhsZnFtc09iS1h5TEtUWWZoSTVmallJTHZpS0E4SkVZYWNCenFK +d1JnY0lrTDQyWVh5Zk5uNWpFeXAvbU12RGwyVjQ5QWJkWW5qVWhXM1dVZHpEZ2p4aHd3NjlMajZp +emZOemxnME8xVUhONzJmTWxYL0hrNE82U2x3dEdPZHV2eHZacVhQN1RDMmxvOW5MY1dneHZtaHQr +UFVVWTl2bk1uMzN3NEJqdk1NdFI3N09KdUhEcXNnWW5vZW96V0dIVFYvekF3TmYrQVRUNTBvUVlj +Qktvd1ljM1dIc0FUUXo1dU5RSFZ2YkwrVDBmdFpOUDlWbDlWcC9WWi9WWmZWYWYxV2YxZVFhZXFz +L24xK2RGSUlFWlM1SURSZlJUdjJZeWdGaFJEV2dia1d0TjAvazF1K1lFOEJQNkVkeHRpNmNRSzdv +aFZnSUs1czZXSGZIeWJRRE52K1FScFpqSUpIYSt0am52OFlsTmM1azNDQ0lOREw4RElTTjdOQ25p +UC9qbTc2KzlJYllDSVlhNFlSVlhvNTVtdjY2UXhORVF5SFI1bG9jRGgzODJpaXlmRmZkZ3h1bUtj +eTd4N2JOSGMxdXpKMjgwMUNCdmlNVFZ1UEpaTWQ0YkpFMXRyMmZZM1gzR3A3c2h0eTA4c1V4TVhE +bndIQVlmL2VBcnl5Mk94ZE5nNHE5QUI3TzdKck5YanZ6amxJM2VNQWRiRGkyY2VidkdONDcwQXo1 +Z3hrMEVMeTl6L05panlmaysvWFhiZUREaHlmWGJBc1ViVy96S1NReUM4NWt2UTN5NXBkN1djQzkz +L1NlMlE4WmRqZUVuZkFjTVRQYXp0UjgzOGhUYm1rc3UrSWZGUEY3MUdudStVbHQ0YU1lNityR1J2 +eHJTaWxyaFpQK3dHbnQ1cTRQK1hIK3p4NERUSHliVlovVlpmVmFmOEZlZjFhYzkxV2YxbVpFOTFX +ZjErWXo2dkJBajhoUldRb2hqcEhHUll0NjZwbGVBSkF1d2h2WjJRT0dBdGlZeEkwM1BOZ2tiR2xN +TVB1MFhVME81c3hkYkhJVUlNUWhIcWtKNjVnOEpmQ2l3bjhyVDFHd2VCUXBIOWlMaDU5Lzc0bjNG +dUcyaEZWWThSVlV3aGN1Nm9pcU0vV3o0YzVob1VnU2JsenNzQ0pZZlBBb3VUOFZSRUh0UE0xMzMy +Wnk0K05Ia09PVFBzRmRNWEhnbVBuZU5ZQTR2R3RqZFB2NE9GL2N4QitIT0R6WU5vQzZhM1hlMHhj +S1BYUEdzQm5KUloxalZ3bDQ4NDFNT01NTERGaCthTWJ6c1lUa0NkYmR1bjZhVkh4N1N1TERDQTcr +K3d1SHlQRml0YVZLMWdFbGNsOTRTbXkvMndRdWZSZzkyUTEzWTgrR0NRMnk1SjErK1lNT1ZYT1dR +R0hyRUpTYzlvQi9aaXV2S0gyRHd3b0ZITVdFeHoxODBwRGR4RXIveVpldk4wZmJQOUNwN0Y1d3c0 +a2FkK2ROM2Vzb2ZLTlZuOVZsOVZwL1ZaL1VKVC9WWmZWYWYxU2NjcjBHZit4VmNBUUJBbElKb01r +bEpnQk9nTlJObkFnb01LQnRnT0JiWUdtSU5qY0dPTDhud1lXekNBMUF6aSsxWjdEUWZYOGhITW9J +a0RYd2FTZ0xXZ0VmR2p0LzhlckhDYmswOFNScWFGK0VPSEQ3ZXZQdk81bVR3THk5dkJ1eExJL08x +WS96Q0oyYUs2MjZPMkRTQU9ldXc0ME14eE1hRGRibHBJbm01ZXpPQUsydmk0a2x6eXRtYW9ZSGtR +cFF1ZGNFZmp1d1ZFMGFYOVRUMTR6REhKNTdsSlU5dktNUnk0UlJ1QnpRQmlMMXZMVVpVRGpKN3Ro +OEdDMXM1d3lxbW12b3NKbjV4SnA0YzJlQnlPWjRlNFpOOXNPY3drUStCcXE5bjNObkhqN3E2NElV +eHpjeS9Ic08xMkQ0dloxTW52WVJQK2NwUGc4dEZuempROFdSdTZ6dzU1OER6cHRGZWRXVUhpOXh6 +TU1IdU9UcVFFMTdaNG9jLythZ25ITzY0MW12eWRiY1hoMkxCemFlODlRT2IxSjhmTVIzaW51R29Q +cXZQNnJQNjNOeG1WSi9WSjUveXFUNnJ6K3F6K254bWZWNDRDd0NnSlE4NG9oUURBUkx6QmlESnNU +ZXZrTVNBZERhS3c3SGc5aE0wUWRyRHhyd0VIQWhzRWF6Uk5iSFlRSnB6Rit1QXZhMHZpWW9CSDM5 +SVpYdkcvYmQ1VGRQWWE5MFFEd1pGOC8xa1A3WDdhM3J6Qm5KOER6bEZJbDQveFd2WUREN0ZoNTg5 +ZjNnaFJCeUlpMmlpMGFpZTVRT2JPTjV3NFF0dWIwcHdHN0VwTWg0MEo4eHFJRitjV09jYm4zd3BM +RDcyZ0pvbTFjRDg0QTFYL0dzZSs4VlhkUFZocTBsZGNoUExnUVdUWEdDQUdZZDdJRTNNSE1KaXBv +SHd3Rjd0TkpoYytNZWJIb0RSR252Y2JPNXpFUWtoOHFVMllzTXNSejVoZGRlTWNzRGpOdkhzY2Zp +Yng2RmhIVjl3ZUpZLy93WU9JZ3A0M2RYbjQ1LzgrUXFYbUlnay9hR1g5S0tEZS90dGN2YTJpRjhj +NGQrelhMZlBodHY4UVlZYk5ueUlDVCs4YlBqMG5YZDZzVTlzUHNTUUQ5NzVjT21qSEpKNkZDK3Ju +OG1YVDNsVW45Vm45Vmw5VnAvVnA5cFVuOVduZFVNOEdLclA2dk5aOVhteHNJa080Um9SVUlZYWtt +TmtLMElFNnM2SjVBRUFWSEI3UEV2SzRCZlpHc20rVFhJYUFGRUVJQ0ZFSThLOGVDSGRmUnQwOWlx +eUpNUkl3Z1NGYlBNNzd1UnBMdVFwNGhubi94ZGtUaU1TcUwrbVA0MThmbDAwRXZoRGlBSWgvQlQ5 +dkdHQ3cvNklFMGZtOE1WT25vb0NDMnhFSmpkN2NHUm9SbmpGbFpNTFh4cGJrV0hUN0lwbUVFd09I +RG56cFpqODJDZW16NHFOTDVqTXI4ajhkK0lTQXM3Z1ZqTk40RER5dld1eFlMQ210bnlkdytIOHhq +VjFFeGMydUlsRVR1YkVkSW1SeGlaK05ZWUYzeEhPRWRiNWR3WnM4U2VYWFp1NmlpdFh0Y05GYXJ3 +Q25UMzdqNnZIUDJ5NHNRNExYQzU5QXE4WTV1R0FYUy9Cd2EvYUVxRSt4aW4vZVBWc2pwakV3b1gr +NEZjZDVRQ3Z0ZTB6aDlkd0JnZE85SWtZN3JoeDVRRDIvMFBpUXl5MWdWR04vU041Y2RuUWdQajRp +RDdVTUllQVN6N1ZaL1ZaZlZhZmJLdlA2bE11MVdmMWVVYjFXWDArdHo0dmdnbHNRVEJBT1FHT0E0 +VmdJMmxyaUFCTVVFa29QbEk4QXhIUm1PZER3b2dGU29NU3QwdmlDRlpzUXd6UGlyTWszakZKaEIr +TkpMNURBS25zMXFlaDhRY3pER202RFBIbEpWa05pcnpUT0VjNHhNd2Zvcnd0MDd6bU03WWhZWjQ3 +Rzc3a0tIK1h0MEt3YTZROUdLYVEvTnVEUDUvbGdUOURjNlNKYzlqQkpqWmJRODV5czhZM2ZqV0Fl +WnpoazI4K1hLbWhabmV3cUJsY3NHcVExTStjaHNVUEh5NTg0VEs0TllsNU1aTzNaemJ1OEtzZGU0 +MmxIbml4RGg4ODZyV0gzQjZFNTM5STYxQ0JRVDNVaWkxZWlNK3pBOFM2UE1RdzU4REU5VGt3eno5 +Y0ZnOHVQY0puY3NhOUN5WkN3NThteDZ2UCt0YUJJTjg5T1BYTXhNRUhYM0xERDB6OHNNV1RYb0Fm +SGo3Rnd5ZHVjMWppUXg3NHMxK2ZPUlRneEtYYXNmZVZDejZTTXh6NDBEdDZBamI4T2J4Z2wydHF5 +eThmYktyUDZsUE01TzI1K3F3K1BWZWYxV2YxV1gwYTFXZjErUjlkbnhkdkVqZ0FRc01JaW13Z09H +Qm9Yb0thdzArK0VoT0VVMnVjQXFtNTJSamV2Q2dVVXQzWkNtd3YwaFZDQWd2bzN0QnNrTVZlZkov +RkFSeng1dmp6MTg3SXM5ZmdGMmJZSll5MHZDM3hHWFp2SUNUc3I0bjVOR0NCMmNGaVhXRVVkQnYy +UHVEaWcyODQrSUNQUU9DR0Evbjh3SWd2ODdCcHhHM3c0WVJRak9RdkJwd2FBejZOY2dSNi9uOVYx +dVV2Rms0Y0J1cWtpUlhkWjhWbWd4Zng3RFBZYVpEd0tFWUVLajk1RW9SbWdsRXRVbXQ0a3pQY3Vj +dWZEN2h5R0dncy9ZQVg2NFFFQjNzY0x4ZFRHM2hna1NjZjF1SEhCYTV3R2Y3NGhvc3ZuS2czc1Jo +OHd4OWN1RTh0Y2JLSDVmakg2L3I0Zi85M3hhbTIzamJoU2gzMHJINFYxeDZmOGFRUDVCNU5xSlBZ +NXZDRDc4eXJPWDh1YzNKVk83endHYjNBb3k3NDlvWm9lUnBjN01Ybk13Y21iTnZUOThQU1hQVlpm +VmFmMVNmYjZyUDY1TU42OVZsOUdqNVhuOVhucytyeklnRmtTQWhKTmdERU1VZWNLOEltTlVEWWNB +d0lFTmFBVkdpZ05RT2dCSXBjeVNDZWplR2VBbGtURnlERlFycGltMzhVb0theFQzTWdpbSt4WUQz +anVvVzFQNFU3elQ2Q0dGeml3dytQZlBrdytJKzRFS2hJM3Rid2xjRWZMdUJLazhrUkwvenc2UTJU +NzU5cmZNUzcwc2p1K0VJMjBsMXlDVTRGZzBGOGZCdmJ2TFBYcFJac3hNTS83bURSWUhLRFJXR3Rl +Vk5pV05QSTh1RlgvZzRXR09HSFdXell2QUVSNzR6clMvT0w0VnJCemdXRGViMGlIL3ZFMWZ3NS9Q +QXBUL2xGbkd6aGdRV201TXdQSHdSbUgyeTQ5Nnl1NWdrRkhqNXdEUy9SaXNGT1BZNHd6ai9HVDg1 +NnlqcE9DRlBPTU9reGQzWXdxSVczTm1MemlTZUNGSU1QK2VKRy9xa3BYR0xCcFRhd2lzVkdUbkxt +WDQveTRYQ1ZKd3o2aEU5ODdOdlMyU00zdmh4MitGVFRrOU50OTFhZjFXZjFXWDJ5TWR6aHFUNnJ6 +K3F6K3F3K3E4OW4xdWZGZ3VTQlYweE5JYURDTTdRR3ZDYjJ6RjRTZ2d2b3MyUjlCdHhlUTFCQUpH +Qy9aQXgzeFBLUkpEV0d6MndSYUU0VEFRdW9SUGczenk0TkoyNkc1NndUT1BJTlBpWE9IbGJmRVpl +SHdTOVJFYSs3d3ladkJ6TGcwVlQ0a044K0QyNThPUVNJaTArK04rNFVLRTJuTURua1lOaURpUEJt +TDd6ZVpPQkk3UE5XYXdRMmhXTnZQK3pMMGVCa0IxYzRzbzRmQmVkYnJDM3M3R2NMRnc3bHpUZk92 +QUZ4UU1uRlB2bndyZkhPdUs2dHhoQTNkV1hMdjlnT1p2bkJpQXVjaWE4aDdkRkQ0cktUTHl4aVdO +ZTRiT01IL2dnUE5qMkdIM3ZrRWNFbEpudUNrcHY4SFNCN3FFMmMvRUdnSitHQ0dSL0V4OTVuL1pF +L09PVEZyemVHM2pqQ0JZT2NIVGhzN0dPM2ZUWjR4RmNEdGJZbWZtcWdKckR3cVo1ODZYdTU0UXJY +M2h6aGxnM2IxQXh1UFlnWC92WWduejZ5TGxiMVdYMmVVWDNLdWZxc1B1R3RQcXZQNnJQNnJENmZW +NThYWXZHVExWTDhoQTZNQWg3aFRPNWppS1J0cGdIbk9jNzlKQTBzUXZnQlNFS0d1MFlVRURqRk0v +ekVMUmtFS0p4bmdNV1A4Q05leFJJL0JFZ0VObGo4eEwwaTJYRitMVFFiU1NJN0FyVlhvVjBJVXBB +dDZnelkrRUcyM09WRGJOc2c5d0dqSFBFaGQ3a2dtUTlOSTI5TndML0NhQmI1d0tHSjdFTytmRFNq +L09XQVp6ajV0NDQ3L2hSTXp0YmxJeFlmS1I1N242M3g3NDRQYnhveThKZm1ncGQvZVc2T0kxRDV3 +RzBPYjJvSnQ2SHArTFd1NmNWVEs3amtESU4rWWE4ZXZzY3ViOC9zNUFTelh0a0RZMFFQbSsrTkV5 +QTg2c1lIbk9tSHZHVVNVNjdxUjB4eUl3N0RQdlZqWncvYllNR3ArdTVoT1ZqRVVRYzU4aU1mT09S +dm43anl6aDU4cTJONkM4Y1JHRHpwTVJkdStlUkhYZG5xTXpqMHp2YkpYQkc2dXF1Qi90cWVHRTdG +aHgybllxb05QczRmRHQ3MG5mOWhNOXpWWi9WWmZWYWZSdlZaZlZhZjFXZjFXWDIrQm4xZWtBczRz +QXJuR2NFcnFBbUlaRVV6bDJaQW5nQytoeTBKUlVHK08xRVprZ0k2eFphWUlRRXgyQ1BTTTVEdTly +cUFsSHpFem9mR3NJY3Z5VXBlZ2hHaWRmRVV5bHNMUDVVYk1FdGVua1NrQ0JHb09OYjRWSFI0L0pV +Mlh4bnlVQkJGUUxvY1UyRHg4V1kvYkdJUWwxellPcmprb0pqMnc2UnBjQVlyWHZIaXJRcmI1TUp2 +K0ZCTU9iRGpTeDFTWkRWeXdYVEVNQ1diK0RpR1dkUHhpdzk1d2lHV3R4Zm1ZY1NCV0E0SGUrVWdo +cHhnaEdFUDA4RnZ5QnNXSElZM1dHQ1RENS9XK1NKT1F3ejFnaWQxalVCYy9LZVBYT0xpQUU2WVBS +TXBIdndqZXpGaEZBOXV6VXlRNXVYcDBGQVBHTVhkUTJscXl3OS8rOXU2SmkrZmlkTmJKMzJqLzkz +NWNaZUwzRTd2WHM4aE9mdHc1MDBpZXpad3lZRmU5bTNRekJGNWNvYlJaNGNVdjdqa2t5MXV6TUhG +OS9hZUEya3VhOVZuOVNsVzlWbDlHdFZuOVZsOVZwL1ZaL1g1R3ZSNWtWaElsNVFHOG95azB6RFhM +UVF3eUVTZ3pYN3ExUVN1TkMzeGFnNURFUUNYbFAzdUJoOGFETUVheDAvRllnQUR1QXNlV0phWVNS +cHdTU0EvY1loVVl5a09HN2JJUkFneUlsQjdVemhOZ0RRNTd0cVE0UTJCZGVLVkkrSS9lL04wbWt0 +VHB5SFlLZ0JCS0RCYzhvQkxrZkFDbDFoOHlZV0ljS0JweFpZamJ2anpESzhHT3MxMk1NUG13aHZi +N0pGZkRpenhYR3A0Qk9ITndua2p3eWMreEUwendlbXU4VFV4ek1sRFRXR0YyenpNN2psZzVhL2h4 +VlZqZS9oU0M5Z1cwL0NtYjZ6bndEQTg0NThkWDJvbEZudDlBci84ODh3SG5naEx2VklQOC81UUNB +ZHl4Qm4vL09wRC9lZ3JCK29Kbzl4OWJVRk10V0hIQis1aDRFTjk1ZXZ1Z05KN09UUzI5MllOdjN6 +SURWYjU2RUVjcTVIN0Ntc0dBUk1qdnZFbEY3N2xJNzQ1dWVMVG1tZXgrQkJQUDd2a1ZuMVduOVZu +OVZsOVZwL3MrRklyc2FyUDZyUDZyRDZmV1o4WERhYVpiUUpFNGdJalljY0VOUStVT1Fsb0FuczBx +OCtLNXhLY0FEbEhBTUlrcGJIY05SQlNKQWtNRW9FenhGZUl2UVlrdjZjdzE4WEVQd3o4NWlmd05C +Ynk3R2UzQWgweXhGbS80d3NPVFlsMCt5SlFCQ2tHSE40Y1daUFRPVFRPMklKTUl5SmRmdGJ4SVM0 +Q0Vhc1ltc2M4WHZqVktQNmZUUERJVWJQTFJVNXlnTmZkUEZ0MTJIeUdPejVneFpsTER2akNvM243 +K0xVM1BMOGNWak0wTEZ3NGM5Y29PTUVCUWVGVzA4SzhRcnB6S0w0NDhOckxWa3oremNFQmowYkhJ +ZHhxQVpQY0Yrdmt0OXpmbTVWL0J6Mmg2Q3Q0MlR2NFZxVGpFNjlxUzV4aTR0Kzh0MEhlbk9Sd0Y4 +UGJIT0pnSi83R0dzNnlueTlmQlZCTEdOVFhJZXh6K282ZHcwSU8xc09WZVhXR2xSK1hPc0VyRDVq +c1lRZVgrT3F3QWgxLzZVZjlvcWR3YkExR0I0MDVuOVByZU5Rcjl1SUZodFB6VThmeFk2MzZyRDdE +WWZWWmZjSmFmVmFmMVdmMXVYN0hWL1ZaZlQ2clBpK0toRUFiT1U0VEtFakkxUWlBUzA2eGJBUjJD +elJnTlpjOUVrMXpDNmdSQkJmRC9BcHA5cVVCL0tUT3AyRmVZMHBtRDRUQmNuemRGaHNNQ213K2pZ +Y1FlOWpBVGFDYTVGR0UvTEtOdURYcDJYUCtmMFR5VVNUejhDQVM2Um1LeWtaRFJFelc3U1ZRY1RX +TU9WeUk2eUlLZi8xdERsNTI5ampBNEpCM0NxTndjbzVZQ0JuSDIxQmo4M2o0OE1YV1p6NXhMN2Zr +aEJNY0VnNjg0c3ZQd1NJL0hLWUoyYW1KQzRkd3F3M2U3ZC82RFU2NUV4UTg2cXlKNUU3MEdoVWVO +WWJOQmR2eGVRNUIyUENxTHZ4YnQxODhzY1N4em85YXlabU43OE9yQzUrRy9CMmtCRWc4NW1FeTVD +Ukh2dGk0d3l4M09OVXRZck5QbitES3VsenhsQVBQZmxqTXM4dmhDVHZPeEdabkxWcnhUQ3ZzWUpl +ck4wSmlFaUpzTHAvbGpWZTIxdmppVTB6cjBaMjE2clA2ckQ2cnorcXorcXcrcTgvcXMvcDhUZnE4 +QU9FQlVScEZzUmt2eVdOa0VBV1FhUzZKQXlXaHh3c3B4R1FnbVc5M1NkZ0RFSkw1TnErSkFSV0hr +QUJYVUVDSkF0aDlxekpyUHZPUGFEaUpZcHZ5M3RCSUZ4dFJtdS9zUFFLRHpUNTVlTU1BaXlFZitX +cEFZbklwWnZZYThDbCtDZ1pIQkFvdjM1cExmR3R5NFZlUkZjbis1SXhuKytRdXBqV1k3ZmM1QXNX +VHR4N1cxRUx6MjBPUTduSVV3MzdjeWlFNWlROFh6UGdTMno1Y2EyeDM5cDdadW9pRm9NU1RwN3Rj +Mk9XQVMvM0VzMjdnVXkza3hzNTlhNjZtaEgvMzdUT2g2YUUwdVhVOGU3T2xsdkN3NHpNeHZIbHpx +UGlNbC9qeHhnZmZjaFBYNFFLck5RZVFmK2NnVHdjSlhnbERuZURqbjIvNzVKUURSZ3hpeGJlY3dv +VTQxdVJ1ajNsN1ZnZlRXeEdXdXFpdm1OYjBJSnpxcEI1NkFRNTI2dXlld3dzMk9CLzdSRTc0cXo2 +cnorcXora3g5ZzhGODlWbDlWcC9WSjJ6VlovVUozN1BwOHlJNG9JQklVZ0tjS3lvamphTFltZ1JB +UDBrVE0zSUJzY2NhNTU2UmFLUXhOSk83b0FGbXpUeFFFbEFzKy9aNVFMR3hKZzd4c2JVSERqRWxU +bkRieUJLWi9ZL05xK0JwV0lScEdrM0doOEpwR2tOemFHSkZsVFBpNVgvaW5pRTNlL0VrZC9GaDVS +L3g1alVHTzAxdElCNkc4Q0VYY1NLK1lMSUhMM3dxMHVJYXZoV05EODBzSDQwdHYyMkdlVllyYS9i +emd4ZTVuSEhkR0JHUDJQSkt3OGlWdmIvK0YyZGpEaTUzemNFblgybE16Mkx3cVNIRmhWbWQxQ0ND +VDVQYmh6ODFzWWZ0SG1LREJmOTg2UjM4YVhiK3JmR0RQMWh6K01DSVI3alUyZDNCNTlwNjNIMFpu +aDEwZlBqK1Awd09Bbnc1ZExjVzB4OTRJQmExbEwrNjRBcFBlQkhib2VCemVsZjk1UUV6WE43OEVI +UDZVbjYwd1I5K3JQTUJCOHppT29EazVsbmZzSFBuQTJiNTR4QmUzS1lQNUZsOVZwL1ZaL1ZaZlZh +ZmZGV2YxV2YxV1gyK0JuMWVKSWhRZ0lDV3VBUk80cWZZTmlLQVUrdWNJbGN3ZXp5Yms0aG53eDV6 +RVUyQ1dsZkVKS1lSalFpVWlKRUNnNFl3N05mVWhNQ3ZKcE1JYk94ZC9HcDBoTUFobHNFWDR2bERo +TC8yUm95QlZQTUtsTHdVN09UZ04xeWQvMGVUV0M3Tm0zdzBsaXNIaHlZVFAzN1R1SGpEcldiZXRj +bGpmVTRlOXNLVUJ0ZFFCbng4dU9OazQ4ODZnZUtNVHpqZ01jZkg0WEV3ajloeXlPSEJQazN1SWxE +TkNrOTRJQ1FYZ2FvNS9qUUdiUEpSQTl4WWgyVVB2b2xocnlaVkM3bmhHUTc3NVhqNFAvK1dRbjdp +ODVjKzRsZjk5QjdSN0lFMmRlQ1RHT1RzM3hoRU5BYS9SSWhyUEZoTGo4RHcwWHRmM3ZvUmlCekY4 +WmxZMk91N2lCc3VlWWducnBySXhSMUhlSlZMNmlkL2ZXRXY3c1MyTGlkKzRZQlRUL0JKa0hESVF5 +MWdGaHV2Y3VZcithY1AxQXFQZTJoTzcvRlZmVmFmMVdmMVdYMVduOVZuOVZsOVZwK3ZTWjhYaGh3 +cnRxQWNBclFKekdBa2dFWUdCR0NGWjhPWnUrVFpFR3ZJbEFoQ0pXQk5ZZE1FQUptelQxTnFFR1N5 +WjdjQ0haTEV0Z1lqVVVnYWNYN3lKbGJDdE00T1h0anNoV01QaEJueU1vKzBUejk1LysyYjczL3BS +YUFJVmhRNVdOZmtPRGpyMS9XdFlQQ0dQQnpBTHA1YzhJVmMremJ1N0xHbUdPYlk0bUlQalJueXhJ +bkdrWXU0Y3RFa0NtYklTNzY0RVp0ZE1PU3paN254TGI4Vjl6U2NBWVBjK0dSTElQalVNQm9jNzJL +ZWVOZXRpOXpUTEE0SCt6UTkvUEN5eGJQNkxPL0RMeHV4eFZKM1hNalBsWUVMc1pOamJNM0Q2Wm1B +Q0RTOUJ3dU12bXFBVTgwdnB2anFSZERpc3QzRFlJYisvTVVQdjdyMUlTRFkrV2FuRmo2TGpXLzc1 +QW9EVFBoUjJ4eVU2aW0zOUtqOENRWXYxdlVoSDNwRGI5ckxsMnZyTVpoeDdiZVYyU3V1SFBuSGpR +UElIbHhhZzltYVdQZy92WHZidkt2UDZyUDZyRDZyeitxeitxdys3YTArcTgvWG9zK0xnRFpvR0k2 +QjBFeHBVZ0MyT1lhOENOUWVnYzFMQmlqUDlxMUFwMUdzQThDblJrSXU0aldlL2E0bGYyd1VDbkJy +NG10OGZtR3dEeEhBU2hweDNuUkkyRnNJaGVORGd5SXRBdFYwUmo1ckFJM2lyNzIzbVdjc3Zzbkh2 +S0pyUW5aaVpvUkErWWd0eDhVemUyR1dtM241dXlOWFBqREtZVVY4ejlld0J4NXhjV3UvcHNIL3Zs +V1pTM3dOQkNkdStiVXZEU0ZQQmVVSExoZXV0ckIzVHNSbEF3TjdBc2x2MklKVm8rTlVqZmN3bXIw +YWJHc3dUUSt2L1pyVXM5cXhoNDJ0bnVGYmc4Y1dOL3JFSGcwR2h6ckl6Vy9NOHRaTUhyREpEV2Fj +RTVDOWNvWFZrTk0vdnY4bis1bDlEZ1U1cTcwNyt3aVUzWnQzMzFrcy9qOUl2ZzZnZHJpRFRlL0lq +WkE5cTZrNkVZVzNONm1qUTVXWTRjRzFYTmpDbWdNTVh2SDBsczh1R09VaWYrc3VmTXRYcmprMDRP +UEhYbmR4K0xJbXZocHZyb05OZlBiVlovVlpmVmFmMVdmMUtRL1lxcy9xMDZnK3E4OW4xdWVGSUpD +dlNSQk9ZSjhWNy95UGFqbXpKbUVBN0VFT0VoVUlXWnhiRTFRQW55WHY4cXl3aXNvbXdnQnFoVFJO +aFlRVjUxenMrWlFZYk5iZzBXVGlhbjdQNGhpS0pERjdZSVZMTEFQeDRpZ3FyUDRodHZpR2hwU1hm +QW5VUG44dExuNkdBaUV4RGFUWU9GbXNFeGNuaWdyajJYZGJ6SnFSYjNiOG5xWTlXSEdRUWxuSFBR +eUduT1NkZzBIeGNHV2ZCcFBuTnUzc1d6OXp3WVdmSEVvRUo1NTg1YzYvTjBONEV3ZFBHalZOdFcr +VzVvSkgvY1VYYytOT0hISFppYUVaRFRaeUlIaTVxak51M1BGenhuWHpVUlB4NElZM1RZc244VFF2 +WFB5eGtRZk9ORGplMVVrOTh3ZkpwNSs4djNsWnc1R0IrdysvOWJuRm9NWnN4Skc3QTFoOC9qMnpT +eDVxaHh1ZitZYURMZDg0eEhmNkQxL3c2NVgwbWJ0YzVPR3lSeHdjT3doODFtOTdxQXlmdUV6LzJK +ZVk4aEdIajlSeEQ3T3hxejZyeitxeitxdytxMDk0cTgvcXMvcXNQbCtEUHZmZmdKcFFUTTQwZ2lU +TUcrNFJLSEFDQXkwQno1eHZVck1IdWVmTjBtMEx4ZDVlUGxJUUlJa21qUWE4NGlQSUhDd0t3amV3 +aXV6Wm5KL2srVVFvL3hHaEdMQ3prNlRQZnNMV2RHTEI3aWQySkNCazM2VE1zRi9lR2t4QitmV2Jx +ZXpQaUFBak1yN2dsQk91eElOckQ2ckp6VkFBUmJaUFFSU01IN25ZWjc4WTF1U3RlSHpBdTlqbWJr +MnpwUW1KeHo3ejhsUmdlT1MzalQ4eGNtQTU4R0NEaVlEa0xYK0hHbHQ3TkRST2NiLzdabytHRU1N +RnU3b0dKMXN4VHg3bnQ2L0I0VzNNTnZQc0NmOE9ZN2x1RFdid0pUNE8xWTBmZGJidW1VQTE4eDZB +ZzlsK3R0a2pGL1p3d2cyWHV6eXNHL0wzUmtsOUhiSzQwNHRzaVZSdDlDQ2ZQL3pyLzdyMk1NcE43 +Zm5oVTc3eWNhbVZmSU1obk9KUjNmWUFuams5TEJlWGRYbmcyVUhCbjN3V3c3MXUxbUczajcyM1dY +clF2SjZuRXdNMkdLclA2clA2ckQ2cnorcXorcXcrZmE0K3E4L1hvTStMQUlKSlRCTjRCdGlpWnRG +c3lCY1FVSWttQ1hOc05RQWkrQkFVbWNDd1V6QXgrRVlRZ2ExNGh5Qjd6UkcwZUo0VkFiSDJ1Uk9S +dU5Za0lZYUV4QmZIZ05rbFFYNDFMdXlhVHp6a0VBQ2NpbkQyblRjNTlzQ3VpYnhCZ1NsTmFDRHM1 +VkJ4Q013elRzUVRTeU40WTJDL2RjTTZQK2I0TXUrdWdCR0EvZmpoVnlFSlNhNFJLR3p5RmtOUlhY +TEJwVGw4K2N3R0YvTEl3V1BnRGpZNDhPMFFnSk90ZldsZ01mRkJjSERCRGhlL2ovaHhhWTJJRFo5 +eDRYdnA3c25MWHJYVy9LbVBtQTQvT1l2TDd0VDl1dmZVUmt6MzdaL3g2YnZvOG9RTmR3NEpObkxT +WDdESnhSRFg0U29Xd2NQT25nQThzK05EcmYxajdsUGowM2UraTgrR1Q3bnFhM1pxSHo1eVFJbnR3 +cmM5Ukx5Y1RNMk1jRTI4N3RiWTZGMkhOZzcwSUI2c2llVVBOL3pJMlZ6KzRJbDI3S2srcTArNDBv +LzhQdUt2UHF0UHo5Vm45Ym45TXo2cnorcXorcXcrL3lQcmMzOEE1WlFEWUV4S1V2RUUxUXljV3Vl +VVE0NkFjd1dzdTgvSVJicWcvQUlqQnVFS0RDemZDQ01JaVJPbHVBalVaQW9vcmlhRkNabnVmaklu +VUxINHQ4OUFKQnRrSXhOZWZ2aUd6VnNCYnlBVVMyNXBIRTJtSUFUUHJ3dGVzVExrQWl1ZkN1anlF +NzlZY0NvRUxMaGhvK21JTG56SUF5YVhobkMzRGdjZTVlMGY4OElXZ2NMdDRvOFBkL0Z3ci9rOXU4 +TUZid1FiUGd6NytYVEowWGZVY1FDdnVBNkVGY2x2ZnIyOEowODhpb2MzTmprazBxemhSajN4NVdz +RWZNSVI3dFZicnNGalA0N2xUWUN3MjYrL3hJV05JSENoendneVFzTXhQeTRZOUpSR3QyNXRoZWFO +MlBSVmVDUU1oekxmRWIyNnNYSDNYWHJ4dHY3anc4R2xmaTcrdDZZVEEwWjcwcnZxdnIwM2EvYXBq +VnoyamRnOUYzWjQ0RU11MWwyNHNrY3RZQWgrZHJEd2l4K0hRQTVhTmFnK3E4L3FzL3FzUHF2UDZy +UDZUSTlVbjlVbjM4K3V6LzBsUkJZRVRYR0FGTWl3emhFbkxvVVF3TE9nOXJFbkhvVUhWRUlJQXBR +dHY4aHdBU1FSZXlTQWVQYkFKS1pDeEJmQUxyajhGTTJuV1B3bkVZSm40NmRyZU5tbVFSVGRRYUFK +a0NKdUJCb0J3MEZrY3ZHWnJ3emttZ3RIaXFnZ09OR3NpcVNSTmFEWUJLcEI4V09OcUhER2h3RWJ1 +OHdSbGpjYlBzTkZMR0xpeWowSG5keFNaRmhnd0tzMUY3L2h3OENKWm9VRGwzNnptc3NjVEpwV1k1 +OXgzVmk0WFM2bktXRVVUd3cxVkJONXVSdUxiZmpRM0RDSmdSdjI2cnUrNy83bGxuNFFGM1pYRG1E +L2NGNE4xQ3I5b2NhKy9nQUxHN1V5N3hEQkt3N0V0UzV2RnovSlV6M0VFTk1CRFpzOFlHRm5YWC9Z +N3cwVmZHTHJrZUNWcTN6RTlvd2JmTnFyYnV6NGdFT3ZzdVBQWjc1d0xVZDcxVkdmd3lNSC9LcS8z +dEhYL0lyRkx6OXF3c1pWZlZhZjFXZjFXWDFXbjlWbjlWbDlWcCt2UlorWHZBRmdvTWgrZ2dVeVJo +eXlFUkJwY2Z6cC9FUXI2VndJdHFiQkFGRXd4Q3FJdmNoQ3NKL2V0NWdEWGdPSWdWeHgyU2lxWmtT +bW9TbGdBQjVHeVc5U1E2cTlCcXdTVTJEWTJSdjh3Z1EzRXF5eHlVRmdQVVhSdE42Z2VFWnlodWZr +YkEwdXpaa0RBVGNhQm41ejhvZVpYM3ZNV3c4MzVzMzVyRkE0OXhVRnRoRW8valFEdmpRWHpMaUUw +Vjc4YlNQTWMzSjJPWlF5TklaWXVGSXJNZEs0bW9VdlF6eWM0NDlmdVlrZDNPNGFNUWVHdHpmWng1 +NUE1UTJIZzBKOThCS0J5aWtOejVlNDh0UFkrR2ZyZ05JWDF1VmhYZDk0K3lSdlB0VFlYVzMxMng1 +MHc5OWpMRDU4UDk2YkpiajBEQ0g3cWdNTW51MXpZT3NKZGJMZm02VWNJSEtXaC9nNFVWOTFFQnZu +MXZRdFA4dkw4R3dmUHl2V3ljRWV2YXJIemVFTTkyejR3SnU5MW1FUlcwM01iZStOMkhHRDArcXor +c1I1OVZsOVZwL1ZaL1ZaZmJJM3FzL3E4OW4xdWI4RlYzTklUa0VsN2JPRUdVblNtd0NDME1EV1E3 +aG1sWkJMSTBoSWswaUlEVnY3K0VXNm9pSlg4dlo3RmlOeHR1bW13SnJYSGdPSlNPRmJjZm1FQits +SG9MY3RudjMyd1NReEF6bjJScUNJWGVFUEFXZmNGZ01iZTd4Rmdrdk1pQjgvWWxvWEgva0k5MU8v +NWpFdnRubjQ1V2llMy9DWnhqRE00UmMvaWdtenVEN2puQkFVWFF5eE5acTlFVkJpK213L0cvaVBR +QTltUXcwMFFONXFhWGFYUnRpNVdUTndqdytpNUV2dU9RejRsQnYvMWduVXRmc21UellPRUp6S0VT +ZHduUVB5L0lZd3RaRnZCT0J5RUcvY3NlRlhrOEtVK3RrRHB6ZEVjc2NIWDNMUzVEaGtCOThLMU1F +MDNNa3BZdWRUWEJ6SlcrL0t3MmZyUHNPTVo3MmhoK1h1TUdPM1lybHorMWdUK0RmKzFNSis4MnE4 +T1ErUDhyU2VQeERNNGV6eHdPRFRIaUkyNzlERVkvaVdLNzl5aEtINnJENnJ6K3F6K3F3K3E4L3Fz +L3FzUGwrTFB2ZTM0R291aGl1YVNaS0RrRWRZd0ZzSEhtaUoyK3p5WmtnRGFINWtjU3hSTmhySk9u +OThlVE1qRm5FQlpJOUNBYVN3ZkxzcklMQVJyK1FsQm9lQ0ViZ0M1azFQQ3FleEZJRHRIakRUUkdJ +anl4NCtIU1RaSjY0NU5nckNKN0kwMmhaOUJwOXd3b0FidnVVaUpxemJWRk1zL2xlRUV4ZkpHZ1Nm +N05QMDF0anQzT1NxNEo3WlJzRDh5bCtUeWdtbUZlTWNYSERnZGZkUDNmamtRMFBnZXZPYUdJYTl1 +SmNmM3pqMzVtUVB6c0VjZ2U2NE4xRU9SODBEcDdwdXJTYy9jZEk0UnBwT2s4R0ZRNzNDYmtVOFBq +VTJQUGJ6eXovTXljZTZHam5FOFE2elBQQlBzQTR1dWZMSm56MzRZUU8vQTRJdEg0YmVJajYrK0ZR +ejNQdEgyZXpWUlgvZ2dXOGNpdm54aDkvWVdsbjNKbTBQejRrbEx1eHFJVyt4MUEwL0x2Ync0QmRP +UTMvcFV4alVoWTdVVTQzNUZOTmVPSERNbnEzNjJNZVBPcnJVcnZxc1BxdlA2clA2ckQ3NXJ6NnJ6 +K1dxK3F3K1g0RSs5NWNRZWRBSWtsQ1VGZWdRUWh6STVzZzZRaVFrb0djT0kxQjcrYkxtRG9SZ2lp +ZGhpU2tXSWhERUhxSGkrT3RmeVNJQUVRalo1TWJlUHY2UUlDYlMrUFM4WTJ6WVN6NkM0V3NiZFM0 +TkJxY2MrRlJBSkdVdkhPWTFtNko1dzZEUlVuUUZrQitPVWppTkJVY09CTnpJZDBVNGc0MjNVb3By +RHZid3hoWSthL2l4N3JDQ1NVeGN1Tmp5QTNjT0xsakYwWGo4V2ZjY1gvWnQzak0wc1ZoaTJ5czNm +eFdQQzgxL21tZkc4RytQbk0ycENYeDZ3QUdjWnJLTzU4ZEQwWUc5Qi9YRVZ4TzEzWHFwdFRGKzdi +RW1EN1ZRUzVoZGF1MndzNDR2SE1OcUQ3K2FXaDVzMk9KY1Q4ckJ3U0NPL3R4YTNYT1FzMXhkT1RD +SXpoNis4VXdRUkp0YTVydjFQdk9yM21yTk56N2d5UjlXK0FoTytaampRMTdHOGpzWWNDTXYvS3Fs +UHlUVW1pODUyWXNUOCtvaGZ6Mk5hL25JMDM1K3E4L3FVODdWWi9WWmZWYWYxV2YxYWQ1VmZWYWZ6 +NnpQaTAwTU9BUkdnM1BnYlFESFFNYkdwWGptSmVtem9xeXc1d0lXVUNUdzRSSlVzUkdzZ1JSVFEv +b0ozcDY4aVJLRGJ3Q1JCSWRtMEFoSTBXZ2FLQ1FpQ1VhSklFR0RpcWZ3eURNUVo2LzRMazF0TDUr +N1BwamtqU3gzRGZIbTNYZTJxQm4yd015R0x4aURGMVk0N1JWZjNqbHNJbWhZRkp4UGVPQVRYLzc0 +NDFkY3pXYXZBb1ozUGxlZ3d5a2NuczN0bTRxSkpUNDd1Rkl6UTRIWmljKy91NFBKbXhDTjcxQjF6 +N0FQdnEzLytMSlhiVFNJL0hBdURtd3VOWk9MZWxqUFBzMEpxejR3OEd1ZkhoRlREZUpQakJYVnhN +V3ZTN1BDcSs3cTVLM0o5c1RZNDR3L2EzcktteFY3VTB2RFowTExWeEpXTk5NYitzMmxGdUk3aEIx +V2FxaVdiQWxFYmVRc0wvRmhsNXU0YXNLL0hPVENGenQxZzBGTjFFOWV0T0dBd0lkYThLTjI0bmtX +UjA1eWxUTzc2TWlhSE5XRS8yaXYrcXcrcTgvcXMvcXNQcXZQNnRPb1BxdlBaOWZuZmdVWGFFMEVq +QUNhZ1dPR0NvRndBVGdTUkpFVVdCTnFUc240cVZnUzlnSWhnUzMwK0VXb05iNGtvbUVDQ0lFU1ZD +RCs3WmVnNWhNSGdlTHdCUisvWWlWNUE0RXd3dVlpWnNOZWpXdS94T1hLTnh5Ry9VU2l1Y3pENm0w +QjJ3d2N3UGtZUHdJU0s1d2hHMTcyc1BBaFQ5andhMDJlMXVCQ3ZuaDQxVHp5MWZnd2Frbzh3SVVu +alNZZXpzWGFRM0FhZzEvemNvUWpBalZ3b0Rhd3c1WURDRytlTlNLQitDb0dqT3BpRDUrd1I2QWFD +VGJyZUdOcmFFWjFoMDhPNnNjZUx2a1phV3d4NWNnbk8zRmdFbjk3YWViVVFiTmE0OXRuUXNLQitI +Z1huejhZMVN1OHZPUXg2M0tVbjNoNDBWLzhlZ1BJVmk4NmhBa1V6K0k3dk5qSTI1Mk4rQnRyNHJQ +SllRQWZNVWZJZTFnT0RySGQyVnNYM3pwY2JQU3MydkVwTGx1K0hOUnFwMGZ4cFA3eUVjdGM5Vmw5 +VnAvVnB6Vytxcy9xVTV6cXMvcXNQcXZQWjlmbnhRWU9rSzBCZ0JORW82WEJ6WEVxZ0R1d2JLeWxT +SDVxWnEreGtPbHVIUUNCTlk5RUZjWlA5L3UyWUh3b1JzQWcwYk1DYUc0QzRsT0JrQ2NSejhqVDNH +bElUYkhYSk9kaWE2L2M3R0V2cnIxaVo1ODdQK0xsOG9aQm5qdUdLSUlLSG8wa0RyL0VLVS8rY1dK +Tkx0WTB0ZHp3cVVFMGhnTGl6VHB1TmJXY1hURHh4eFp2aWdjRGp1Q3p4MzQyT0pXZlBNVXdKeDZP +RjdKRGEzRERyQ1p3ODZGcHhORzg2cWxtYSs4TjI3MGg1Q09tMk9rRjJNM3JFVGpZR1hMZ1g2UENJ +RVlhZGdVNlB2RkJwSW12VjJBTlp2a2FzQk1qZi9LeVgwTjdjd08vT2J5SXFZZlVBRDY0Y0VVRVox +dzNQOXpLRlFmeU0wZVU4dElIM2lDcDgySVluTEQ1R29QY2lCV3Y5aENRT09aZGNzTy9mdUxmbXBx +eVV4OTRjQ0FmK0k5QXo2OWRKL3djb3ZEekI0dURSajN0Y1lCWmt3ZXUxY0s5K3F3KzhTNmY2clA2 +bEZ2MVdYMVduOVZuOVZsOW1udFdmVjQ0NW9DQllFQ253WkZoRTRJUnBKbXNteGVjQS9zRVVYREZR +Smg1UGlTS0RKOEZWVEQ3a0lFc3lZcWZodlVyaGlVclZ2YndKekZGY2pmUEZ2bmlzZUhEUEtHd2t3 +LzgvTEpESkhGYWcvWHNtOXFNRFdIQm9oSDM4OWlLazRFd21PUnA3VkdnL0NIWm1ndjVmTXM5QjVS +bVk2c2djb0hSbkRjSWlpaStROHhlOGZFTGo1ajh5SXU5ZzRJZm53a0hqajEweGw1am1EdU5lcHFW +RDQwa2Y5ais4ZjAvMmVZakF2VWtMR01GT252RWdFMTkxRTJ0N2NPbFdHcnV3clhoV1E3NEZGK2Z3 +S2FIK0pLUGVydnd4azdkVWwrNDE5ZUkxRjRjK0d2OWNLV2h6WVZuM1BHbHB2YktIVC93dll3Ukcr +RzRIQWIyN0NFOC9uLzB0Myt3WFBERmQ3NnVZZWhkdlNldU4wejhFbWppaUN0ZmU5V0x2N3p4U2sz +WjRNRStQS3lBeDFaTnJhczFYdldPL09XbEZyQ29GYno0TVM4UFBQSmZmVmFmMVdmMVdYMVduOVZu +OWNtdStxdytYNHMrTDV5K2lHQUtZYk9MSTgzR3FlSUQ2czdlbXFRNDRGVEJGUlpJalFTZ0pBVW14 +aFhURk1PNk9UK1JLeFNDVW5oZzJaclQyUGFiVndEWWtFVmtuaFdPelRiWEpLUEJZSlVVTW1BZ1hN +V0JtMC9OSmo4NUtMSmhQMkZxRWtUNnpJYWZOTHE0TUNPYkgrc2FjQnQzQ2laWDVIdVRBUVBmU1Ba +WlRMYWVjZVlpR3ZzMHJPS3drUTl1eE1mTEN1OStvT0FWUGo0alRwYzg4YXBXYXJLSDNmM2dNZGp5 +S3dadU5iODcvc1JXcytWdWZPZGcyUU42NHVMRUhRNDEwTGp5M1o1WWdWNTNYaDFkZk9KSHpleGxJ +eGMrUFZ2alN3N3VPSVI1YldaOUczaDQ4VVpJempqVTZDNmZjYmlDSG45NDRnT242cEtEZGVzMXVk +aERQSElWRDNhMlAvdk9GNVlUc2J3ZElsRHpPQkNiUU9Wb0RnKzR3NEdhTHorREU5L3F3VjRjT094 +eHdaYURKZGhoTnVRTGo1eVg1K2xQT0J5Q09VejBBWC8yaU1lblE3VDZyRDZyeitwVGZ0Vm45ZW1P +dytxeitxdytxODluMStjRmVUWUNGaUVBU0xUSWVGd0QwQm9DN2VOVUFJNGtraUlqVHFPNUFON21t +UVJEbHE4aW1KTXNjdTFCcXNURnQwOEM0bWdFUkdsS2Ewam4wMmNGUnJDNDd2WkU0RDViVDZNbGVV +V1NsOEUvSWh4Q0dzRXpNaFY2cjJsRWF6RERaQzgrMkJHR2hzUUxvdGxwcE9DVkN5SGdrVTlZWU05 +YmdCUlIzdml3Rng2Mkx2SGtJYVo1dWVIWlp6RnprQ3EydS95V2h4azRsU3Q4ZUZJbjlYS284dTFn +aFhrYis4NVRjSVpqL3NTU0t4SEFBS002NG9aWU5aM0RTYjFkc0tqeDRoZ2J0ckFSTVgvV2NhbVBj +TFQ1M3Y5d3dJRzNNK0tKRXhHdzA3Q3g1NVA5WXo5c0gyeml2MTdPL2VOdWJ5Ymx5Qjg3YjhkZ3dN +a3ZmdmpWdHovLzNoZjNrTklMZXRnZkRyQ2F3N1U1Mk9CTlR0YkZoYzJWTmJqU3h3NFo0b1pkcnho +cWlRUDc0ZGNqWXFnRGNhcU52bkJQUG56UlIvVlpmY0pXZlZhZjFXZjFpY3Zxcy9xc1BxdlAxNkRQ +UzVJTUdaeFlSQkpDR1NGZkltek5lZVpjNFRrR2RodmtYaEFrS0lJN08zNVRQRVdYUUpySGtDQkNr +Q09HZWMyWUlpTUtlRGpZaXFWSUNrZEVFa1lnUXV3bkZvU0pod2kyTHY3NTJjYWNZWi9tODJ1emla +NzlZcDM4TTNiUGlONjh2WEJiUnpxTThqTlBUSnAyQlRvWXJPRUdGbnUzeVdaK0czWDJ5emVIRmJz +Y0NuaHlXUENIaHoxc1p0NmNXUEt3RDgvbTRKYzNqTGc1NDdaemF1ZUNsVGcxSWY3dE44OXZtaXVI +RTQ3REZWczVwZVpzNUdlWWQ3amdUVTQ0d0Y4T0tXTGhIdzV4eGRkbk9lakVZaWNQVFUxQTN0eW9G +NzZJak5nODg4bU9QUjk0Y1RqZ0JyL0wzUXk5UXVRNDVTOENaVU9VZU5aTDR2M2syNS9mdmxVWGgw +d09TZm5nV1kvQ0xyZlVWZDN3dkQwL2UreFhBeHlLRFpzY2NMY0NueHdOT2NPRFcvWFNxM3lKUTZE +d2hlL3R2ZUVPN3RYTzlFejFXWDFXbjlWbjlWbDlWcC9WWi9WWmZiNFdmZTV2d2ZWQlFNOGFuaU0v +cGRxSU1DQTFwdms0QjlJOEVnQjF0eWNDQWM2elJuQlBvVmUwQXdZQjlodmlJbnpGTUlWV2dHMWFh +MFBvK2h6QlM1US9CNFBQR3NKQXJIaDdIM3VGUXlqaVVsREptOWNvR2ZhNGlNbTFtQWwwY0daa0x3 +enVNRmpIbVp3UkhCRm80b2lNYlJwWFhzR202UGpETjJ6eTNGeG5uMlpSUUJ4Rm9QSzBKcDdtSUM1 +ckd0WWNYUGl6NTNHdzA2emlpeU0vbitHMGhzUEQzL1VsdHZ6RmdERUhBWHR4TktBY3dybTd1aE9m +dnVCZjNlV21SM2FNWDdqODVqWHhjbGl5RDRmNlFrMElrbkQ0VUFQOTRRQVFIeWR5NEhkem5uV2M0 +NGF2YzFDZlBpQnE0bkRIUDl4OEVvaDlocmMxSDM3cmM4dU5QZjV3RWx0TzRxb0hIL0RpR2tackxu +VWxISmo1d1pYNC9NRHFxeURoUTIwTWR6SFVIejl5aHdWLytrY2NYTVBManlGbi9WVjlWcC9WWi9W +WmZWYWYxV2YxV1gyZVVYMitEbjFlVWdDRkFZWXpEalEzQURaTHhMcUdVVkROajNBZ0lpak9BQVZP +Y0UzRHQwVE0yYU1CRkNnaTQxTVJrYWdZNXV5TjhBMk5hSTlZa2dneFNFb2ppQXNuSWxjVTQ1ZFlK +TXNmVWhYSm1uMGhRWjUrb3YrbkgvL3Bmb2RhVFBrZ01zTytOTG40OE1pRkQzbnhwNkM0TXk4WDVO +b1RQdVZsVGp3Y0tLbzNFdkt5cG5qZTZ2QXBqbjI0c3lhK2cwK040SE9KQzZOOCtlS2JyZGpuN2Rk +dGMzWG9hV0syY0lqSnQzcmhNNDF0RDc3WVdlY1BaM0t3VDJ5TkJWKzRFeHNXdVcrUERPZXdhdUs4 +Z2RNcjF2d0dNTGFlOFNjM1dQR1ZlaEdJWHgyTkUzWUU1V0JqcnhjSlBma3U3N09XQTFNUEdXSTdE +SWhkODhzRjV6anc1a2NjWExvVEV0N2xBeHQ3K1JPMkhyTU9CM3o4YnUybXA2M2hMbi9JNENjOUxJ +NGMxSUkvY1dDSGVRVTZ0VjgvRTBkY2g3U2E0QzJIc1o1V0Z6SDVxVDZyeitxeitxdytxMC9QMVdm +MXlXLzFXWDNLQmVmUHJNOExKNUlYa0tIZ0NpUVJBTkpra2hDRUE4UWlod05pQThaUHVwa0haSnRp +eEtHQW1rNlIzWUhRNk5iY3Q1aVRvTGNYZkNHQmpjdFFJQmdqRXNVSlNSckFwWEF3UzBxaWJJMHR6 +dVNFWkhjNWFBRE5hTURvRFljWWZvdVVvc0NMMEF3NEVLa1IrSmJIbzlqNE5tK1BZaUNlalRoNGhR +a25uc1d4MTdvNDVzT2pZVjQ4bkxPWHAvdmpYMW16dFM2Mk9YR0pCM2NucjhNSlhKcU5mL1dEMy9m +R2NjTUhYN0hWWERsWThKVEczRHJOM2pRVUcxd2JiQjBBYXE4bjJLc05HL1dNU09WTWJMaUh3YlVI +K1BqY3BwNTREbWNYZk94eDVtc2pPTUtIV0hodzF3dDhoVC81bjFqWDNmdlJENzZ5dVRuRTVRcXZQ +WDVMR254ODZ4M3grUEZaSHJpT2tOUVRacm5EaDJkK1lNbFhHTml3bFkvNFJBV2pmc0lKN3NVM3Ix +WTVrT1VOcDE3aHg1c28zTkVQdnRtNzlCZDgxV2YxV1gxV24zS3NQcXZQNnJQNnJENnJ6OWVpejR0 +aVNXREJEQUJCQkZSc0c0aUNNVHNYVWhVd2dtSGpwM3ZBSlVIb0FFaVdMeUFReXhaUWNkZ2dDOEVH +c0VoYWdZNk5SQlRSa0JDU1VpanJ5SlNRNFNCQk1yOFNKU2FZdkNYUnVHTFlMMGVrbmNZOEEwNXgy +U0ZWVWV3MW44R3ZYT0ZSRkhsWTk5a2FySWpFaVFNS1hybkQ0am1jK0t5WThJdG4za1ZzZkJqOHdt +c09QL0M2RytMeUE1L1l5K2Y0NTQ5NFlNUEZHZGZsYXh2OExqaE5Ka2M0NU1QWHNUKy9UUTBXTnNG +cEQxeHNZVklqUEl0cGp3dVhHczgrTnZZUnE5NXhOL1RBTnZ0ZzVzOW5lSEZnbjNsWTFVY2YrYXpm +N0JFYmwrbWRpRXY5NVlZYnRza2JmLy93M1Q5KzZVbjlLcFk3NGU3Qk52ajVkUURBSW05Q2swZjZn +VjNFQjV2NDhzZUpXdU5PN3U1NklIL0l3T29QTmJWdytReWJQM1QwcFhqdzhtL05mcm9KeDNLbkw3 +SGc1S1A2ckQ2cnorb3pISXZEcHZxc1BxdlA2bE9NNnJQNmZGWjk3bS9CWlFpTTRzUXBJeHRja3BR +MFVRaUFUR1J6WU01UDIzNGlSNkRFMG1DYWcyK0pBNGxrQ1FNbHdTT2tRNnptUWFUNGJKQ2hDZXhE +Z3VTc3dhbDRpUERtWk1tWnU3VUlsNDA1VFFDbjVPRkFQcHZkTTRPZzVBS0x3d0dKWXNuM2pQTlg3 +T2Jrd2dZT2VZaUJHNzc1WUtmWWlpbC9jZmhKMFIvOTRKbGR1R1J2c09mWEpZNjNCOVlOZS9Eanps +ZjRpRkExVHZJeXJNZGVYRGFleGMvaFNKaG5YQmNMckxoUGJtTG5zM1Y4RXQ0S1lob1Nia0tKUUgx +bXAzZGM4T0JkZjhDZ1grREFrLzI3UHR6RFpjM2JGYml0NDA5OFhGdVBHTm5yTVp6S0JTYXhETDdm +Kzh2ZlhWLzZ5UjV6ZW9Wd0hYajZpVisvSlF4ZXNlU2dwK1NCYzMzSkZnZncrSU5CM25MQUVRd0VM +VGR6K0hBbzhhZTM4UTRqN05hMnY2ZTNrZ003dGJNWE5qSHRnVXRNdnRqcDVlcXorcXcrcTgvcXMv +cXNQcXZQNnZNTTY5WG44K3Z6SW5uaVFnaUhlMDFTQUxsSFdFQnp0Z2tQQ0lYandFL3N5UENzNGRr +UXZhS3lVVGg3Sk9xWnZZSUJCS0FoQVFtWjIwWWVrTzRLeU1hVnBwV2tRa3BLSXR1VUkwWjRKYWhS +RVdvNE1QaEJpSDBLZHc2Rjg0WURDV0xDcE9BSUo4SnRvQm5JdFI5SGNwS25uQlFTTC9EdzRSRGdu +MDl6Yk9IUVBBU2trZVNnS096U0FNdnRuVjlEUG1MellSOU8rZmEyUy82d3d1TFpmRGpoWC8xd2tD +R1dPSExIcjMzNGw1ODVuMWRveG9nbFhJbUxQemcxSWoveWRhVjU4QTB6ZkRoM3NNQ3d0WjU4K0dO +RE9IcExyYXpEdnowbDMxazM3Q0VPdU54aDVVTTk4QzFIZWFpTmZYZzFyd2ZObTF0TU05ajZxb2s3 +WExETHhiK0RJRnk5YWMwLzBIN3ovUy90WG5qZ0l5RDEycmp6ckVmdFYyKzhpZ0ZiZkZ2WHMrcnFv +UE5tUjQ1aTRZMVBYT3FKRmVoZ1ZqTThobWUreGNLalBmclRQRDVweWVmcXMvcXNQcXRQZlZSOVZw +L2JVL0t0UHF2UDZyUDZmSEo5N3QrQUFyN0c0d1FCQ2lkZ0drUXhKWU5nbTVIRkhxbHNCTkhrbkFL +R3VNY0dNWi9rN0pXWXU0SnJQazFtam45K2ZYWmxUYlBBQlN0Ly9pR3NoQ0pRL2wzV2xyVDFlLzRo +c1V2Qk5RS3MzZ1preU1sbnZqVzRkZCtUajhBMW1yMHd3YzVXRElWVW1PU0VTUGxHMExqZ0s0MXNY +ZkhnaHlOWStFbCtjbVhyc3hqODRZaXRIT05YTGJhNDQwK2VMdmE0ZXhIY0REWWF5WDVZN1UvTjRP +QXJqUzIyZXE4NHBtRng2WTRUKytGekVJbXB3UXpyM3BMd0Zjeml5WUUvM0dsY1F0TDA4b0VSdHc0 +RDNMZ1RzZjF3c2JQdWdnOXUrZkdyUmo3akVpNDlaNS82N0NFOUE3NDM3NzZ6dWVOMzhjeTYvdllW +QmVKUlEvK1EyMTc1NmhlWDNwY1QyejBBNXM0UHZ1R1VDM3NjNHNTNkN6ZTR3NzBjeFlEVnYvbElI +K0pNWExaeWt3ZWVjQ2NYT2FtMVdIdzRFRnoyc0JGSDNPcXorcXcrcTA5OFZKL1ZaL1ZaZlZhZjFh +ZitmbFo5N3I4QlJZN2tpTXV6Z0FBeDJPWWFVRUFUbkkxRWl5REpDOEpPQVB2WVdkY1FFcmV1NEFL +Ykl5eHZEYndoVVZURkJCcWhzSWduRHJBR0FZaWZPWDc4MWltZllkUUk5b3NORDRFcFRINHlkNkFn +U0g0dTVPMllCaEhmWjVkbVV6aSsxKy9kaGc5MnlBM0pNTWdMeWJCNUZrTXNPY0ltZHdMd3JBZ2FR +bnhGczBjelBvclhzTVplQVRWSG10RGcxMmUyZTJqTUhTNzgySWU3Rjl3ejRPWnI3YWZ4NEpZbmJ1 +RGw2MUhRNnBxODRDTklQdG56SmFZREJjK0dPVTFvRDd5UFBPQ2U4TnpWV1NQTEZaNGNZcmpkZzJE +ODJxL3Y5QVljZk1BSFEzampYLzMxS0gvNlJXd1hQd1liWHoyd2g4RDFJdndPQ1gyUlBuZkEyNDhY +Yy9FSml6Yy8rdGY4SG9UalUzK0pnV2RyNnNtbm5yRis0czhCTy83MER4N1VWMTZHUE95QjFUTTcv +S1VPZUlZM3VmS25kL1JiOVZsOUd0Vm45Vmw5VnAvVlovVlpmVmFmcjBXZkYvL1JUQXFOVEp1UXhB +aWhpc29aNE82Wmx5Q0g3TzBUd05BSWlEUnZyNFEwcmlRQnRCY2d5U3BhQklZUSs5SnNFdmVUdjNs +elBnTVBwNjhvc0kxQUpTMkd0MEYrNVRRYmhDbTBOVVN6dGNkUDk5YU1QVERtWUpFSG9oUU9INHRy +eDNXZjVRRzNuT1RBdjJlNCtiQWZMMkxCS1E3aFdlZGZreWkrdmVad2hET2Y1WnA0Q3Flb0s0aVpp +NGpsU0l6MjROTStjZGlLN1RPZVRxTk03NDhJWUlTWnFHQ0VWODNNUjNqeTQ5dUZPN2hTYzNFMWxY +M3dlRXRpVG0vZ1QwMGN0UENFQjRMQXdmcWRZUTh1eElXVmI4S0ZnUzgxU2RPeklWQnpNSHZUc2p3 +TWJ6RFlhOTJob0VaaXFhVjFZaGNUUityUFhvNHd1ZnNIMi9yWFBudCsrdmQvdExXV3M5NndobHRj +RXF2YTBZVFBPSkVUVEd6aGpVRHRrWjkxbk12UnI4aUdsZUQxcTJGZGY5a3ZiejdrNW5QK3NKS2Iv +YmpmTjJnellLOCtxOC9xcy9wVVEzMWtyN2d3cHZlc1ZaL1ZaL1ZaZlZhZjFlY3o2Zk9DSEFFQkp6 +TEpJVkZ6UzFEaEJmTDlaWTFnamExa2ZVNUIrVkU0UkxPWEhCdlBpaW9vZnhyTG5mMlNPODBCckVa +T0xLUWNrYjE5RWE0NEVhcWY5bjFXRkw1ZElWQ0JKR3BvMkYwYmdtQ1YyNUttb0hORjlIREFxbWhF +b2VFeWlBWVdPT1Rrczd2UHlMWUdsOFpLOHltd0M1N2trOXl0dzQ1enorNTh5Z1ZHbHoyYURTYjR2 +UzFnaTF0KzArekx5VHlyRmNHSmIyeXpEQ2JyY2pFdkw4M0FYdjF3c2VLY21sbVhpNWd3aXVHek8x +dlljZVNDeGJDdUNkWE5NOHo4T3lqM0FCemYrOVp1MXRqQWdqZXh3Z01iK2VOU3crc2pjekI3K3lJ +dTdtRGdYLzdtaVllbzJMTTVlWngvbUc1ZHYvQVpubjd5N2MrdktJa0VMeDkvK0kzdFp6MGpKemI2 +RVY2L1BVd2NCNEQ2dWVDUmx6NkZRejVxZzBPeDlMWTZpUzhmUE1MSVZpM1VWMzVxS3g3TWZJbm5R +Q0RRY0k0YmZacy9uS3JQNnRPNlhLclA2bE44OStxeittUlRmVmFmMVdmMSthejZ2TmlFQUVRUUtL +Y0dZejlCQXk4eFA3RmJRN0JrT0FYSXMzbjdEYmFBSThkUHVBcVI1alVuSWVzU1JGWWEwMC8ra3VO +SFFvZ2hJblk3TjNnUTZwbnRGbmdHNHVDVWxEMklUOE1UcG1jTmJGOEkzWEVYS1B4K09wZTdyeWdR +LzJreTQ3eDFzQWNPenprazVNWk9UUGpGVjBocmlzUGVQSnpiNElNL0JjS1hDelp4RlJnZU5ueXlW +eE5yQktHUVl2dXNPZVNpb0o3NTN3TnVjbUNYZ2F1dHplREJnNzE0VTAvN3JlRk5JNmdQTE9ZSW5T +OXI2aXQzUG1Ea00xamxxZ2MwczcxOHlsdWo0azFqcWprL2ZNQXZmL25oa0wyaFFYSEpsNGEwMXg1 +WTlVdjRzZ2VuRGhweHZJbXhqMS80N2NPWlF3TTMra0RPZlBoL0pQbEgyZXh4cGNaNkRTOThFNk04 +OEw1Q25zOWlwWC9rZ1Z1MXhnWC9mRHNneEJSZjNsa0xEcjd4YTYvOHhNYWJYTVRDUldLcmkxaDZJ +QnpiajdQcXMvcUV4VnoxV1gxV245VW5lNlA2ckQ2cnorcnpXZlc1UDRBQ3Jua1pDV0lnQmlBZ2JF +U0VBSnBkSVNWb1hZTklKbzF2UDZMWks0S0FpZ1dNV0lxT1lNSDVNNEQxZGdHRytMU21VQVJ2M1Jw +LzFyMGRnTStiQWVTNDJOckhEMklWakMyc2FTQVlnMVBqYTNENUlRbWhQL3ZPRi9ickQvQ2VjZHY5 +RVlObitibDhSblo0VWdUUE9OVGtHbzl2ZG5JVzN6N1l3cmY5OG9NZFh2eWtLZVFDYXdUa00rSEF3 +bGV3YUVCY2krbVF5TUM3L1pwRkhIeXFVM0lXWHkxeEtGOGM4MmRlREh2Z1ZDZStZSlRqeHBnOTV1 +U284VFFuZkpyV09yeHdhelR6L09BSUh2bXg1OSt3aGdNOVlUL2ZZbm1qWWc5LzhyTVhmdk1hV3Az +aDVQKzhJYnB0YnY1bnYrYlZrVTgxSVdwZlU4amg1Q0FXRTQ3VXpIelc4R3dkVnJ3Nm1EekR3VFlI +QlN6V3hjZWxYR0N6WHoreEYwUHZPekQ0bDdjNnF4dS9zTnF6ZlRxY3lqa0NsVnYxV1gxV245Vm45 +Vmw5d2lPLzZyUDZyRDZyejllZ3o0dm1rYnpDY0t4SWZpTG15RE94K1ltV0l5QVUybWZCUFFzS3JQ +Mkc0SjdOOHcyRUJrQ0VnR0lwcWpXTnFKajJhQnJKZTBhMGZScUNIY0FTSmlUN3ZUMlFqTDFzckMv +ZWFTYUZPdzEvaEM5NVBpVHV6c2FRb3diak13M3kwWHRmM3JjSGp5TWN1T1RrTTV6OHlHZjVHTElW +Q1JZSEYyNGlBcndwcmozV1lJWEpzL3cvTzFET1g3SGpsazhGZzMrYll5NHhORERCaVFzSFR0bEVy +QnJCd0hjYVh5eWNzZVViSnZ4Yng1OEJ0NmJqd3dVSFh6RGFLelpPaVFVVysvUUNQSXQvNXVDUTg0 +clRBVGNOeGk5L3ZqYUFmeHpDd2JjOURrbWN3b04zbUFoYkxlR3h4L1B5TWhoZ0lRcCs5ckNaZnJW +ZkxSMXc1dDY4Kzg1eUZEdmNFSWYrU1IvNEtvclBhaUV2TmpoVnQreURSNDZlWVlWWkxQWGtYeXg5 +S0Q5RFA1cUwyR2dDWm56QXh4LzhmS212aTAvNzlWNzBwSy93Wmg4dHdHeGY5Vmw5OHVHQ28vcXNQ +cXZQNmhPbjFXZjFXWDFXbittRFo5TG5CYW1BYXlDR2FXN0d3Q2lvUmhZUW9SR2dOVUpDcWtJdDBD +a0tQd21XNWhUTTNWN0piYVBmZ2ZBcFpqRDRpWm92ellCQSt5U0NLUEdRNEEyUlp2TldRSk5yRHZG +U0dDUVkxalFaSEFoVUJMRU5wQ21Tdk9RdEoyOFJQSjl4L3FHeFBYQVJzcGhpd01IZXRlS1lYSUpY +Z2VEWENMR0hodzljYVRqNzVDRnY5bWt3ZVdoZ05zdlIrTEhYUEYvaUtySTEvdGptNERFdmZvWjFu +S2xGL0hsbXAxNXdPMVFNOVlHREh6Rzk5Y0NkZVBMWGFKcFZqakREeXhZbitMWUdwMXpsUVdUcXVn +Zno4SzVleEpSbWgyZmZ6a3dNM0JFNUcrdHF5cC9EQldieGxwZXhoVVhONUFBalB2bTNCeVl4L0pZ +d1BjMG5HendUcU0vODY0VmZmdkMxN1F0WVlWRjNPTVFqdXZTWWZNM3BQNWp3bDdxTHhlZnB3MEV3 +K2NvZjcrenNoeGNYY282dHZMYzNobFB6K0laRmZQclF4M1FoSDdsVm45Vm45Vmw5VnAvVlovVlpm +VmFmWjFpdlBwOWZuL3YvQWVXUW9jUVFFV0VJWUNNd2ltcGpDZ3dNOFBZTGdFVHJTT1BMWjRsR29B +RWxBV3YybVJmTFozRVFxbW0yZ09OTEFta0l2dUJ6T1RENGN5QklncjBjc21hUG9pSERQRXdycE1H +RkFNTStieTRVTExHUnF1blN1QVpzZUFpSktaSmlhMUxyZklzbEY0M0ZGMjZzdVl2SkJ4NXhnRHY3 +K1lOWEhnckpIMXlhd1JxdWlaQi9hOGxEb2QxeGtJWlhYRTJTQVFQTTR1T0RML0h4TDc0OE5LQzRH +bFhlK09IVFhqandhWjg4K0ZZSHNWendFelg4cVlFNWR1ckt0OGFHeXlISWpsLzlrRWExWm8rODhP +YlprRC9oeTVFdi9uRUxpN2RKdUdRcm4xUFAyOGJ6MlQvQURqWjEwcXQ2aHovNXlOOGJJdkhnazYr +NjQxc2MvdTBuS0oveERnOE84ZU9DVjZ3WGdVNzk5S3Q0dUlYdjB6bm84UVlYak9LSkpROTMrM0Ft +UjJ2dStJaE4rT0d6K3F3K3E4L3FzL3FzUHF2UDZyUDZyRDVmaXo0dmtrNmpBVytUWU82Y0NKYWdF +Z0xRbXdPMkVyRGZUKzBjSTk4OElnUmtwNEZDTXFCOG1aZWtOd2lLenJjMVRSUFNnSFJZd0dWdXhh +UDVod1R4N0NVa0RhOEorYkFmdVpMVEtFbFM4MmtPc1pGdHlFOEQ4K2NPdDBaUzBNOEVldDM4K0pj +M0grTEpqYkNKSjgxdkQ4eDh5ZDh6VEhKSWNhM1pMdzlONDdPWUdrdytFU2o3aUVtQjdiVm1EOTZD +d1J6TzVTUWVERWR3YnhjZm52REJIa2J4Y0NBUEdITjRpaTNXNWpjMXQ1Yy8vdTNYdlB5eWhRMTNC +Q0FQK05YTEJiUGVrWThCbDNqaXFwOTFlOVdVTDdYUnlHeFd3Rk1MY2F6ekxhNTRlb04vc1FnTkx2 +SHh3WWQ0OFBJUmdlYUE0OTgrd3NPam12QWhWeGhnNUVlTTVHVk56OENCNXh5Q3lWTk1kZUFidHpE +ajBoeGU5WXVjK1lRcjloRTZmOWJzNFg4UHg4RnJUay9wZHpuQktnZjVWcC9WNStaWGZlNWE5Vmw5 +K2x4OVZwL1ZaL1VKbzNnNGtFZjErUno2dk5ob2tWUE9DU1pONVc1ekdsUURzQ0d3SldIbVhCSm1T +eFI4S1RCeUpNNlBuNkFCQlVBUkpLWWh4TExIdk0rd0FPZlpQcGY0UUNOWUhEWWFieHRyaUpHRVdC +cExjN00zaU5OKzYvd2hRaU5vZUFNNWlnRnI4TWdMc1JuOEkxZWgwdFN3aUNkUDhmYlFtbWROTGc4 +K05hSWlzRlZZei9iQUpvWjRjdG5QWTV2bUY0c1BQdG1KeWE4bXNJWlA4N2dPOS9KejJYZkVZZHhl +bWtJTUJ4Y2JjZVdnQm5DS2s4TU1OekRaWnoxTkZGRTdyTmpoVDA2YTNwcExuakE1M0U1em5TR21I +QWhIL1hEQXIwdnQ5WVNZY0xua2FlQ1ZmMWpnVXlzMWdKMHYyT1dXdXVOUHJlM3g5WU1JeTUxZnR1 +b3NIL2I3aHVqZUoyeDhmOTI4UE5YREhkYzVDR0Vnek9qQW5UODJjamR3RDU4KzR0TUJZeTM5clc3 +cEJVTGx6ek43dVBHVU9yT3pMeldzUHF2UDZyUDZoTFA2ckQ1ZDFXZjFhYjkxL3FyUDZsTSt6NmJQ +UzRoR01EQWFnb01JaWhoc0prZ0NWUVRCMkdwTVRwR1AxQ1NTUXJrcmFFUXZPWUFrNHRtYXhQaEEy +UG9aTzc3ZzRBc0o3aW1pTncyK1dtQ3ZKTmk2dkRWUU1NV3dWOEVRalhnNHJjR2NCcktPNUFqR1hh +SE1PVkFNK2NCcEw0NXdFSCthbUQ5RllNTVdEbHdwTWh2ci9MR0JSZDZhU3c3OHNlTmI4K05XN3Jp +WG0zbSs4TU4vQk1jZjdJcG9Uak1Rb0ZqMlpxaWRYeTB1TjFnMWxaaDRkbmZ4b3piSk0wMEpJOTh3 +d01nL2ZIaUpHTlNLYi83Z3hndWYyN0JzWnhDVzJIcU1uZHJqWDkzWWFGRGZUOWRqZklscEVMYS8v +cGVEL1BFaFQxaGc0MU52dWVzTnZ2UXJVZVRyQitwcEQ3NzRjaERJRFQ3L0x5MzQ1WU0zMzZHWGd4 +ckZ2NE1BSGhmTStGWVA5cmhpSnk4eCtJRlJmZlFJcnVVRGZ3NTV0dTdpeEs5NjRVUXN2YUdIWVJT +TFA1K3J6K3F6K3F3KytaZFg5Vmw5VnAvVlovVlpmYjRHZlY0RVpDeVJMY1FNeEtaWkpJb01RYjNG +a0N3Z2JDVUNJSUk1amhEWkt3SkNCVUtnQXFZNTdMTWZxUXJBaHowYVpFVS90bUx4cVVrVVVoS1NJ +VTUySzZLNUZ1dGRKT0t4OFliRW5tMjQ4YVhnNG9tOWVHWWd3Um9TRlZUQi9BUGRJNWd6K0laSEU4 +Z1JGL3k0RkZPc0hCRHc4czBuZnVRam5wemxhVTRSY2hBRUQvNk41QXZYOGo2NXJFQ0paekRnaUE4 +WFBHckczalBNY29WWDdvWjF2NnBaWTlpdmZtejJrSmw5K0JKTHpjVFdET3h3Q1JlL1lzRWdKNDIy +NDg0NXp0U0VmeHl5WVJ0K0hUcWV6ZUVnbC83UUd6bk1IU0k0NFF1UGhub1JEY3d3c2xYUENCUjIy +TlRBdkZnT0RTSjg4KzQ3dTllNk5iMUdrQVFqTnpqOUt1cnRvWHNzaHdRczZpbUdmTmo3akJmOHdJ +Rm5kZU5ITGZud2JQaERnWWJrQXA5YytjYXZPMnh5SjNaNXFnOThjdEI3WXNHbXp2aDF5Yi82ckQ3 +RnJqNnJUNWlyeitxeitxdys3V0hIVi9WWmZUNnJQaStLeFZqaGdGUmdZQURqeUp4Z21pWnZCampU +V01oaVowNHd4QU9KbklnbUFnM0JDaXNXMEJKWGNINEJSUUJDSmJtSlRFSkp4RDVZa0xMa3pqNmt3 +TFhOZFJlb2hqREUzK2E2TjdGa05TQ01ob1BCdkhYRllPc2lyQXkyOXNFaFBtemIzSGZCSUZSOERj +UVdUdHp3NTFKTWZOaVBFMDJvK2MzSmRYM0NPL3lrbWZIQ3Q0UElaeHpoUUUxZzQwY0Q4UTBMMzVw +RkRUVERFZWh0L1pzaklGekFxWTQ0VWp1K2NXRGdYeTc4eVlHL0hGeHkzYjRZakFhaEJndmZZb1Jq +Kyt6Zk1mWnMweWRxNXBCanAyN3lsYXZEQ3NmODhXTzQ0MDl1L0dsVytPRG5DeC9wRjMxaXdBVFBQ +Ly8wTDE3NlNGN2hHVFoxVWgvL0x5VTlpRnQ1K2gvNTRnTldNZlNUUFdKdTduTUF3S0xIK0lGWGZM +V0V4ZUFyQndnLzh1RUh4L3pnQ1I0WTFHTDdhdlRGbnovMDRNV0hTNzBjNnFseDlWbDlWcC9WWi9W +WmZiS3JQcXZQNnJQNmZBMzZ2QkNEUlVCdFJFZ0tCZ1NBZm9KR2hNYVJRQm9KRUtBUmczQTIxczNa +cTdoTDRJakp1czhLSlRIQmdROWdHTkowbWhEaENpTUpKR2hZOGF3cG5zRW5mUGJCd3pmaVE2ZzU2 +L1lyak05N0tNd0ljZWI0czI4UGtNSC9NcWJKOEFLSGZPSlAvdllxL2haaGNsaC9nOTErZkxDMUhx +RXFXSjdGa2k4Lzl1OFlqbUQwV2U2S3BuaDdVRTNCaUk4UDRwWVBIekFvSkQ4YUNvNEkxRjV2U25D +TkN6NC8rZWliMjBEc3hiZVhpQnd1ZUJWZi9kVjVCVDArY2E2WkhnVUtEM3Q1OHNkZWJlM1hPL2pu +VTB6ck9HRlBvR3psQ3FzKzhaVUNhK2tyZWNwdkJUcTEzeDRZL050YmcwY2ZpSVVIKzluREpCZHYr +TjU4LzB2YkIzeEdvSGh3T0txUno5N1cyQXNEMzNESk1jTGtuNzFhNDBQUHlBVys5Skg0Yk5Sei83 +QVlET2toNjJ6bGdRZDMrZkVoSHI5aXlZOGY0dFZmK2djT0hNblg1K3F6K3F3K3E4L3FzL3FzUHF2 +UDZyUDZmRTM2dkZqZ3pBWmtLRG9qZ1pETHFRUVZKVTBYNFM2UUtiUkNBY2toMEFMd0VSSVFwZUQy +S0hpYURIZ2lRNEo1R0JETnYyZmlFTjllcEp0M0lkK3dUd3drc1UraWFRRHg0TFllY3ROb2ZNS1BK +TVRhcTRIbGw2R2dmTERsSTZLWEc1N0V0aTZXM0ZNWWg1NDgyTVBEVHRFMW5YWDRpYzB6UDRZWU9F +ak84dVViUDdoU01ITXVjZFFHcGh3ZWNzQkhoancrL0xzL1hKK3cyYWVPNXZFZ3JqYzFtZzkrZFdP +elRUU2N1ZlFBVy9tcTFZci9mbWdSZmc0QStOaHVMc3Z2ZFlWakwwSHg2OENRUCt4c3hGWXpYNlBR +dlBqWFMvQ29TUTVrSElySG54aHc0bEwrL1BOREhIamdnL2djYnZpVkF3N0ZJTjd0bDltcnpuSktM +b2tqUHJIS0ExWTFVVWY1NEVWZCtaVnZ4T2g1RC8zcE9aallxTGZZVzQrSklSOXI1dFNUciszTmlR +a1AzRFNSM3JMT0RpNjFyVDZyeitxeityUkh6T3F6K3F3K3E4L2xZSEJYbjlYbnMrcnpvcGdNaWN4 +ZE1RSFZSQnBJRVArbzJJWTBlUnFUUTgyQlRFUkZyQkc2ejRpVnBNQXU5b29sTVFJSVdJMGpQakZM +MG1mK0pRZURlWmVtQkh4eHp4cC81bHh3d0djZzEwRWhEbnQyRVorQ0lvdC9jeHBGdnJEems2RXBF +Wmo5OExIRjBSSThQc3l4RTljY1cvZ1ZQcC94b0FESlMvNHVSY09sQWE4WTlzalRIbXV3OHNHL1J0 +L0dtN3lYcjdtTDQxbHRjSmtCNXdmZi9QMzFrZndJeW43UGNHc2UyUEZMSkRzL1B1MFJML1dXNXhH +by8zbnhkVEZxS24vVm40Wk5nN0dEbVYxNlNTUEwzV1Z1UlR3TmJSOUIySnREV3grSjUwME4zSHBK +VGF6WlQ0QXc0VXFQT1Z3SVJPNE9DM2hnazQ5YytQSzFCWi9Ec1Y5bExWYzlEVE5lOVlvNGhBZWpu +bWUvSXB0Y0hKQnFCbWNPQ0xaaXJFRG44SUxQM05ibTNzdlc0SVNiWC9tb21Yek1zOGNCam1DeFpp +L2Q0TTd3dWZxc1BuZCtmRmFmMVNkODFXZjFXWDFXbjlFWG4wYjFPVG9jM05YbmYzeDlYb2dFcVNr +YVV0SW9hVEtGUmJxZnprTXlrQW9vQ0RMamc2M0VJMGFCa01nT0FTazhnSVFyQmx2a0lzRm5Ra0ZZ +NGtnWURzblp5OTRJd1M1ejNnTHdZMGhjdkZ6OEtKeVJwcmRQdm1KYWcwMFJNcENiUmhBWGhseDh1 +aXNDRG1Ga2F6L0NyWW1CRS93b0VCL2loUWZ6RWFnR3N4OHY4TUFQSC83TUtSYjdOQVYrTkErTzJK +bkhqNkVXWXVTNzMvQ0xKMGQyTGo1UFk3MWRyUEN0NyttQjFCWTJ6V3UvQnN3UTN4dVVmL241WCsw +ZWVNVTdka1I4L3I5UHNQRkhOUHluZC9BdnRqM21jY1Fmdi9ieEoxYzFmUlNvbkhHclArekJ0Wmo4 +NlRINWVSdmtEWkZuL1dXZlgxMjkvZ2VMT3VrVEFvc0ErZk9NSTN2RjArdGk0OGFCb2pmVlFJMVN1 +M0J6RHFTM0t6TDl4OGJCb0Mrc3dVM3djdWZUWFR6ODVsREJlZnJKWG5lSFFmVlpmY0phZlZhZjhw +YWJmWGlyUHF2UDZyUDZkRldmMWVjejZ2TWlnQ1p3ejJhQTNZR1NJTkFJQlNxa0NPcW43dnpFREl4 +aW1iT1hqYzlJSWlRQjJZbWhjWS9JYnR1d0VRYXlnR1RIaDlqczBseUE3eldGTkdEaW53KzJjR3ND +Yng4VUFnRjh1V3U0WGR0eDIzbSt4ZUZQam9vbXJ3ek5JaDY3a0I2TW1RdXAvR2thZWNPcm1QejVM +SDRPRDhWd0thNDc3QWEvTUVjZ2VPUkQ0OG1SQ1BIUEI0emlFbldhRG45NFB1TzZQalRyQ25SeVpH +Ty9tSERhZjhidFJTajQ0ME1NdG1MS3hmMEk3N1B2dnF1dDMvQ2wvdkRnZzdEMjdZOHhOY0NyL0dI +VG9FUUNDMTdsQmVQK2xyQnBiSUk0bUs1SG9KTy9QdEhJeVJlUDRjQWVHTXp6cFJmTStmY2MvdUcz +TmYxcnI5ejh3U0VQZUdCUm0rUmlEWDZYWno3eHBDWnFtejZ6YmkwSGxCcWVmam5jNE1xbHB1TEFa +VjRPOHBNWGY5YjR3RHVjL09MRjNieWF3cVgrYkt2UDZyUDZyRDdaVlovVlovVlpmVmFmMWVkcjBP +ZitGbHdiTmQ4MjJwQ0FBT0NJeWJ4a0FFUTBBSWpsV0VHQkZNaGV6NER3S1JIRkF5NU5IY0daOXpr +L1BTTkRqQlExb2dlUUNGelcrWkU4SHdac1BvdlB6eFp1UkloNEpNQnFMendFb2pIUE9BMXN6ZDV0 +aUNsODN1eDhOczVmc3lPTWZ3VldkSGR4dC9rbmpqeGc1UjkvZkxyekZ4d2EwVG9PNFpRekc1d1kr +QXN2MW1DVHAzanU0dkVuUDhXelQvNnd3Y05HalRMNDN2OW5FS0hmUlFxSGcwNzg1WCtFc3JiRGhU +a2M4d3V6ZS9JUUR4ZnkxS3hxeEk4REFHWlkrWVlIOStlTnllMmxDYTNCN29KVDQ3SVJBeTg0VVI5 +eERUMmpsOWlLdlRVWVh6aUdrejBlSERwOGJNekpoUkNKMDNmZ2lWQy8ySmZEMGVFalYxOVo4Tms2 +MzN2NGp4MmNCQzR1THNUMmh4ZmZ0R0RlWlE5TzRjQzlQNXpZNlJINThwMDhIVmoycHJmeWg2QjRp +VU5MY3JlWEQvMklBOXl5clQ2clQzTTQ1aGRtOStSUmZWYWZZbFNmMWFkUmZWYWYxV2YxK1F6NnZB +Q3NDQUpLZ0tGTkF0dm9iajBOaVJTZkl5b053TEhtUUtyUGdyQVhJS0xpUzJEN0VXMWVQQVFyOEtm +ekV6ZWlrU1JCZTJDRFJ6eUhnYVFpWGtPaUVTQS8zdko0cTJDSXhaZDVGd0h1Mmh3Q0NPZlRCU3Vp +K0ZjY3VEVFhHZWVuZmh6d3B5RFc1WXRzL256V2JIeUpBVHRlK0ZSNEJZRTVmNTB2ZnpZNFNFRU1R +alF2Vm9STWZBcVBkM1l3OHBXbWhZa283QkZuRzJJR1BIaVJqNXJ3aHdNKzVNTzN6MGZRMTUxbkE1 +dVlFYWdZbnNYQm0yYkRYZmdraU9WUm5lNU5kOFI1M2FaTjdmUUVXLytHd1dkcmhsaDR3WVhjK0RE +a2dndHhQTE9MZ01URm83ZEJlZ1RtQ0lIWTlaRmNpRktUMjB1d0JPSXpYb2pYUHZuTFRYeHIvRnFU +anpqVytIWW93UUU3N3RqQm16cmpSdjM0NFZmZWNsRTdsNzA1Z0RhZnFSbmZMZ2NOekhEeEpXZFkx +RmlzNnJQNk5GOTlWcC93MkY5OVZwOUc5Vmw5bW5kVm45WG5NK3B6ZndCbGlCQnZLUmdRbndaTU1F +UzRrS0lwUFhQSXVYMGFUU0lhS2Y2UUthRTBMOUlBQTl4K1Jjb2JLWHZTZUh6eXd4WVdjMmwybjlm +UEZNdXdwcm1TTEhKOVJxaW1oVUdCa3Q4S1ZKTU5KdkZjNGl5R3VleEhhQWE3YmRxSjk5czV1Rzl6 +VEI0T0dxVGlqU0FVeXJPM0ZldzhpOCtIM0gyR2pTMmNEZ1ErRkI3K2NDMWZ4V2NIdTMzbWs1Tmlp +NnRHOXZsczVLRFRSUGgwVnlQNFliTm1QenVYT0xnUUl6akVndDArNjRScDRCWSt2dVRrV2Erd3dR +dC84bEZibU14SG9HcUlNMnU0WlU4ODhCQ1FQakRrUVNEODRsNSs3akE3aERXMHZPeUhGV1pEL1h6 +RndKcTNRTEFUanR6NHc1dGVFcE1QM0ptVGErcUNLenpBTEI0Unc4cVB2UzQ0NWM3ZTV6TnVtd2Rl +NUEwYlRhUS9ZQlBEZ1M2dUhQbG5qeGMrN1hORm9JOTZFcS82ckQ2cnorclQvdXF6K21SZmZWYWZ5 +YS82ckQ2ZlRaK1hOQ2xqamhBaGFTQ1JLVWwzUURsU1lJMW1YdEpwTXZ2NUFzeG5QdHcxdkx2R1ln +TWt3aFZjTWEzNVNkaGVCUUZVMG9DS1kxM2lDUEhzRGp4Q0ZEOTcyQ0lkSWJCYWt3TWMvQ3NzUDhp +MlYwN201U3NldjRwTHdHZmNYcHJJWWNTWDNQa1ZIeFp6bW8ydHhvVWhqVWFjdmlLZ0FlUW1udml1 +TkpjM0ROWU5CWU1IcitJcFBuN2tZNzlMMDdqalVjMDBnRnh4QUU4RWluTngxRWVSN1F0M2ZIdVd0 +NzM4dzhMZTEwdmtKNGE2OHV0WmprZDQxNjBidnRMTThvSkZ2ZkFpTmh0OHdCbmJIRnpzTlRzN3ZR +Q1RuTlVPYi9iaWhLQmhpVzk0NVF1alBrenQrRHNDdlcwTS95QmJyKzNicVBFWEx2U0JPejZJUlR4 +NHhUS0hFK3ZCbVQ0akZybTdpOGxPVG1La1YzTjR5VmV1K05ZTGVNT3gvZnBDbnZJUjB5Vi9Qdm15 +NXBLUFdQYWw3dFZuOWNtKytxdytxOC9xcy9xc1BxdlA2cE8vWjlmbkpZV3dxRkJJRVJRUlFBTExF +Vkkxc0o5b09XRWphQklCU0tBSVEyRDdraERnYVNZMkdnT1FDQXNPOCt4VGtCUm9CVGlYcFBpQlJi +TllVeHoyYWNpUUtSKys0WUFaVnI2SnptV1Bab1V0amVLdnl6VkJodUtLYVI4ZUZKbFBQK1hiSTAv +eDFtN3c4cWw1K09SUFRQTUtaejk3TWVIRWd6bitEWHpnRDdmNFpIY0VkOXU2eUlHZzVjTWZidGl4 +NGNzVjdCb0dIK0x6SXk0ZjhHL013WVE3Zmh4SWZJbExjT0hOZm52d0xWODVFaDA4N09YQVB4NDBJ +bHMrVjhpLytmWGFzck1PT3ord3d3TWZmK0pvUW5OOHJVQ25OcmpBSTN4eWRLRHpwZGFhSHcvd1dX +TnpEc256Mjg1Ky9yMHY3bDRIc3J6NVVndng1WjgzTXA3MUNkL3lobC91OG5LQW1zT1QzT0VWaHg4 +WStVMCtlSk1yN3ZraWRqSFVGUzd4MWNqQklWY1lsLzlaRjVNUGNjM3JHLzdscGNmVnF2cXNQdm1x +UHF2UDZyUDZoS2Y2ckQ2cnorcFQzcyt1ejR1bU1NbkFwR2ZFK1NrYUVjQWltVU52UFFpSkRXZklB +aHB4Z1Bvc2FRa0FhczhHSFgvV0ZCQzRiYVlCYmgzUmJCMEl3Q1plOWtsT3dzQUNqa0JDMXlqOElJ +RWZlOWhzZ1llc3hUQ2YrZVJiUHV3TnNmbmVocDNZQ0VLNEp1RTNRMkdXeUFmaUl5RGs0b0V2ZHA2 +dDVXSXZGM3k0OXZOd0RSY08rWkQ3T1d3T3BoVFF4UjVmVytCcGt1eUhXV3lIaGdzK0I0TlloeGZq +dWhqVVMvN2krS3pwNUFpRCtPckdGdllJUlgxOEZvOG9ZRjBjdzJrRWlnK0hLbjk0VnlNTnpJNC9J +NzBrcmo3YTJCK2ZYOVZOd0hMQzUvYlk1QVdYT29oanJ6OE05QUNjY01oUkhEanRrUU9zNXBPM1h2 +QS9KMVpyL3o1QVRYSFB0OE0zZGZIMkNINFk3UEYxQnY3d3hCWm5SR3pONVJCVEI5enpJV2M0OUJj +dURpL25mK1lySmw4dXVPMlRqMy9RYm45OHVtQ0hSeDM1MUpONzBFMU5ZZk81K3F3K1lhOCtxOC9x +cy9ya3QvcXNQaGREOVZsOVByays5N2ZnZWtDd2dWUVhRUUZLRUFxSEhFa2hCMGdYZ3RraDFiTi9K +S3RJd0pzSGpGLzdKYThBbWs5TVJiS1B3QlZDTEFuYkt4WjdhOGdYMHpQZ2Z2SmYwbVpJQ2tHSklW +RWtLUng4YVU1M0RjSVdrUnJUTXh4aUkybWJmQnJsY2NESGpqOStrTTVlanVibDQwRGdFMjdYTnNI +a2dTdEY1b052ZkJCUjdPRGNacDNpR3dvaVB0OThpR2RPYnU1aXlZOXY5ZUVUQm12d2Fhd1hnWTVQ +ZkJBb1c1ajVjMUFSSEIrNDlKYkcvZ2lGSFIvNGdrV3R6T05MSG1ybURaQjF2dFJDMDZuRGludXdP +Nnp3b1dsVEEvRTBybitvck5jTW1HR0oyTFkrVXcvNzRJTGQvbkRBSGc3N2NadGVzcGU5d2ZabjMv +bkM4azZnZWtZdmllMS9Lb3gzZU8yQmlZRFVqMzBPRTg5NDF2ZjZWNi9KQ1MvMldzUDk2bVN3aUhs +eVBvY3NiTlpkWXNzSEx3NEJhL0hwNEpBWDdsTWpkNWprNnM1LzlWbDlWcC9WcC82b1BxdlA2clA2 +bEY5Nmd4LzM2clA2eFB1ejZmTmlRYUVGVGhDa1NnNEpnRXBJWVRTWnBsSThZSkdyVU5iNDBDUUNj +MndmNHZobkM0aTdSZ0ZFSEdENWVCSHUrTmpEWUpKbVo4ODI4cENwbUJKakw1NDNGVWlRUEJ0eEVP +dzU1TUh0cmpDd3NYMXNIblpFSTQ0MWVUOE92bURpbXkrY3lGMk83SzBwamp6aFI2cDFmbncvSHVa +d2dJL2t6NDg1amFQcFlaS3p2WExFaDJLS29ZamltWFB4SFk3NElEU05yUTQ0emNCWjNrb29PS3hw +V21zYUR2L3U0bXBNdnZuREYyeDZBajl5STlBVjZmQ09PM1dBa1M4ODVVQ0pXTUl4clB6eVE5Qnda +TjJhZmZMeVZrYzhndUVudG1KdWIweU9HbDQ5NVpCbVhtejN2UFhGTDM3NDFYMGJKRmZ4MVVkOVAv +N3dHNHRaWHNGeit1SHQ5b2I2dW5DaHI5ZzZFUEREaHpyWWkwc1hySXRwc0NZK1B0VUJiamp0aFYr +ZHZQR0MyK2ZVek43MGhUemtaQzgrNWFZTzlwcXJQcXRQdnF2UDZyUDZyRDZyeitxeitxdytuMTJm +Ri8vaHpJUkdFVnhpTmlKRU1nZ0VWZ0lLZ2xoSitLbmVULzJLc3VSTkFQUDJJQ1dOYTE3QzRnREdq +elVKaVVQMGtyRFhIUm5iSEVNV2UvRWt3UThDdHFuLzdWLzN6UVM4ZXlqTUdqdDc3VlA0WElxaENO +WU1CSmpqRytsd2ljdjJjU2lnR0M0MmNzZU5YTVhGbWVKcERNV0FIejdZMzd6N3pqYko1amdIRzY3 +WUs1TFBudUU5NDdxRnd4bU15L053c28wNUJlTTN6UlB1STFycmZPRjhEeTV2bklZZm45V0dIM3Zn +NDU4UFhNSUZ1M21mK1hlZ3lCay9jdVhYbXhwK0NjcGw0Q281NFNNQ2RSZWZ5UEtXaXEyWXJzOEVl +dkpOTCtHTDJPV2x6M0NnTCtRUFgzaVFNNXpXeElYUEpSYzVFNHFEVWQ0T0ovbklrWDhDdFVjOE5Y +SWc4QXVMejNqU0QvcENMOEVxSGhISkJTLzhpV2ZPWG5PdVI0SHFjMXlFWTNtcWhmNmpOUndTS0V3 +NWlQU0VmdG8zbm9OQlhQN1pWNS9WWi9WWmZWYWYxV2YxV1gxV245WG5hOUxuaFRPRUJLeEZoaFk1 +dDQ1a1RvR1hTSVRHTWRBU1NHRDI1Z0ZFTnBEMm14UERHeEJFSWRTYWVRa29CTkI4c0JIRG1qZ0tJ +bzdQbXNKYkNrMFFnYkxYQUFyT0ozOXcySU13dUlsRkRFT084TUVsSDBWWFZFMzNPRFFNWDVwRDBX +Q3h6NTZOUFZqbDRiQkoweXE4b25randBNCtHUEJwdjd0bVZHZys5ckNaU3o0d2krVXVub2JuQ3c2 +Y3lkK2FmV3JHMWxyZUtxMHdwb1o1aStQd1lZTVhkM1B3dTJ0Y0kyS0FuWUJoVEo2dzg0RlRPZVlB +VUtjME9lN1VGUzc4OHV1eWh4OGN3Y2kzdk0zeEFiTTUvS1kvWU9FRE45YlVpWjFHdHVaNUJUcFk4 +V1BPcGM4TU1mMWphR0x3Zlh3K1lCR1RjUEVHdXg0aUROams1YXMxY01oWGZlQ0JWWHcxWUFPRE9o +STRRYkpWRzd3K0NoUmZlaEVmOGxRTHorSzU0ekFDeEF1c2NvY3RCeTVjcVlFOWNxdytxOC9xcy9y +RWNmVlpmY0pjZlZhZjFXZjFLZWF6NnZNaVlSUHVJVlpqTVRiUEtZY0FTVXl6Kyt0c1lEbjJXVk1R +cUtDYXdSeWdCL2h0U2VIVE92L0kxVXhwWHZZSWtEQkNQR3NpaGJDR0xJVHY1MG5lWGxnVlVvSDRa +SWRrODhRdGhzYUQxUjV2RE5nWm1vQ3RIQlZETEFTZVhENGJPSkVqWEd3Sm5wM0NLMVNhelVFR0Mz +dkZaa2Z3eUphelBTdlErWXhQeit6NVBlTzZUY0FuTFB6akN3LzRFTWNjTEJFWVgvSGhqUlcvYXJB +Q0hjSERJbzUxamNDM0p1REhmV3N6ZG55RXU4VTRzYXpqY09zMjljU1h4c29Cd0NkZU5SZis1Q2hu +ZUU5VG5yZHdhb0EzRi85c2tqUE96TUVObjFwWkUwZE5ZVW92cEtHVHB6N01vUU12NFJxRTZrMFF2 +bk9vdzg3dW94OThaYmxqajBkZlk0Qkx2NWlYci9nNGdJZU5lSGlDRlJhWXhCVUhOL3pheHc2WE1P +SkY3ZGlwcVNHbWVmSGlTMDU0MXR0cWdpdDF4U2Rkd01CSDlWbDlocnZxcy9xc1BxdFBvL3FzUHF2 +UDZ2UFo5WGxST0VJVUVMa1NJaTdrTVVpU05wcEhrRGwzZ0RXL0lhaW1FTVM4d05zRTQxTWk5dklu +dUtTQVI0aGsyQVA0MkZSd3dVREEza1RzWVRDeCtFOFRwb0FJTTgvZXN6dC8vQ0tmc0JVc0FrVU92 +d2lEeVIwK01SOUhjc1lQbjJMd3Q0MDhPZUVzNGt5ak9Falkra2ZCQktQWk5KUTkvQ3NpUC9EQm41 +RWNjSU5ieGVQVEhEc1k3Y2NCWEFxNEFoMCsrYlRuQ0dpb21hYnpHVmJZMWMvZUNCUW1XT0hEWDRU +RXQ4LzhzWFhISWIvczVXcVArc2hKMDhFQ0p6N2RIY2hzeE9SUFRmbTNiZy9jNnFjM1lJR1JELzBD +bDFqeVlydjVEWDUzRjJ6eXlsY0s4TTlmNmtyY0JPcHdWRGZ4NGVCWEQ0a25KelgzeHNqZENPOTRY +czRtUDdueExaZjBxbHp3UXBDSm5maHc2d200WVdZbnR2N0hyVGVVYXNCV0hQV1drM2ppODRlbkZm +emtDVE44OWxTZjFXZjFXWDN5VjMxV245Vm45Y2xmOVZsOXd2SE0rcnhJaEdQR0NtQmhHMkVLNDA2 +TUVvL0lPRVNTWkJHUkpBRXlyMGlJSVFxTkFnaWY1bFo4MDFBdWE0ckVMNTk4RTZGOUV1TmZvOFBo +ZTlxU3RJNGt3MzdrRUp1TGYvWnlTTkx1OWlGYmc5aGpJRUJlOGt2ajJtL1A0MENrZURES1RWUEF4 +VTVzdVNwZ2ltN2VyMEJtcTFBT0VjWE1Qdk40VmdSWThaQ0JDeHk0dzh3WG53cW40UGFJeVErOC9N +Q0ZXL2tmWHViZzJuSGJwcmNmUm5ieTFBRDJ5eDh1b3RONDR1TEhPdTdGdzEwRW9ZWnM5WUdHRTA4 +dDVBVUhEUHlHRHp5enR4Y09lYkxGdVhpR2h0WVBlemhQYnVvdVYySndkOURCb2FIdEZ3ZG1lYWlu +T3o3RXRzZHdKMUJ2NmRKTHdjNmZuSmIveVZPZDdEZk1POERGZ1RIOW12anlrUnVmdUxlZXZPM0Jr +MkVOdHRSTGJIeUpweDhlK1hWWFoveHNMY2UvdTl6aDFWUDRxVDZyeitxeitxdytxOC9xcy9xc1Bv +M3E4N1hvOHhMUW1oRVlvRG5YSUFnV1VOTllCOTRha20zMlU3Y2dHbEZ3ZTFKVURXZ0FhYS9BOWlv +dUlpV05jTGJlRHJqNFdoRlBBZ3J1VFFCU3pQR05SSVdXTkV5ZStZVVpGbmI4OGhPU3hlUmI0Ylo1 +cGpHQ2tVL0V5TWRudmg0SDNFaG5FMXN4TlljOS9JZ25sdnp4NDYvdDJmcU92SkZpV1U5UnhlTERN +KzVjRGhhQ3drMHdpYjMxbUwxOHFCVWgyY2VHOFBqRU1YL3lPK082OXVJUWJnNG9Rc1FGbjhFdm5y +MndtemVuRDJEUkpLbXBDMFl4eEl4SU5CZ2M5clBIcjhGZWJIMmdIcDdWMGw0RGIrb0RselhZNUp0 +RG0zK05LcWFlV2FFUFp2NzBoZHlXZzduMGtTSG1SKzk5ZWI5K0lCWWY1dFRWR3p1YzRFNXU2b05Y +d3lFaHZydzl3d2lidU1rWk4yeFNSM0U5NHdjdUEwYUhnalhQUktpMjhpZFFPT1RIbm0vNzFSUWVI +Tml6ZkU4K3VKUmo5Vmw5VnAvVlovVlpmVmFmMVdmMWFWU2ZyMFdmKzF0d2JiUkpRekpLc1lCRUJq +TFpLSzcxZ0xOM0JUcURvQVRqSTRVeGdFUzRmYkhYRkJJWFEwSUJENXdZYk5KTW5wTUVjU2ppR2Rk +TndHY3hDWU9kWnVVWHFjam1rN0QvNmNkL3Vua1lrbzh2UGhRUkJ2cy9HOWN0aFBocEJNL3loRlh6 +aUpXRzlhWkFVN2cwSFRzRFQ3aUFBeGYyV0JOYmtjNDRmNjJ2Z1RRSmYzTFk0by9BQ1VFdStJUGJu +UDFzK0JmYm5RK0RTT1JsUDh3T0w3bXBDeC95VjI5WERnYTFoa3R6cEw1N3VNd2RadGk5SWNLaGV1 +b0wvbHh3YjJQT2ZPTGpSMHpZWVkydzRNRXRHK3ZpNGxOZThoYkRuVDkxaFFGZVB1RGxoL0RFbGYv +T1QvNEdqTjc4NkIxOEVhSDkvUHZIMi9EWkk1WTY4V3Y0ck5mWTVxQ3dMajQ4TG12c3hMVXZIRVgw +YlBCdG56bXhjQ0JQL0txQk9YemJiNDB2OGRURXM3ajRVUU81OG1NTi91cXorcXcrcTAvMjFXZjFX +WDFXbjlWbjlXay8vOCtxejR1RUxRS2dZUVF4aHp5QTNCVkZrRGpqQktoTjZrNk9RckFYbkEwL0Js +QUlGOE9GSUFKMXQ3Yk5NV1JxVEJqNEFVenkvUE9wY2NUMmJJOUJWUHpCQVRkaXhORk04UEVIUndU +enlVZmZYQkt6TjgwVEVYdUR4Si9tTWJZb1ExS0VrZnpnNEYrZWlEUmdZTHR2c0NZZlRSS2NoSkpD +V3JPUEgzNFZtVGk5UmNBSnYveVkxMnpzK1lZWEI5YjV3eE9mN0h5V053N09BVFQrNXJLdStjVlZ6 +M0RxOExVUGRnMFlJZUZhYm55cHpjYUJaZktBeFp1TjhHZU9nTkpRUG91eE9mL2IrVWZhL0lvRkE1 +OFJvK2ZGT1RtckN4NWdFMHVlOXVzcGErYlVFMDY4dWJNbDBJMDNlUkNmV2h1NDhsVUUrUGxWQno3 +aDhqL0tUYzNWVTYzWUdYenhLY2ZGTXp6aFFueFlpUXdIK2dNdnNMaGd3UUVjNnJjaW5wcUpKMDlp +Tk96Qkx5eHJOL201cHg3VytiSVBKcldSaXo2QlRkenFzL3FzUHF0UDhhdlA2clA2ckQ2cnorcVRU +N2llVlovN0ZWd0drcGVzWUFJeEJnalFDRlJSL05RdG1EMFNaWXNnZ0JTTGoyMCt4Wm9oc0tEV3hB +Q09QMFFCSVFaNzg1TDJXU3kremVmQWNDa2FmNFprNHlQTkxHRUZ0bzh0ak43OGVGWXNRMnl4RkNU +WStDRlF1VHdPZWNHakNBUUNBNTl5a2E5OGlBRm5HdEk2bjVwRURJTXQzbEowdnRLczlrV2dtb09O +ZFRFVjErY1VUU0Y5RmwvandHek5SWnl3Yk9NYjR3K08xTlplK0hQWXdhNEo1SWM3SE1Gb2pUOTFn +TnVhbUd6NTl0bXdKbGN4UGFzNXpQZ0xCZzBvUGpzMVZBT2YwL1R5VmlkejF1M0ZvYmRjTU9BUVAy +bFdtSDNXRi9tTmJ1eXNzVEY4OWovcVpZZkhpRlZlZTFoTkxqN3JGUWUydDVZR2Z2UURQdkRBRDl4 +N1lEaXdwNmRnd3prT1laRnpjSHQyMzFxUDhHSGlHd2VHZURENGpFTWNlR2JEWHF5OGVlVXJPbEc3 +MU5DY05UWml3WWp6NnJQNnJENnJ6K3F6K3F3K3EwK3hxOC9xODFuMHVUK0FTaFlBR3lYQ1dIQi9y +Y29CcCs0YXpEL01SaENpQk9iSVQvRjhjQjdoSkhuQmdCRk00YTFyUHY2VHNLOFhSSndTRVF1NTFo +QXRjZnZOUjZDYTBuNzdFQ0lQOGIzRmNIZUk4QVVqMG1FLzQ3WUVheGcrclB1c1lBcVR3WSs4emZI +alRWUDh3VzVOUWRuQjdUTThMaHlkNGs3VERPbithajY4MkM4di9NVEc0RXR1THR4YVV6ajVhU3o4 +aW9NTHVQRWlUNTgxS0V4cFZFMlZnd2R2RVFKLzR1T0xEM1Z6NFZYTVBSQUdyNXpaSjZlRDgvYlNj +RER5aVkvd2JpLzc4eGJwdGdjT24zQ2xCb1N3SEUvREczb05yMkxvUFh1OGpZRjkvMHAvc01qSloz +amx5dDQrejNwUDN5WnZJdkg5K0JYM3hORm4ray9lL21EaHgrZkZNem43TEo3NFB2TURKMzVnNTA4 +K0x2RnhLVTlZcmVIQk16OCs4d3UzMmxnVEh4ZDZUKzc0NDBjY0I1Rzk0dVd3ZHRrYmdmSlJmVmFm +ZUswK3E4L3FzL3FzUHF2UDZyUDZmQzM2dkFnb01ISTQ5TFlBOFN2V0tZTGtFQzl4Z1Roa28xazQx +dHdJMTNBQ0FPcDU1MmRvQUFYV2xIeEpoajl4eGJFbmpVczBpQVZNa3l1R21FblFYUUtHSkJWZk1u +d2hXQXh2SHhSTlR2YTRZUFlHSUVQVHM5L21uRFUreGM2aFl2REJEcjZJRXk0NHhiUEdCdW00Z0lW +UHo4U09ZQU9uRGlEcmFWYk50WVVlcnZLMlJEN21WNUFUQjM5NHRnY245c2twQlRUSFpodHBhZ1dm +dldkY3Q4bllwNm1JaUk4MFE4VGtZSkNmcGlZMlB2akhHK3ppeUJQT3hmcWJYKzljbXA1L2Vhamgy +azNEczhHdFBMYVpod3NISnAvcUwxOFlnMDlPZko2R1B2K3phSWVhSFBqY3Zob2Y2aU5mOW5ES2ll +MDVGRWFnRSt0WFAvcjY5dXIyMXZqa0F6YjVpYTJuZlYwaEI3U1k5dWtQTVhCZ0QwN2M4YVRHZWho +V01jM0Q1QmsrTnE2dDcvUWdmMm9UWE9sQnNmalpnMmk0WkFlUHVxU2UvRWFnT2ZqRXJUNnJ6K3F6 +K3F3K3E4L3FzL3FVa3ozaHB2cXNQcDlSbnhkQkdkbW9TUlFUU0FZU0l3by96Zm90U3hxR3JXVHRR +enBuOWlrSTRFQXFqSG5DNVVmUStGMFJ6RHhRU05TVTV1elJ5SUR5N1ZjTlc5Y0lFV29hMTBDSVlp +bVVOVmo1UWtLSThyVUVleVhLSnNNZVgwbFFNTVYyd2JhWTcwTStNTVl2bjlibEFRUE1Dc3pPcy8y +YVg4UDd0ZHE0MEd5S29VQnk0UU1ldHZ6ZzdUVHJPUkQ0VVFmNFVpdzV3MGY0eTlQc0U1ZC84eG9M +QmhoeGJSQkplTUhScm8ySTdHVVBoNllqSkJqTXFha0RCaWZXN01XeEpwS1RYTmk3OHcxTHVQVVpU +M0R0bVByS1IxejFsSWQ2dU5pcmtSaGlzZUdIdnhYbzdJVkpML0RybVozYzVFdFljakxIRng0T3Z2 +UG1paEQ0VWd2NXNPVmZuUFNuZk5VQWIzb2JQamJwZVhIMXVYcXpnUWtYOEl1QlIvV1FHMTVobFB2 +bWU2K1orcW05SVJaY3krZkVzRWV1N0syNXc1VjhZaVBINnJQNnJENnJ6L2pGWGZWWmZWYWYxV2Yx +V1gwK3V6N1BENkNUREtBY21wU2dBQnJMdWlieUV6dEJtay9oelNFblBvQUVHa0FGOGhNeVV2a3py +K0czaUZQb0pYTUlSRXdLaERqSjh1Mm5mT0RUMVBaS0JENERjZVlsRlNGckpqOTVJOWMrZVBreWIy +OEdvb2dYQm52NWtNTVIxUm44UjFTd2k2dGdNS1VJT0lQWDNnZ1hzWXFSdHdOOHdxNmc5dUFKVm5q +Z09PTjZCRFRyK01TZGZjU3FXR0ltYnJqY0lzN0ZId3h5MEZqeEJ5c2NjdEJZaTIxNHdFME9CN0hV +SEI0OHFiZDhOUS9NN01XQVU2N0pLYnp4c3cwMTJIRnc0dC8yZ09EZk9qK3dPOXlKU3ovQUo3YTE4 +Q3Mzb25YWjZ3MmhlYy84cDNIaFY5Y1hnY3djYkFaTzRkS2Zla25lK1BFNXRZUVJMa0pNL3ZtTWIx +ajVaTTgyYjhWZ2dGZE1YS1VIOEl5djdCRUROdldJUU8zVHEzb0twK29yTmk1d3psKzR6TVd2K05G +VzlWbDlWcC9WWi9WWmZWYWYxYWQ5MVdmMStlejZ2Tmlza0pMa0FJbkFFaEtuakFEZ3lHY0JnRWhR +aEFxQU5QdVJoRVNBcmRtdnVTVmtEOUF1WkNBdWg0Qm5iMWI0OTh3M0FpVGhNeHUyRXRjRWZDQlQ0 +N0ZSaUloWGNlSlhQaHQvaVBXVHZpRTNaTUtxRWNSQkVLd1pzQ01hV2ZabWZRK0N5WTFQTnBvWFgv +emhnVTlYQms1Z2c1V052OVpQUVU2ODY5cDVsb2VHMEF4OHlFMmhsdTlwTm5OeXdpWCszT0ZSOERS +c2huMUVZVjFjdnZuMU9ZZU5QSENDMDBlTTF1UzlCOFRVZzBpSXptWGd5M2ZCdDFFbkp2c0lkTVV5 +RFoxZTRGdXU2aW1HQSthTTIrNFJMLzJoejlUVjNuelhIQWFYSENNQzlZWEJaempWd2ZEc1RaZWF5 +MFZjK0pLelozZjlDVXNPS1hicGVUenphdzkvRGlWNDVDZ21HL2JMNTNDVHd3dEdPZG9uYjdiOEcr +b25KcHpzOENSWFdQTHZUZVR2VW5OeHhXTlhmVmFmMVdmMUtYYjFXWDFXbjlWbjlWbDl2aFo5WG16 +MnBzY0dZRGlTSUNKdFBvUmVOeUYyN216c2t4UndpbUROWFlLSTBHajhKUmhiYTRnMnJFdkNYMFc3 +RStmSFAvbnpiU0tBK1ZkQWwzakF3MlhkNENkazhFM0U0c25Ec3lJaVRFRmRDcVlwRE1ScDZCdzJ4 +R3lQUXV4WUlWLy9uVWdValgwSzZabTlBc01vTndkUVltdFNRMU5iVXd3eGZZZWJEWTVPWTgxaE01 +Z1ZUbzZ3ZWJhSFgveno2ek9lY21qQUl5ZDROTFU3ckNmb3I5Y1hUdm5BRXoveWdRMkhhU3EyL09Q +SUh2VzFSNHpOYytLc1FLZGVMdllhaHgvN2NJTUQrYmlyQWY3NGpvREY1aGQreit0bmhoaTQ4QWVF +TlRYVjhIem9DM2p4TElaTGY4bEZQL0JESk1RaWY0TTlUTGl3WDIxaFZWOVlrcU83ZnVVVEQzendD +USs4OG1adkx5d3dtUlBibkRWeDRNVk43bmpqSzMrNDhLK1A0cDhmRnd6V2NDdDN2WUV2K01XWHN6 +VjIxV2YxeVgvMVdYMWFxejZyeitxeitxdytxOC9Yb00rTEpCa2hYaERCSmFnaHpDc21RbjNXRUln +R0ZGbklWUXcyUUFFQkRHTE1hV0JKdXlSa240U1JBb1NHVW1nWTdFY3FZTEhYakxENEtnTml6RVdn +aWdLTFdNaGpCNzhoU2RnVkpxU0pqU3lEZjFqbGtLWmhmeHJuMkJDUCtMQWhMNlFoVVJ4NEZFWStZ +aURUSGpsNVN4R0JzdldtUmhIRTgyOE40T0p6RDRUaGp4OXg0Q1pvdWNsVmJ1TGhQV0xrUTc3OHlR +bXU0SEZsNE1sK2VhcVRYT0RFTVIvd3FZUEJ6emJkL1ZDd2o3MW0wa2ppeWZNY1lLZmh2TUdCeHhv +KzJXK3ZESDdESEU1enFNQWdEdC9pT3BUa0t5YkIySXNIdWN2SC8xZ1hKbjRpVWptelZ5L3pXNy9C +Y25DZE4wN2lpS0dmeE9VVDMvakVJZDlzck84Qk5mdXMyY3VXUDNYMldVM1VCMWJ4L1FHaHp2WnZM +OTU3SXV0OHdpYU91cG16bjArNXlGRjhlY0R2YmcrZmFpZW15NXJQK0trK3EwOStxcy9xMDc3cXMv +cXNQcXZQNnJQNkZQZlo5WG1SQklkblhMZUFpQlRJVDlFQ0lNQUdZQVRhSkFaY21sNFJQUE1qS1FR +cG5JUUYxN1FTUXE0Q0JQVGpaVTB6MmVmYXp5TkErQkFzV2VDOWhURDRnU1dOeVI0bVpDRkIwVjNp +S3VTU3ZlTTBtRmdJZ0E5UnZwTnZiNGJpaVc4TmZyYml5REhOQkF0UjRzckZiMFFoam1IL0oyLyta +ckhLUldNckNwLzhzT09EZno2SXdoby9QdU5WVFNLeXJjUHN3eEhzbnMwRnA0WXpjSUV2KzhUbXgw +SG5LeExzQ1JTSEJDVlArYnZqVVk1aTQxR3ppQVVua2JyRDRSRGlYM3l4NVA3eWhtZ0dMdkd2TjNB +UGcxckk4eHdNNTN2OHljc0ZQMHo4L00rZi9lWGl3RWN1Ni9EeG9hWXdabys4emNrenVUdEE0Q0Fz +Ynp2VCtQNVF3QjNlWWNsQmI1ODk3ckFTV2VvalR6bkFaaTM1OG9rbmUvUnBjcFdQZGZ0akx3ZGN3 +aTEzL3NWMmg0ZU52ZWxYZWZKVGZWYWYxV2YxaWR2cXMvcXNQcXZQNnJQNmZBMzZ2SGpnV0pLYWpq +UEVTTkQzeDgwcEpvQ2FGa0ZzN09QUWNCZEljb0pyd0NURkYxTHNXL3Z4WlFEaU1pZldwNSs4djhR +aFZMTjV1NkRSWXljaFBvamJpSURFUUM0ZjVod2lDT01YZVJwRWdkSzhHb2hmaFl3NDNlVjZHdWNN +T09TejRwbDFwQ0VNTG52RVJYZ09DaHhxU0lXQjY0emJZdVBIM1Y3L09Kd3YrWndDM3BaanZNa2hl +SWhKdnJBU0EwNzV0VmRzKytXYWcwSTk3STFBdDFaVEo3bTc4dzhqWC9iSjllUjdXOXdhV0swODI0 +TTcrYkVWUzkwaVBySGt3aTg3emFuSitGdWJzWVdKcjNDc0htemdPbS9pemlHcjd0WmQ2c1lIZjM2 +VnREVi9PTUNSMmo2SzNsMThOWklIYm1CaXB5YXBuN3h4eXk4YlB2UUR2N0RhZytzY0FIenFEN1lF +Qm9NMThmaEwvK2hGUHEycGd4anE3NDJnZXFlZmNDVjNoNkVjMVVJTmNjTGVaNzNGQmpaM3NmaXRQ +cXRQdUt2UDZuUHRxOC8xVlgxV256aXBQcXRQOStyek9mVjVzZWluZEVBRTV6Z2tLS1pHbFFTQ0NJ +Mjl4SUZNVWN3QmpEUkpLVGJ5RllkdmdRQ3dwamoySUlFL1RXZXY3OGNqRWdiK05HZ09DczM1Z25N +RmZsMDg5aUlaRHNWRUZyL1d6SWt2RDI5bEVMSmpTSTA0MCtTZXhVampiSXl4dzBYRXpnWmV6K1pj +Q29jZk9GM3lGeGMzR2R0SXN5ZENFVnN6Mm5zRU5XTmk0ZFRGTHgvd3lOYytCVmJJSEhaNU8rUWlo +SEJ0RFRkR0RsdCs3TVdONW5NUXVZdXRZUXh4K0RadkQveGJzL0Zwbnh3SmJ3L3JHVGpWaE9yRkR4 +dUNrTk55T1B5cGc5aDR4N0hhUjRUd0dyQjdDNmszK053RGIyS3B0VGRaOXVzSGRZTEYzdlFGLzdp +MVZ4NHVQdXcxcndmMUJ6c2NlbFlITnA1eERCdk1mRzlkcDIva0Fhc1k1dlVxVFBoWUhpY1hmNUJZ +VndmejhzQVBiRGhoeHovc2h0NzBHUzg0NUdNRk9sandMYVk5K2czMzBaSDRMMzFmZlZhZjFXZjFX +WDFXbjlWbjlUbHp5L3ZrVVgxV244K3F6NHVtUU1ScC9QTS9qRTJqK1VlK1NFTTZwOFRDS1dBY2NZ +Z1l4QXRnSFdBQ1h5Q0tjeGVOZmRiNGNpSEc1eFRvemJ2djdGL2xTMVpEYWdEcnNQbnNHZFl6cmt1 +YU9IQ0pyYmswS0Yvd3dLSVFXL3doSHdZNUluTEpuM2w3a1NCWHBNczEvbkVoUDc3RWRrY2FNcmRw +cDhrVVFCNzJ3aXVPZFExaFdQTlowN0JSREdKZ3AvQjROR0F5WjEwY2NRazU0dVFEWGpuQ0xFODIv +T0paemN3dHZ5dTYyeloxNHNYV1hST3BIUTV3YmZDZGVYRTNSOXpNUE43a0FXUHdxaXNzT0k0QTFj +azZPenpqQ0hiNzRYTmYyN2tlQlNwSGZvSkhYV0h3dFF3NDlJcGNjTThQNGRuRGo5ckJLRitEalp6 +MWIzcFJMQUloSE90NEVrdXVNUExQWG4rcXYvMDVjUGlIQjZmbTRqdC9nSW05d2g1YzR1QkFMOEtM +YTRJM3hJTkZ6YTF0eldhTkQ3aGVEbys1NE9LWHZacFZuOVVuMzlWbjlibjlVMzJ1WGZWWmZWYWYx +V2YxK2R6NjNCOUFPZDBrLysxZmQ4Rm5UdjJVTGpDQTVqblNpQUlrYVFQNWdHc3VBTndWU2NFRVE3 +aExrcG9mVVBZS21MYzNZcVVCRlNBRXVZc2x2ajE3a013RkUreHNGRU55WW91Uk9YN3NlU1NMcUdC +QmpsaXd3b2owTk03R0dKejhzRkVraGVVYkwrWmM4dENRU0ExK1JmSm16VkE0aDQ2OWNvVkRnOEZr +TC85aWFRd2NKb1oxaDBieXdMdm1VSmR0OE1HRDI3Vy9ON3djWEVlZzU5ZFU0NGRBWWNZUERMNTdI +dEd2bUNaUGpXdU96VGJ2eE9NM2ZCTG9IdEpidjlzMkRnN1R6R29qTnpqd0t6ZDhxeHZNOExLTHFN +SXpRY0lubmp6MUgvK2U4ZVFlWDNJVkI3ZnFDak9lMG12MnlSL2ZzTWtkSm5pSVhWM000ZFc4M3VY +VFNENzh1K3R2dGo0djFzR0ZDL25hTHljK1lGaWJ5VTh1T0dVakh6eml6T0RUdnRSTlB1cXRYMXhz +MmNqWE01LzZDeTQ0cXMvcXMvcXNQazk5cTArWXFzL3FzL3FzUHF2UDU5Ym5CV0UyUzBTaUFrZ1V3 +RFNhQWdsdU0zSVVkdDhvREJBRWV0YndrZ0I4aFRRREVIN3N0VStjQ0JRNUV2MWZiLzU2aStTdnpz +VTFyemoyK0F5MDV3VzlEV0tjcjFPa0NNUWxVZkgyVUppY0ZFVFR5Y2U2ZWZ2MkxjQ2RGTVZGQWx6 +czlwQ2FnVmoyZkcvY2FheVFDb3MxYzRxQ0crUnFBajd6dHNpQVY5N0lkaWRrQjVMQ2lzSGZOdUFJ +Mlp4OFBjdFpYRnp3alo4VVVHeE5nMnZyOXZrc2grVjNtL1hFSmppNTQ0S2RPdVZYVitQdUNPTFlo +bi9jd1lXbkNBTW1PRFd1Z1JlMXd5TjdQS3I1Q241OGFrVDRpRUFzK09KYm4vQUhKOS83NWt5akR2 +L2JnelBZbVU5Znloa1dPUkM5bk9UR0YxdlkxSTdkZmdWamZNbjVrNCsrdVpqNXdUME1lSVhYWHZZ +d1cxZFhlTlJRTEh6NzdNQlRLN0hsSTI4OUU0SEtoUTg1bXpkbkh3d1JxUHJCR3c1eFl4OE0vT0Zv +ODVuTHZ2d0JFTTdDUS9WWmZlS3UrcXcrNVZCOVZwL1ZaL1ZaZlZhZno2clBDMEJ4cHVHU0FQRnBL +b0dSSkdHZ0ZKWnpqUWFRb2ZCQXVUUW53dmxUZUVHQVJ0QTJ4UkM1ODBPc0puQUlzUGZybXlXZ09m +aEFHSHMreFFWZVlobUVZUjY1RXRRZ2ZFb1k4WkozSVZwc2MyTGJaNTUvc2V6bjI1dUZFR3J3cHhG +Q2x2M203T1dEUU4wUkRyL2l3Y2VYZ2hqV0hSSTRJT0NQUC96R2ZwYVQzRGVmYVZUOHdnNExEdVFq +Tm54eWRHRGhtMS83eE03QkFSTTd0ZGg2ckVEUDJ5MTc0OHRuR01XM1IzM1YyOUNRYWgxNy9sejh1 +OFNFYXdVNEEzZDQ1UThmNmdsYjZzUFdIRjlxajZOSGNZaXI4YzJwZGZJUWg4RHhvQjdxajBQY0V6 +bmU5Q3VjL05yam5ycnhiNTkrWm9jM1dNUXlCL01LWTJwRXlPZkFPVytrckltREYzN1VWQTZHL2VF +TUpzOHVmdkMwZmdZM0hzM0xFVWM1Y0dBeHJ5N2JPOU5QOXBnVG0yMzZWZi9pUTczNHFUNnJ6K3F6 +K3F3K3E4L3FzL3FzUHF2UDE2VFBpK0o1SUVUZ1BXZWpuOFk1NVV3eEZBRTVpQ1hLTkVRYUdWRVNC +OXlRSUdMczBSaUMvL2E4MzVvbGFZWGlDd2JrOEFrRHNnQkhvQmdaQ2dZUDNBcktGNSt3UnlqSVVF +Q0hDL0lNemNHT2J3VkhCR3diLzJISURiRmk4cEZyNTRaQWUva1Nnei9mOVErT0NGU0JGRlcrMWo5 +Njc4dUxSVkh0bDVzOCtCSEhaOE02L09MbG9JU1JYL0dzczVXbmZEVVdmMkx2R0I3RmxKdkR5WnVT +NFBKR3pwejgxVmJ6d1pBNXRaQ1BXcWx6eEtHMnArR3UyeVBzNVpLRzRuOEZQSTBxL3ZiSVlKWkQ4 +cFVqWEtrRjdONEUyWitEejM2NXE0YzdidVRwbmtPSUx4ak1iOTlORG9hMW4zM25DOHVYSHNJZDBT +Zis5dERFWXFjZTV2TUhCcEhBRlQya2orUWlkM3pqUnI3NFNUK3dvUSs1MjRNWDYveHRQak9XeTZr +RjduQUpnejYzbjYyWTRYMFBoc25KK2lPZTZyUDZyRDZyejkrZXJ6NnJ6K3F6K3F3K3E4OW4xT2ZG +WklvQW1FV0JKQ09JUVFTS2E1NGp4Q0ZmUXhvQWNJZ29EYXc0K2NrY3NZRHc5MGlJZ2hDOU55Zjgr +WTY1a1dLWlE0bzlhWnpkUDRTNVlJSWRNWkxuRXo1N05BdE05aUNNTDNqczBXVDgyNE5jNUhnKzJN +NXYyREp3QXAvQ3ltM2pEWG55VUhUemZDa08zbkRGcjF6REN6dCtERmprNm8wWXJOdXNrNnZpSzRT +bTRWc2QrRTBkOEM1dVlzTVIvdXh6OFFPUFBOT3NNT05CazRwcHI4dkJDaXRPY2JKalJPRnpmTXJC +TXk1eEEwK0ViSWpIVjJvdFIzekh6b1VUL01XUHo1NWhndCtRc3o0U3d6TysxQlovQkFySDVqdnh4 +SkNmbU9yT2o5ajJKUStjZVVNa3JxOWlFTC8rZ05WZUdOV0xmMitQNEZKei92Y1BtYm1yRXk3WjZB +Kys1UVcvdGZTVTJQQ1o1MU1kb3c5N3hkUDdocmpXMk1nSHh6bVU1UzRYT2FWbitZYUZYZlZaZlZh +ZjFTZC8xV2YxV1gxV245Vm45ZmxhOUhrUmhLR2hxRUQ1Q1R2QzJma3hsRFF3Q0VLMHR3TWNHeHl6 +QmRTNmVXQ0kwVjZBQlFWSzhiWkJCd0FmTHZFUVpQQ0RVRDRVekUvWjFpU1ladWNYY2Z4YVR3UFlB +NzltMW9ocGJQTnlSQTZ5eFRBSGs4WlVaSGhleGpTc3dzcUZYN0VValE5OHdiV2NUVFBKeVg3K2lC +emgrRENzMlM5blJmVVBwSW5GUEs3Y05ZZzdMRGtRNU9tem5JbkpoUU81c0JVSERyWnloOTBjZXpr +bU5oNWh3UlBmOHZIV3o2VVJqdUJ1dXdjK1hDUTJqc1NRMi9vY1R1SmI3bnp4SVQ4Mll1RmxCVHEx +aHluY3c4bE8zN2hneGE4MTlUY241amI2N05lc3ZwSmhUcjNZODgzZWdZVVBlY0hIQmpaRERIMnJC +dXJ2TFpEOWNPUXJIckRESFR2eGNJZ2YvS1dPTUVWNDZzLzNDbjNtK2NNRlgrTHZIMDUzZ2RxUEQ1 +Zjk2Z3VyejJMaEhCLzZGa2VlN2RrRGZqaDN3YVVPT1N5MzEyWlVuL2RSZlM1SDFXZjFXWDFXbiti +RXJENnJ6K3F6K253bWZWNEVrNmhFQkF0NHlTa3NJT2E5YVJESVhRSUt5aGx5RkFzQWRyNXk0Tmtn +UklFQjBHQVJHQkc1VmtSVERIdVc2Qm1hWklFaVl0WWtBS040Z0dzb3dvQlhIREhNS3lCN3BPVkFR +UmhTNVlkRWhMQzFUNTVpc05Hd1ltVHdMUy8rNUlZRGR2aXdqeTk0OXhBYU83eXczZm54ZjNBT3pI +dlJZRmJnWC8zbzY1dUh3cm5ndElkL254Zi9ZTVNYdk9ERG41am14REVmWWZLWjNQaFNGMDFnNE5y +Qkp4N2Mzc1R3SmRmd3ZRZkVjRWtzYWlIWGNPS3VEbUxFTGtLUUgxN2Q0WUdQYjAyWCtxZ0h2T29K +cnl1KzdjUHhpbmh5aGtVK09RRHNnVk5Pc0luaERvKzZXcGNUbjN0UTNuSEplYjhqcjErbVQrVnNu +M2llellkWGI2RHNOK0FnWGx6aVdjNXdFaE54eWdjV2Vkb2pMNXl6dDFjdUx2amtvdzZ4TVR5TEdh +N2xpdFB0eDhrZHJ1Z2s4ZkdYUDJ5cXorcXorcXcrcTgvcWsyLzdjQ3hYT2NOU2ZWYWZ5OGZZVnAv +VjU3UG84K0k3MDVLT0Uwa3FsZ1QvK2FkL2NRZ1l4OGczeDVGQ1JtU2MyV3NPY2NENXFaZ1AvL05k +UkVxV0g2RFlhRVp6L0svSTcwa2d3RjhyRTdPNzVsSnc5dGIvNWVkL3RYRzhhZm1uSC8vcEVmd1VS +OElhUmt3SitwNDB3ZzJZOXpDWjNQeTFkQTRQall3Y0J4R2ZPRmlSS3ZhOTRFajNSc2VCeFMveVlY +UWhXdkVVVVh5WXhXR1R4dEVZOHVTZktHRGsreC9mLzVQOUI5dWFCWi8yZUlNaEJsem1OYWhML0Rm +Zi85TDZ4emUrK1BGWms5blBYck9rMlBBNEhEUThmT3o0V3B2QmhBTTVpYWtSOE9oTmplL1NMdytE +eDJkMm51VmdzT05EL2hHTFEwRHp5aGxmYkgwOUlEYnFMMGIyVzhNSE8vbnlaNGdWa2VncFE3N3Mr +SllMYnF5eGs1YzU4ZVhrTXgvNTdIa1B3UkVKcnV5RFI0NTZDVC9zOUVUcVpYMFBpdkZsSDl4NDAw +TjQwRVA4eWtQOTNYMjFoZzFidmYyOVAvOVBiei80NXUrL2lNNWU5Wkl6YkRqeldhK3dGOCtob2ov +MHJkNjJUeHpyMVdmMWljZnFzL3FzUHF2UDZyUDZyRDZyVDgrdlFaOFgvM0JZY0tBWks3YUVYRGI2 +S1JaZ3d1TVk0V3dsVExTYWdrTnJMdlBBRXhqZjdvcGxQMEk5SXdiZ2ozN3dsUVhDWGp6RUkxQnlH +bE15eGg0T2s5QXZQL2phWXBUME5yakRZdzREL3V4aHp3Ky8zZ2dZU0VlZ1pPVWpuZ0dMNHRzakx2 +SldvSE1ZN1Z1T0dScENveXFpbUd5MldPeUllR3o1U2RPSXdUOU9YUHpqaWc4RmtMdnhpeDkrZGZG +YjR3dEdCdzRlRFRYQW0zM0U5Wk52ZjM3ejByQmlXRXVoVTh6VWhoMS9HbkRGUHZWekVNcEI4L09o +SVRXQ2YxQVBQNEh4NTdDU0g1LzR4eXZlY1c0NFlObkt5NXFjcmVzUG1NVVQyOEdjL0ZOZmg1bW16 +Nkd0SCtTUmVzQWxWMzQ5RzJMNXJQbkZsZ3VSc0JQVGdlQVBtUFFpZnZHWkhyWHVHWDc3NUM5SEdD +TXcrUEZCekxHQjB6cWY2dWJDTzE4dXNlVUtFMTdoc2QrYUhvVkovbXJCUDU4MEZvM0kyNW80N1BT +OFd0anJ6Z1ozOEZTZjFXZjFXWDFXbjlWbjlWbDk4bE45VnArdlJaOFh6V0tEbjlZalVzME5FR0Fj +SkJrYmdCQ01jN1lJdHBlOUlpTmU4d0xHTjF0KzNEVzBCa0NvSkRRTy80amtXMEhXLzVEc2JaQUdO +UlFHc1QvLzNoZGZ5UGFXQmJFYWNYKzZuemt4MlBsSlczd0ROdm54U2FnU04rUmpuMlowV1V2aE1q +d2pmeHQzL0VXRThPZnRrQWEzejlzcCtjdEhnK0hGSG5zMXBWL0RqVjhIQ25Fb2hrTktiQmpGNGRl +UUx6d3c4L3Vqdi8yRGJUNmNLK3dlRk9OYnZueW1NYzFaUHdmQitYOUp5Vk44SExDRkN3Y0VxTTRh +RGFjNUVPSGhGeDZ4N1lsZ05HSEVxZW5FWW0vKzJOd1dnNE1RRDU1eHJFWjR3cG1lVUZlQ3RzNlBJ +V2Z4Y0JLQjJNTVBIL0tRajMzc3hGUi9PR0VrRm5qWTgybU92ZHpTbitva1R6Mm0xL2lWTXovOHNv +RkRMbUlUcURkcExudlMvN0RKUlY1eVVXUDd4RTV0OE9ST2JPcHFMOXpxSkVkM2VjS3ExbkRvYndK +VkYzdmtVSDFXbjlWbjllbGVmVmFmMVdmMVdYMVduK1plZ3o3M0s3aVMxSHdtQ0V3UXlkZ0VMRUNB +S1M0YlJMQnpBWUFBYzU0QkE1NC9iellJa3Bpc21lY2JLRDladXhDSURJRDVCb3d2dUNSSTdEQlkw +OWlhaHc5LzFRMlRwb0FqdURUSGgzLzNoMHVDZ1hRSkk4KzZZaGpJaDhPYUprTTB6UHlkY2U1SWRa +RHdENk5jWVV6VDRRWXZTQ1lDL2wxdytnb0EzM3pnT0Uwc0R6N2h5V0hJaGgrRGYwMEJqN3UvOG9i +Vm14STFzUWMzTU9IVkhBNElFNTh1d29QTlBrVVhIM1l4Z3ZjSStYem4zVisxcXhVc01PZk5oNXps +YVd4ZTh6a2lUV3ppSUJneGZmYTJLL1ZnQndlKzVHK05iL1VRNTlSd0JEb0NnVWx1R2xmZENRZ1hj +cE03d2FuYjFuUDh5bE04c2F5Ymh5MmkxRzk4aU9FejdQSno1YkN4aDdqc3dhbFlzSWpGQmxhMUVZ +OG1jSmVEbVkwMXZZaG4vdGp3NFJrMkhLelB3WUZmbk12YnZFT0hYNGVHM3ZtSDcvN3h2aTNDQTM5 +c3E4L3FVNDlVbjlWbjlWbDlWcC9WWi9WWmZiNFdmVjQwcGdRWmNwcW1UWVA1N00yT083TE14emtD +Sk0xUndLOEF4cDZkTnhPSytPYmRkOVpPUWltS04wREFpTG5rVHFLSVV6aitFQ05CUThHUXgyZWEy +Vi94cDhFUVlGNnNDQlFaQmgvZVVGbVRWd1NxYWZ6VVRnQXBzbnhoUDhPdnJMNXVrMnBlK2NtQkhT +N1NMSXJGajBNRHNiREF5MWIrR2tOY29reHNHQjFhNW9sSGZPdDhHUHlMS2JicncyOTliblBWNEVR +ck4zY3hYRGpOZ2VxdTJXQWdITHhvQURhd3djQkdydWNRdTY0TkhPb0tiemp6NWdOR09ScHFaWTk4 +MVJvWHV6N05pZ3VpNHNQYk1EVzJEZ3V1eGZITXIvemxBN3Q0MXZTV3VzcVhRSWpkR3J4NmdnODQ1 +TVRINWp3NFlMVlhENm14M01TRkZTN1A2VXM5eEtjN2YzcDR4VGYrY2M2bnU4OEdnY0txRi9tRDE1 +cStkK0NJU1ZqdzBJK2MyY0ZJa1BxQnZ0VE5ITzV3TFE1Zk1PZ2puSW5oS3owMHdhZmM5SHYxV1gy +eXFUNnJ6K3F6K3F3K3E4L3FzL3FFOVRYbzg4STVrbTFFb0V1amNLSjUwNHlTc2VhempZSWlqSjNB +Z0p0bnM4S2RaOGtyaE1DSU1vOG9pU21FZFg0MFUwU2k4UUtZYjBOc3hMTkRnQWJWQklnM2tBRy8v +WnJLWC9YeVo3aHJQa1JaMDZSYjdDbWUvVEFvbVAyNDBHUVo4TUtCSDc3VFVISmtKMytYM0JVSFhu +ZDQ1ZUF0Rjl4NDlGZnpHc25nOC8yLytiM0ZwSWpXWWNTWlpzVUpMTmJ3OGRPLy82UE5PYlhoMjd6 +OWFVcTQxTUZsditaeVJYeHlUQTcyOE8vWnNKZUljYVZld2NXT3YrVmtEaTdjczVVanJKb0lEbnhx +UUcrL1lITzRzR0VMUy9ialJReTFnOHNGdXpWMXdaOHJlL2hYY3ozakxZNzU5STQraEFkM2VCSGZY +UzNVR1k3NGNNRWovL3dod3k5L3hHRE9QbmoxQkN3d3NmRW1UNC9Bd0tlNGVzY0JJcWJlNE1PNnZm +dzRyQnd5Y09KZEhXaG9ENldKalZlNW0zT2w5MzcxbzYrL3ZMRmxpNHZxcy9xMHQvcXNQcXZQNnJQ +NnJENnJ6K29UanZoNFpuM3VWM0FGUUJ4Z21oMFFtOHo3eVZlaklpWUZGSlN0d0pMeUdRRUtpd3hO +cTZFMGszbHZjL2hER1ArSTBhU2Fnbjl6a3VNTE9ISDlwTHhFelJCbmhUUSsyV2thZGhKZG4wTUd6 +UEI1ODZHaFBSdjJzSVVOQ1d5SlFFN0kxb2hzeFVUMkRnMDFReEhrb01IRkVnUEJDdW9OZ1VMSUZT +YU55bzhtZ1ZmaitLdHJKQ3Z5bzBENTlOYU1EWC84YTFwTmFHZ011TzBWMTRHRGQvTjRzTis2bkRT +ZDVoRFhNNHdhaUdoOEQ1d1BPYklsU0RZYUcvL3d5bEdOTnYveHpZYTkyc0NtYm56aGpKaHdwbWJ3 +cURVY2VEZ0NmYnQ3MVk0ZmZRR3p2VGpWUTd2KzVueUhuZy9jRVRadWNDaW11bXRjL3RYTFp6RWlL +aldXZzd6Rnp4OGdjdWRQWEJlUmlZTkQvTGowR0M1d3JlL2tqQk54SGcrVjlMeTNmSENaYytkZlBI +V1hzMXFvSXp6VzJPa05zWEFhZit6NXgyZnFzTi9UbjdocUpDZXg5RW9FcXMrcnorcXorcXcrcTgv +cXMvcXNQcXZQNnZNMTZmTmlRakVrSVVtT0pTZFpqWE1JbktZWlp6WXFqaUFJNVJ3QkxnRTVkRmNN +eEFIT2puOStGSHFiYS93alJvTUFMMjVJNUFNNEI0ZG1NQ1RuS3cyU1VEUUo4cjNZSGdRS24rSjcr +OEtQSVFiaDhXbi84WG4rQVROU1EwWWE5QXhmVDdpOUhEU3c4S2V3UHU4aE5DSlFRRVhDaDcxWk04 +Y1dGblArMGJpdkpKeG1QZCs3MzdjKzkwWWlYci9aek42OG1aRGZZaDliQnh4eDRFa3N2cjBGMGV6 +bThhYlpjb2lJcjZFMWg3YzFPTmNjL0t0UERpYTIrRE1uRHA3Q3JXWVIzK2VEKzdiKytVak44UVlM +WFBndytJWE5tdHdkVm5JU2h4LzlwcThJUkM2cHNUN2d5eHc3VGE1M2lCUFBFYWg5RGxyeCtVNHQx +RFArNE43Y1poQ3gzUG5YdzhsUHorQUhSdXRpT0J4d0lZYjQ5dmpxU1BxVVR6MmpQL1p3bnJqMjZH +L3pMbHpvYzNFSmp6OTk1TEt1WHVGUVRINXg1Y0Iwc1BrM0JHckJ4bnoxV1gyYUU2ZjZyRDZyeitx +eitxdytxOC9xMDNoMmZlN2ZnR29BUVphQUtmU0thSUM3U3dTNXdHZ21oRW5JZkpKQ2pMc0NScUIr +bXRhZ2dpaThKdkpUZHhxWklGd0FLZ1NBYkloT2NocEV3UXdOUjZDSVlvc28vKzhnc1pDQkZITVIv +Zy8rKzM5WmpBWWY1dGlLa1FQSFVBaHp5R0NqY0JyeGpOdjY1VlBUMlErM3ZCUldBWEd5VFRLa3Mv +RkdTRnh6dU1DcE56LytrVFdPemR1amlZaVNqZmcvL2gvL2JXMXdhR2cybk5sdm43ZEo4bFpnTlhG +WTRNS3pmUERtNElTTFQzVnhXY2Nqd2JCUnV6U0p3K28wOGZtT1BCeDg4aUVYZlBQRnIzb1NtWWJi +V2srVDUwQ3dSK1BqdzREYm5CelZQbUtMUUIxVVl2UExqMFkxY0kxYnVlRkpYY1dBMjVvWTV2VUdy +UHBHMDZ1ZGVYWnl3NU5HbDd0aEhaNEluWDgxNWRPYVozbnhaei9NbmcwNStIcU4vZWJnUzErdDZP +NENkV0RZcXhad3lqVUN0UzV2dHRiRmxETzhSQTZUM3RLbjZvMGZ2U2lPK2VxeitxdytxMDk0cXMv +cXMvcXNQcXZQNmxQdXhyUHJjLzhOcUNLNmExZ2JKUUdvaGd4WVRpV3R1RFpLR0VqRkJCd1F5UUtB +WEhNYVZzRThzMTJCem1mK3JTbVVab3pvK000QjROYzRTOUNRSEZ0dmM5aUtKUWtrR01pRzM3eW0r +ZTZmL2M0U1pzQzNhOU1NbnVGYkVRNko5aW1tTlhlTnZFTkQzdDhDV0lORDQ4b0RCN2lSaTNVRnNl +YXcwZFRza1M0SGg1KzNJWElsUUxrWm10TnZoRXFERUxIUDlocjR3NU5jNU9tdEFaOXF3MGJqOEkr +ekZkejR5Yk83aGlVS2pTVW4yT3pCRjQ3NWhVc2REWFk0VWhkK2ZjYTN2SWlFT0dHU040N3h5RmVh +M1FIS3hwVjRNSW1scDNDRlV6M2dEVkhxZ2RjOUFLWWVmTUFERzE3aGw3UCtnOEVjREhLRUxjME5U +Mm9Canp6ZFlUZGdZS04vOFNJM051ejlJV01ORnMvbUhVQ2JzNzJUZ3dNeTYyb1N2MktHRXpuTEF6 +WmlsQTk3dnZpQWt6MXU0V0hMajk2QkF3WThPbmdjeHZ6eGl4OWNWWi9WSjQ3VXBmcXNQdm12UHF2 +UDZyUDZoQy85NEJtKzZyUDZaUDhNK256NUFkUlBwMmtDeHNpeWhtQ0RjNDVTRkVBRTlWYkZUN2VJ +Rlp5QU5EZHhhMHlOc0VXWUFpbUNCamZ2ZTk4YVQ5SDQ1U014Z2Rmc0VqVEVJU0FKK0NuYlpRMmho +aVRoa0JBUnZmZVh2L3NpWHY3c2cxdHhRakFTNVVyMGNuTlgrS3hwTnQ4ZlI1WjgrZFY0L0NoWUdr +alJrYWw0T0R5SHluWEo5NFlNYnI1OVRVR3hyTUZQa09MaXcxK0QrMDFnT0RQNHRBZHZPTFd1R1RR +T0FSSUlQTVRMRitISFZoM3d0YzAzT1BsaHl6ZGJNVFVyYkZ1WHlSTXZEaE1OcG43eTlOVUd1YXIv +TnZQYytWY2pQbkdIUzU5aE05eHhxT2FKWlI4ZXc0azF2dTNGMjJLWUlXZng3RHROZnV3SlZINzQ1 +Z3UvOG5hNGloY0JPTVQxa053ZS9lb0RQSWhuemgySC9NcFZUUGJpeXdrSDV5Qzk3VHh0c00vaGVQ +cnF0am5obWIvOFlRRTdYTEJ2M09sck1mUTkyNWREYTJvRGl6ejlBWVZETlZCWHV0QkhZcHVyUHF2 +UDZyUDZyRDZyeitxeitxdytxODlIdjgrdXo0dm1KUmgzb0FYUk5BcXQ2YzBoQndFRUlTRnJ5RUlN +cDJsY2pRd0VNQUlJNUVvQ1JKcW1JVUJ2UnZoVlNNRDVSekliYjRqRU1UU0lyeVFRZ2VRMG5MM25N +RGpmWlUvamFwQWYvdlYvWFR1REwvRmhrNWM0R1JxZVQyUlpoK3V6Y2Q1YUVMaDloQ3JINUNtbWRm +Z1ZBaWJZRlN5WWZHWXJ2bnh4b25pYXdIZmt4WVhIdXNNcStlSVZGclhnbTNqQ1B4dGlNODgzSDVx +U1QvNFZHOC8yNG9NZnRtenN0KzV3RTAvK1JnVEt6dVZ6OWhHQmVzSkVIR0xnUXMxd20wTlVUSFpp +THE2Slp3MUhPK1lnSUFJQ2RjbmJPanQ4NlRONSthekpOUzVSeUlPdFovTnFJUVpmK29LOTNvSnY4 +eHcrM1BGRVJOYjV0VWR0eE5UcnhNR2Z6L3pyWi9tSWQzaTVMbGUwb2JieVZuczQ2QUV2TVBJSkUx +SHYzdnVCRkVITEMwN2NXRmNEdllrL2UvaUlydlJ1dmhMQkw3NnJ6K3F6K3F3K3E4L3FzL3FzUHF2 +UDZ0TWRUNjlCbnhmQUVhWWdmaUtYYklnWFdPSXIwR2s0aVZnakRBUkpVb1Bicnlqc09RWmVjdDVD +U05EL3R3Z1lJODFBdU42ZUlFTk1pYmdqV0xNUnFNYmRNY1gxazNwRTR1Q3d2cUlmSWpVdnY0aTM5 +cU8vL1lPWGVFaVJtNEt4Y1lDY2NkdFljSVI4dVdnMFBnM05KYVo1NU1rSmFRNERNZkhsYms3K3VO +RU04bGNrZSt6bHczZmkvWFUzbjRxMy94UGoyZU53azh1YmQ5L1pSakJnZGdDdDMvRW5MNFZVMVBC +c1hqN2hMTTJCVDdWUlAvdmxMVDgyYWhNUmFnU05ZZkNsVHZFTnI3YzQ4cFNIMm92RGx6anNORDQv +YXFVK0doQnY5c2xaYitnWElzRDEraG11Y0lMekhCWnBlSGNIbXJyWksxKzV5Tm1GTjcwb05xenBR +Umcwdk56aFUxLzJjb1dIblQzNkloeW9OWHYxaS9pSXlENDI1M0EvYnptOW5YTTNMMmVZWUlIUEpV +ZWNtWmNUN1BiNkRJL1BFYWo5OE9EU1hhNzZuaTB1NkVnZjZGZVlZYXMrcTArK3FzL3EwNmcrcTgv +cXMvcXNQcXRQZUo1ZG41Y1h3VXdDeUdRc2FmUG1PT0FjS0FFVlhnREp1ZEtBN0FGbGcwamlJa0JO +aTFEZytVR09wTDJoOE5hRUQvYkFBdTNaM1I0TllraFFIRzlyaUI0dWQ4MWkyQ08rUzFONlE0UlFB +K2w3Z0F6SkVwZGJCaHNOQXhNUnlFTXpIWkhPYmZCdXpNa0RXWW92SHo0VkFmazVqTkljOGpabm1P +Y1Q2ZDV3eVRuNStxdG9QbUcyN2lzTGNqVHdLejhONnc3REZuMmFTbnd4Q050ZFk3dmtwZEg1aGsr +QjJhclhIcXl6amsrNWFwUmYvZWpyaTgvQURXeml3NE1UTldKTFdCcFIvWEtReVZXOTJNTktvT3hj +ZXNsK2ZVTzgrZ21mV1NkT1hOdW5mdXlzUjZCNlVLK3hKVUJZMVFtR0hEelpqNDl3Z0ZlNFhIS0Ex +YkJIVDlnRGN6aUJ5eDZjNEVGTVBNRzhtSVp2dnVDMVIwL2l3SjFQTlhZUnZGcVl0eGYvQm54cVFP +emk2Q1YxWVFPSDNPSG15eDBPR1AyMk9QWEdzMHN1MVdmMVdYMVduOVZuOVZsOVZwL1ZaL1g1V3ZS +NVFiSW1JQzdPNDVpeG4vWTVNNGpFUEVBSWs0UTVZdEdFaWl3aHlYdWpvRkFFeVA3VFQ4NC9qZ1pT +c3Zab0NHOUdnRWNtM3dpUU5GdVlJaVlZckJNWXZQenhMd0VqamFzdzl2aEgydUlhOFdVZm9VYjBo +bHlSWVM1K0RjMnhRcDNHc3djUHlPVmJzWkhNUnY0cHJDSmJJenB2UWlKZ3VQekQ1UENoR05ZSkZP +ZHlJRkJ2YkJJZnYvalhmTjRzeWR2QWp6bjcySXJGbjF6bERvOGFwb25saGpkTkxrZlA5bWtPbUU0 +VDM5WldISDdsNlM1bjJQbFROejdranllNW1sZDNtSGFNeU16bDhPQWJMK29iZ2VKVHc2cy83dTBs +U1BOcUtCOTN2SWxwTHp5dUNCZGY4R1cvdk5URGVnNGpoL3ZwbmZNUDdlWG53TEhIT3E3MEwvR29H +VnZQMjl1VHB6enNOUThyZXpuRHdZZjY2Vk9IQm80Y1NQemF2L2tNcHc0QTJGYWdrNDloUHp0cito +SUdzY3c1bkZ6cW91ZjQzUU8vK3F3K3E4L3FzL3FzUHF2UHRhcytxOC9xODNYbzh3SzR2enEzeURr +Z2lxQXBHQXFLWEtKRHpDWTBtelVHZ0FoWEtJVkhIcExZS3pDaWlGYWptRGNpWm1COTdVQXpJZ1hK +Z0hybWg2aGdNWkRDdnlhWHRPZDkrN1JGT0ljSFhIekQ2eXNSaVNkSkJiTG1yLzJSbmNGdm10Zzht +eDNUVUdmY2xnTU5wNm5oWWd1N21QaHl5UkhXRmNIa3RnV2Vwck1Hby8wNGxwTWMrZmVQdHZuVGxM +NnVRS1I4NDA0QjdWRUhid3o0OWZZSFh0ejd6QThSZURPaGR2SlZJN2pnd2I4YXNuT0pCYmM3cnRY +OWNIVGIzTldDdVBBUGg3emQxVmt6eW85Zmwza1lVMGZZQ01yYkRsandMVzl4MlMyZmQ1SGlHelp4 +N04wL0FHYU5NTzNONFMrbU9ienpvOWZrcDc2NGdVZnU2c3NITWNQRnIvN0FsWGptZmRiNC9NbGRI +TUlSQjUrL0xWQnhZUElaVm56REpaWjhZSUZQZmEzaG1lMnVqNS9VSGxmNDh3eUxaL3ZoNVk4dFRQ +WnQ3d3grUGE2M2NXdSsrcXcrcTgvcXMvcXNQdVZXZlZhZjFXZjErVnIwdVQrQUFtRVJ3Y0I2UmtB +S0h3SWxTZ2pBK0N5b3B0WjR3RWpXbXArZzJTcVUvUkpJOHl1UVMzQnZSVFRvQXIrVEZ5S0lDV2dE +MlpySVBrM0w5OHZoTWMwaFlaaloyT3Z0aytJYlBsdnpVN3g1VFpqQjd6YlQ0SmFEQStXTTIvMStY +Y0p3UkF4eVQ3R0lVSUZkNHNwREhJZFkzaEJGVUhMM2xRQThMNjdoVXl4KzVlRTNoQkV3UDRZbWtT +c3grZi9teUZmVGVDTWlCazc1OFF5TGZXbDB1R0JRWkpoMnp4UWJEbmNIS3IrdzRCdnZ1SUhGMnhE +MTV3OHUrWHE3b1JuTnA1bmxBd1AvNHVlTmlUdmN1Q1FzR09XaVR6UXRFWXNQMjh2NjdMZW1jZUUy +aDFOMWRTZkNmUXMxTmo3alZCNzZFemIyK2tmZWhLdE9hc3dPSC9hd2xROUJ3QytPZGNKaEx4Zngz +Y1Vqd05SQnJtelNuM2lXSnh0NjJUb01IalpyZHorUStCR0hEOWh5eU1IcTBKQy92UGd3eHdlOWVZ +djQvOW03dTEzZHVxdzZ6Tzh0UjVFc1I3WVJBUW9KaEFIblQ1WVYyeEJBeFcrQitDOFFFSi81QXI3 +ajNFQ2tLRWRXdkhmUWwvNjB1ZHF1QmNvRnNKYmFrR2JOOTUxampONWJiNzIxc2JYbVh0OHVXSEV0 +OXZ3NWY4NmY4K2Y4T1gvT24vUG4vRGwvZmlaL3Z2emtUSVFXS0ZqVEVTK0FaNHhIc1A1NnVPWVVX +TEdTQ3FvaGtsdkxFSklTc3VJUW9pbEVvQ2d4aWRHY0FzUlJHREErRXlJU0NFWk1RdzRZbVZNY3Bp +THdOUGRHbTZZb01iMGg4bGJCZ01relRTTU05WFhVQ09wQ1NzMGJRUmxIdE9mTUZRTmVEaUt3VDQz +d2FnZzh4QzIydWpRRVozQ3B6YndZRldZTjZqdDhmbDNCNSsvT2lJeXRZZXBrRkViQ0ExN1U1SmtE +VHh4OEVBS09DVWcvN0pVUFgvcWlkdndWdTg5NGM5ZEhjZFFHai9xSmxJSFZvWmV3RXBUUE5halA2 +clplcjVnSlp6RGlxd2R3aEg4Y3Fja0ZuN2h5RVhzTzVPUFNZQ1E0NklBTzViVlhQZGJaS3hjdVhO +SGdZWENKb1E1MTR6TUNQNXlHUFdyMUJzeGE4V0J3bDlNK2E4dWp1OTRhc1BDSHZzaW5IbGpnVXpP +RHc2SVB1T0VOUGJjZngvaldNK3Z3NHhrTTFzQ3N2cjVwcEIyOTlZZUl2dUhQK3Zsei9wdy81OC81 +Yy82Y1ArZlArWFArL0V6K2ZER1B5NlRFSml3V21PZzBDMW5JSnd5QXJERVBpUGtXQUNTQ0dGRk1S +SmtuTmtJVEF3SGlFSTQ3Y0NINXdJaXRTQVVUck0rR1FnbXVZa2U0SXV3ekZBcHo5cDVvL0ovaUl0 +S0Frd0dSTHlhU082d1JWMTY0MWZ1UGg1L1kvWm9BdkQwY0VPeHRCMVA0WEhQakJSZWVHZmlSeng0 +Q2NSR0Vab25wMXhMRVpWQnJpczJhSENySEE5N1U1WUJVQXhITDRiT2U0ZE5sRGQ0SkVtYTFpRmxP +clhXcEUzL21QVysvY2FPR2N0VkRBRlk4NlcxNWdsT040aEU1YkFUcjNyelc2ejJPdk4wUkIyZmlN +cWlld1EydklRNWNNY0FkQ0RSSEc3N0RLRFpUaWV2d2g1TmVIWWEwWTA4UGZUblVZTkFJdlA1UXNJ +NHg4QnF6SERiNk1COGU3aENMY1c2UGV0U3ZaMnBTZXc4UWRUaHc1SWVuUENmbTdZVXBmamdNY3ZU +d05LOEdPVnE3dDAzbTdkY0hCNEk2NUxabS9wdy81OC81Yy82Y1ArZlArWFArbkQvbFVJUHgwZjM1 +VXFnSDdvSUxqQURrMW1DRzVuc09rSXRaa0lJUXdJRlJwUDBhNGptVDJvOFFGeURBV3hlVG5TZ1Vq +Q1FYa0lBUkFUUElZUUR0SjJkTnJZQmdFd2RSNGpJeDRSTDA3L3d2LzIzV2VVc2pudHpJOTJzUFlu +Y2dqSmpWSlo2Y2hQUnRYQjNpcUVWOEJCSXgwVEFmUEg3Q0o0aHdjTHlvaTlqZ1FqNFJ3eVMrR3ZD +aitjejUrLy91bndlYi9lYUxUV00wM1J3eDQ4YW9jUWhHWERrWlJSODBIaWJZeFBLV0JyODViSThu +OHo3cmg3MFI0OVV2cHVkd3UyaWhoL1p6QUg1Tlg0aVlvWEVsbmx6MjRSNEg5S0czelkzM2l0YkJq +QThtZzBrZi92OE1xbi95TUNhRHFrM3RkQ0NHNXo2cld4MitNd2tNOHNPdkh0eXBVUXc0NVpGWEx1 +YXdUaC9raEs4bXhiZnY0dW1SL1BUaHVjOWlxMHN1bDNyZ3daVVk4cmlZMFhlWE91WDB6T2RndUJ3 +NUNDNlB1dlFHSG5YaGhzWnEwUGx6L3B3LzUwOFhQUFBuL0RsL3pwL3o1L3o1R2Z6NUlpcmdnZmJB +WnMxREFDRm9naUdCNWdOcGoyWVJCbkF4elQzVEVHc1FveWlpVm9EWWhJVlFGd0JFTHc5dzlybXNG +ZDk2WnBIRDhOZjd4RzJOUEFyM3JJS3VRVFdGd0JoVXNVaFdxQnJNSVZ2OER2VjZRNlJXd29Mbkg0 +eXJneUM5SlZCakRwTVRwM3dsWHBNSVVyN2l3d3VqRXdjTzVWQ3Z0MXI0clVILytOZCtLdWJGb2Nh +bzM2RUFzL3JGOVZ4Y1JsRXZibkNMR3p5YWs2ZE5sMTh0ZnNVRDUrcFZPMkdFOCtQQ1ByblVBWGY1 +VVZ2akVua042am1PNU1lejc0d2hYZzhHZzJqVmlxOEtsQjU2NkRtdzFPclFZUTZpajBHUFo1ekJJ +ajlPNWExd3hSSGJRZUF6clBCYjUxQWdjaGRlOUJUKzl0OXpNV2djUC9TbkRySFZvS2M0VXBNZXcr +VzV1TkgzOVU4c1dPa0QzK29RMXpxeDhDMEh2dlFPVG5WYnF5L205RFdIeWVsR0xGaDhGNnQ2MHRz +ZUNIREZiOWZQK1hQK25EL256L2x6L3B3LzU4LzVjLzVzL3orNlAxOUU0eUZCSUF2US9xU3V3Ukh0 +QldkUTRNM2JMQ21DZ0t0NEJmWVplSTBnYnFERmN3RURsTGlNcE5ubUFleFA2SVNod2Q0S2FZWUJu +My9TbWtqRTEwUXhrR3pBSlQ2aTNSblVaMDNWVEJmaHFoSCtEalhrRGN3OUV3OUI3NGNHeXdlYkhK +cWhXY2pGbGZ5YXdXVHFFYTlpd3BsR2lHdGVITFhaUzVCNHc3MDVkNWU5L3ZvYmR0ekE1cS9XN2JW +SHcySFJFODFMdkJNQU1mbU9GMzFRTDRQaXl6ekJpczFRN25oMkp3cDhpWStiQ1BXZWV5YUdQaHFl +eXlrZVhCSGoyMTQxeXE5ZS9VMWRaNFp3OGFZVkJvMUpyd1p4cXpVWHc5aExCN2hsUElhM1hrMzRF +Z01XejNFZ2htZmllVjd6MGh5RE9Oamc3Snc5TU1GUGEyckJGUU9xclR3MXQzcDZ3WW92ZFZvbmxs +dzVWTjRNS204TkQ2OTU5VGNIN2RDU05lS29WVDdQWWJUZUhBM1J2cnY2WFBQbi9DbisvRGwvenAv +enA3ano1L3pwOC93NWYzNTBmNzQwV1RQOUpBdzh3SVFLYklXRlFNSkJKZ0tBY0NuQVBuc2tSSW9p +a0VLWUV2aU1lQ0t4WGd6ZjIwZ0YyT3N0QlpIYWI2Mzk1Z3h2U1JpVzRaRVZNOThhRFRRVWw3ZFpS +N3o3Ny8zYmY1YVlNR3RXc1NDU0FEczhrMU1jcElqL2pPZGZDVU1jcklpR0R5ZHlpMEY4Qk9hUVVv +Y0xKdzRDend6ZjFTTy81d1Q5WVA2YVovN1BleldFQ05SSWVJUXB0aHlhNzQyTDV3YThQcXVSQU1T +TDBhNXY0dm9NRTV6cVlraUNFc2Q2bnhrTFQzTFlBNGQ5WXNLcWwvQllnei9EWjNscHdSM2Y0dG5y +Y3dSNHVQVzNmSHN1THIzUWdNc2hyMyt3UlVmWG8zS2xCdlhnV2QvRHcrMkhzYUtuSGIyQXdSeVRl +QzZHZXcwcXY5cmxwRjFyYVZsOStGR0hPTXdJZzU3YUp5NSttSWFKR0M0R1BhMzlZNE9xQldaYzh4 +QSsxUXdyTTRvcFhneDU2enhUbDcyK3UzeVh5MVcvK1lQSVhSd2N6NS96cDMxaXdqcC96cC96NS93 +NWY4NmY4K2Y4K2RIOStRS0FDQkFIZkVtMWdYZ0lTM0ROQXRvOE1OWWdESGlnZlNmd0ZvQXNBSDJY +VUh4RnllRXpROGlOWEkyMUZ6akVtWGRvV0dzUXYzK3UyWFBHRnRlOEpoaUtrY01Cb1RsLzlCLy9W +WENLcmVscUlHNXZYSHp1OEV3Y0dNVk4wNDFydktFTzZ3bmNHdUx4SFM1N0NWT1QvZnFFV2p3enI3 +R0d6L2hoY3JGaHdTRSsxZUtmNmNheEJuc1RaTjRRcDJiQm8vMEdNYW5QT3NLMUJqWU50c2VGQzJ2 +VXcxZ3d5RTBvNWpVZEp2M05nWGVmL2JVK0xvbEtEcjNCc2RvY0p1cHU3ZllRb243YjQyNi9ZYjIz +ZVQxQXJCZFB2UXluSDNxRUt6alYwRU00bXJsOS9uQ2dCME1NYTRpWmRxeXhUMTM2NFpuOU5TaE96 +ZU1FWG0rc0RCakVwbHY4NDBGY2VmVFNXcWFGVFEzTlk4NWhJcTY5K29BWHhoZjcvUjgyNHNXZ2w0 +dFhZT2dCV1E3Z0ZGTTk5dEtQWEE0RjNPdTFQNGprMUMvNzVzLzVjLzZjUDhXZlArZlArWFArbkQv +bno4L2l6eGZSbWdBaUNRNllDUTFFdGpzaWFpNE4wWEJySk9yYkpFQUlTaUVLSnhMRkFLcHdqU1Vr +KzRBQkNqSHlFWFJOSnpZeS8vb1Bmamw0RElMNTBiLy9GOGxGbk9hUklnNEJLZGFiQ1dMVFBQOU10 +ZGlhMFFQRVdnY1JQQjF5cTlHY2Y3VkxUSFYySUFrdjZpSkNzYTJGUzBQbGhSMmhOUTFSYVFiaWNZ +VWJSckllei9iSW9iRU9GY1pVanhvSnh4QURYMm93SjY4QnI2WjdYcEhhQTZjOExoakVGUy85T0Q1 +aEZ4TWUrT0V3aDAvWXpPUGZkekVkR0hncUYrYmtFVXZlaXJmNThhd1B1SERnNE55OEdxek5tNXEz +dHpudzB3UE1OWi8vZ0pzeDRMS25Cc1d6TlRpTHllK0NGVTVZK29lR3VBeURGNWh3cmY0SDEvTXJM +UFJvempQejZxTkJPY3FMSG9ySlNQYkl4Ui9pNWcrT3F3dCtkVHB3M01XaEVYSGpuemN6OXZERUZ5 +N0VWR01OQ3JNNjVYUEk2QjFlNUtQVDlteituRC9OejUvejUvdzVmODZmOCtmOE9YOStGbisrQ0xR +Q3Mxa0NnVnNrd3lFWHlRVmhvNkNLQU5oNmphaElyS3RJekJPK3BpUEVwZUVFS3FkNGdIdHJBSWU4 +ZmtwK2IxQXgvRWZOeEVwWTR2a3IvYWU1ejVzSlJNRGhyY1JmL1BBSGFRWlNpZ0ZoNHNQWEliNVlh +cFRmQVJFeHZZMEtTdk9RQjdmYU5ZdVFDQWRuM21BUmwza1ljR092T2JIVkNvZjRNUGxyYzgvVVd1 +N0Z4NzNoM2h6bUNOSWUzS2tmSG50N0lMalVKNGJQQkNNZnZxelRweG9KSG5PTjczc0Ztc1BvK05N +clBCR2g0VG5CMkdPdk5iREloV2Z4clNVNEJsVWJidHhoWkhRR3dvbGE0UExjcFhjR2JFd0VaNTY5 +MVN1dldQYUxBMnZYeVlrclJqR1BlN2pWVk5Nazl1bEVmK1hHdjdxWlRwL2dMNGYycUVGK05kRUgv +dFhkdkdyaUI4YXlSazV4N1pjN3VNNmc5T2l6L2ZDTDZSSlRYYmh3T1p6VVViL3hEVzIzdC9Qbi9E +bC96cC96NS93NWY4NmY4K2Y4cWFiUDRzLzhEU2hTQmMvbUs2WWlJQjQvaVJNdEVwQWhtY1FBSTRB +d05JeEIzVjNXZUdQVHR6YnV4RU93M2d6VXNJcW9rWkRqSjJQUC92eTNmaTYva3REbWlmRm52L216 +SWRYRnZON29hSVJHV3FmcHlCUEhXeGR6bml2Y3N6Nlhyd01KY0ZpREZPUjRpL1dNcjkrYXJsYjRO +ZGxubkdneVRuREdvR3FVUHdmQVlkSkFuTml2Sm5uOHkyWUVvSUhXaVFrVHZ0MnROZUFpQ3JoUzgr +MFhzd2VpNytZMUVKLzZvUStFRXRNZFB2R3RNNmVYc09xWHorYnhBNitjVEFDRHZlNzI5YkJRQis3 +VTNVUEFYajJUQTU4MFEyVHdPWVFjVkRRZ25sckM1ZkZLUi9JN3pDckE5UGdFRDV0NnhXUEVQcU1a +Y1luWmMzempBQjdER2p6akZTZXd5MnVOdmJpV2c3YjBJQnA1ZTlNamxzdCtuRlNqY3RsalRzL0Zs +RWN2Y1B3YzRsOVR0KytNWmIvYTFHVWVCaGpsY1JEUUVveDRaVkNmSDROK0h5N2hFb2ZuNU96Qk1Y +L09uL1BuL0RsL3pwL3o1L3c1Zjg2ZjhuNFdmNzRJMDM4c3JER2E3U0dUQWVWM3Y0RkdETUlyQ2tW +STRqUFFubXR5UlFHUXB0cExBQXoxM2Yya1RDUUtRUkF3Y2hOUWpVVGtudmsvMmlWWXp4VW1qeGhp +SWRrLzcyd056QVpDNU5ONCs3eE5LdkV3aUEyYkp0bERUQWF5aUFtRy9BNys0ZXFjbjl6VnFRSHFn +OThob0FuMnlhZG1EV0ZzYy9MZ2ppamdydGpkNVNGZU5lRkhQQno4NlcvOFRQSTZKT1V4Y0ZTaHVX +dXlvYW53TUlxOEJLSVA2b1JMSGhlZU5OaCt0Y2xubldkRWd5KzUxQ0JYelVFa1lsdFg4VmpybWZV +NHRBY2V1ZVcxbG9FTWZmSjcveldvZVBJYkRGcE85QmwrRno2SUhkZmg3K1o3aU9IWlJjRGx0RDNC +czMzNWcrTnd5Q2Mvck9ySG0rY09HSHQ3aUtvREwrNjRxSTdWWnA5NDFaMDVQWkxQNS9qaTlQNWd2 +c1BobnVtNzJIZ1VEMTVZZW5pSnlWZDBGWU1lSnZ2RVQxMzNIRGM0azhjZm1QUUVIKzdtei9sei9w +dy82WEgrbkQvaG16L256L2x6L3ZUOG8vdno1VzBOVXlCZUk0bE9zd1VtN29wV2NJVGFCSVNtU2x3 +Z2lFR3d2UUFxekYvcDJ1UFhJSmpIUHZ1UlorN3YzdjYvamdEMTFzRTZlSUJrUU0wdzdQTnJCOVln +Q0Y3Zm55WmRtKzRBZ2Q5M2R3ZUxtUERCeEt3d21kT01EdGlJRFg0SGdGbzAvYm0rRHg4VkRjeHEw +dHlZNFhLSldkd3c0OERhbVB6Ly9YK3lSbng1MWUrbmYzUHdpc09rNmlCd2paTS9RcjdMSHZFMFRo +OWdzczhhUWlBUytkVUF2L1UrbXljYTM4WEhMMEhnMG1kY0dQS3JwLzJVZzZGY2NCT1FRZUE5cEhB +bkZ5NzB2NGV5ejRaWURrajdHUmdHK3d4eGlsOWVPY1g3SnZaYmI2NXZuN3dSczBZT0JzT1JOV3Fr +SFRvd0hDVFc0Y0o4TmFnUDF1SVNkempSZ3h4K3Q5ZTh5ejc3NFpTZlpzUjA0UW9mMXNOaFBYenFn +TkZlL2ZWR3gxNTU5VkZOZW9RbmZNR2hIdXR6S0J4ZitPVXBkK3R4N0tJUldoTEwzdmx6L3NUdi9E +bC8yamQvenAvejUvdzVmODZmMW41MGYrWmZ3V1VLWkFic0pWQWt3SUFCSUlqaUVhL0o1cEZyUFdE +V0ZZeWt3Q05EMHpVTm9YNktCazdSMXNzbnI1Z0laQVp2Y0t5ejNxOHBJTU9iR3ZzSVdTeDR2R241 +azEvLzZlUTBGR2dQRXUweFR4Q0VpV0NDc0pheEt4aER2SXJZV3ljTnlEaHpHZUxDcDE3cllJT2Rj +WkJKTklqMEU3MDNWN2lBbGFEd29FWUdreHNYMW9pTkgzRjlkMGhaeXpCcU0wOGsxc3NybjF4NElK +cUt6NXNNL09zRGdjR2s5bkxyOHRhcGZiV2VVUFJScks3cHdZQS9tTVFuWXJseVVKMnd4TFRYOHhw +QVBLYTN2d2VwT0hLcVJaL0ZVNHM0RGlZOXdiOUQzM014OWQyOEdPTGFsOXpIUTJxNytMN24wRHZj +OHVMejBjWmo3QjVDWXVrM2pMUUlyeDdwaHozeWhyL0RoVk4zKytXMjNyNDhPd1BwaVhyNFE1OXFZ +cHFCaC9HOVBiT0grY1hWQXhyRUdSMDBuenJVS0JkTVl0RldZOEJ1cllzSCtLS0gvZnc1Zjg2Zjgr +ZjhPWC9Pbi9Qbi9EbC93dmhaL1BraUJCUEFJTWNkd1FpUTJMd0NCVU1FZ0pJZ1dCSUMxR2pnMnl5 +WEpBUlhVM2x6QUR4d1lpTElYU3hEZk9zcU1HOWRZTkVVamZRNzh2WmFENiszUVBBWXlDUXc2eENp +U0lYRG8xQk5Fd3NtZURwOGxnK1pmdWYrTWNLTk40TXlrdjFxTktkUjh0dmpBQ0o4RFdYZWlsanRj +aE9TdnhLSHRUWEJpRXNOaHIxNXhXSnl6VGIwd1ZwOXNGZCtEUlpYRHJYZ1hnNzFpa1dBOXVNVWo3 +RDZWUTE3OFM0WG9jaFBRUEk1Y01SVFV3N2tpNEVyUEQ4Y2ZJMnB4UE1NTHJtc3o0RjlBcktmb0sw +bGNtODN5amZ1N0pIVFd4WDQ1UkVQVHpBeFVBOGw4WEJLQy9ib1gzV21GNTczMEJYSEtGZmkrSU5F +UDNBQ2wvamlpVS9QdUxIUEhqV29oYW54S0xjZXc2MDJPYTNSUTdxeHh4cng1VEhjOGV3ZkZmQmNQ +RHpCYnE5OG5qMzhQSWVKL0xEMDRIRHd3SWdUdmRJVE9lWENweGp6NS95cGYvUG4vRGwvenAvaXpa +L3o1L3c1ZjM1MGY3NU11QUN6a0dnMTJxU2Zqb0ZHTEJNb1hES2JKVktZdlFqU0RJVGFoMGdrZmpQ +NkNaQ2h4SlpEVVlRbmpqc1JBSXFJQ3RtdkREQzF4aURINzVKYnEyai9mMHJFSjRlaEVkNytSS2kz +Um55Q2tNOGFNZDFENU9Yc2dNMXp0ZnVyWVhrSnJVT3Q2a0Vzd3MyTHBSNDRZTU9KNXpoVHJ3Ykpy +ZkVPdU9SbjBoTy90eWVwOVJwVHd4QzBkUXdEdjhGUTF1TmZzOFMxVHo2Q3JXaGQ4cXJaZ2FZWGNz +R0d5d3JIdkJyZUc5UWFlL0JoRHk0SUJGNTk3Q0dsOXoyWThFRVA5amhrMUFDcjJIaUQyZHM1ZHp5 +SVl3L2M0cmpFZ2ROemVHaklYc2FEVlUweDZPbEJiRUtXaXhtc3BSL3Iya2M4NDc5R2wxZmZYZUxU +clVGRERtNjZoVmNzdlhSMzZTT2V4SVhGTTJ2MVJsL0VVVHUrd3lGKzdoS0RUdkdtTm5uZ2RVRG9Y +enh6K1dwRThhMlJRMDB1c2ZYQW5IejIyWSsvK1hQK25EL256L2x6L3B3LzUwOXI1cy81ODdQNDgr +VUI0QnFxV0FFSkFpaUFKUFdXUmtCTlJJUTVva2E0UUFpemg1aVJMNkY0K2RXQUs1cklDRkJzNUh1 +allnOHlGVWg4aHYzMk1KLy9jSnlwWVJMdkwzLzc1ME1DRVpuM0t3djJHZ1RMWU9iYzVUTVFnbERG +bDBqZk8yQ0FGekUxV01oL0c1N0xxZEZpRTdTRFNEd2tJaDAvYXUxek1kU29LVDRUcXpYNElsN21W +Uy9lQ01CaEl5N01lQlZUUHRnODB4K2ZjYTBlbkprbmNDS0NFVzh3NkpjWXNNaUxTNSt0SVhiN3hJ +ZEJML1FFTnZ2VnFWZUVMWWNhREFMeUhINDg0OGxuUGFFSE1lRmlNcmtkWHZBMm56dlR5ZWxTczhQ +QmN4eUlZM2hqMUlORVhjUWZrUjRXYTJwUW4rVnZINWxCcmZTcGQrN3FFVXR0RDdaSEl3NUlYTVNB +YjhZV3p4cTl3b2thek1NampoN292ZS91YXNPTld2VEVNMnZzRjV1cHhIVEhQNng2VllPS3J5WTVj +S0F1UE1FQ0wvNzB4WDVHbmovblQvdm56L2x6L3B3LzU4LzVVejRESC9Qbi9HbDhWSCsrbUlYWU5O +SUFVbEFKRWFsNGdrS3lBSUpxRFBJa1ZBU3lKQVdRRVFCa0ZHQTAzSnNFUlpnSEptK0lMcTRjaEtG +UWgwQUVkd1I0cytIUUlGd2dGY0dnekFRbmcvclhxR0EzNUJaVGJtK1dGTnZuU1BLODVvVzlvNllx +Ym5nZkxNOHczNmFJQXhmOGpDQWZVZUtxeHJjT2Z3WU9mRlluam1DeVA0ZlJpYTljYWdiajZvRVlF +ZG1idVdHREFTN3hpRXArOFh5dTBheGhaT3ZFaFUyK0hreWU2OEVqMXVkZlAxT3pBMUI5OW9nUkRt +K1BtZ2pLcUhqdGw2dUhBTkhaaDJOdmdIQWhKaHhpd3FqM2pLRi9GVFNPMUdvdnN6Nll2bzlnNVZF +N3JSbHlpaUcrZFRod2lmRVk5UGsvK0lYWGMyKythRWp1L09GeXZjYVRkWFRrYlp3YXhNdUJjTDJC +eVI2R2xpK0h4R0h4QjVHMWZtVUFKL1FlclZ4dS9kTkhkZEdnd3hFZWU5WEV3SHFyQnpqeFdWK0Qv +YjYzRjJxVDMxN2NtK01UOGF5QmVmNmNQKzBSSXh6ZW52bHovcHcvNTgvNWMvNmNQK2ZQait6UGww +TDhWRXI4SGdDdUFVZ21TczhWNUNkeEJTdmVHcDhWb1VoR2xxd05RakxoYVhyZVBOd2FGOEFFNWk0 +SDhaaFBnNDljb01VUWoraGpxR3NrUXZ4SDI0M24xeFdZRkNHRVJDejJ3T3AzempYYUhzOUQzT1ZH +a2pXSTdyQWY2Um9CcjVyaDZGQ2ZmV3JEa3diRFl3OGNjaUNTOGV3bEZMRnFKbXZ3WkkzY3VDRlVv +bEluWWRqSEtJU0FreHJVSHZ6Ylo2MDloQk9lVHlSNllLMDlNTUdQVC8zeEdjZWFyWDZZeENJY0Y1 +eUU1UUJVRng3YzVmSzhJalBVd2xDTWlEdGN4cUNIeFRyUENSVnZ2c3VOZjgvVnJnZEVxTzZhSFRa +ck0zOXhjamdmWCtJeWRFMWxyODlxcHovOVlBd2MySXQveiszem5CYmtNcWRlZFQveG40T0dudFNI +ZDMxbGlod2dWMnQ3aFZ0NVBCUFhyNzdna0Rmd2d5dERQV3B1WC9WRFRiakZoN2d4M2NYREJYeXd3 +ZTdDcS9yVUJpZHR3MHdIWXFwYnpQbHovcHcvNTgvNWMvNmNQK2ZQK1hQK05QZFovUG55d05zWWJ5 +bUFKZzVDMSt3MC8wQURZMDZEWEFVaWlMc0NYUFloSHluK2Y0Y3FNTS9kaVpGQkZlU3pTNTRVZXcx +UmxJTEU4SzluYWJaaU5JSTVZUFRUczM4aGpJSHROL3gxYnNVb3J5WWd3M1BFSThCbnRmcmNRV3oy +cWRNY3ZJanNRQ3poRXBVNVBPRkQzaHdDMXlCODlRMFBrcG1KYUJ3NjlwWWZwbllRaXEvNWpJUS8r +UG9mYWpPbFdzczFiQzR4TmQxNjNOdGpqUnFiRTZhS1Vpem02MTcxd1ZJUnF3SEhPSVFyK0s0UCtM +Q2ZHTlhnclE4QjRSTTJmS2pIZ1VBOFlsdjc5Ty9wQXd6VzRFVU4xc0VPcTFqNktZODFXWGNpeGFN +NGRDSUg3b0x6OHRSQWVMRy9JaTlYMWxybnVRTkxqVGxJemtqcXRFNHNwcU03ejhXQlR3OFpWNHpV +ZjNOcW9sKzQ1TUZUelF1dnZ0a1RuVngrWE5PcDJzMVpCMjhOcWwvdk9WQ24zdUVYRHAvRnBYczlv +aU05c2FlYW5UL256L2x6L3FTaCtYUCtuRC9uVDF4NFBuL09ueC9abnk5dko0Z2ZlQTl0MEd4TkJk +YWxBUVd0ZUdZVDBFKzVnaU1XRVlpWEJCa0VJbzd2bXU2enUvV0VRVHdJVVFTQ2lFRlI1aldaUVpt +bWpZRFJkN2o4dW9MUGhNUFlpbFlZSXBHRktHUm9IR3l0amJuVjAwSGNhdEVJRFJMdnZVSGwxaUM0 +N1lWQi9kWWhVUTdOOHVaQkhvZVBuSVNDZkFKUXR6cDlobG1qWEo1RlNNY1pQdUNvbU5RRGs3MXlN +WkNZbW9vZldQVUMxdlRvZXFFV1hMckRsa2JmT3Q4MU9nZks4UWl6ZVBqM1pzdGFHSEN1VjJKWnI3 +Zkd0eHgzRVo5NW9yUEdaejNyV3BqRmhCa1hlRk9qTnpMdzQwcHY0R3FjN3FjN0hNamhJTkpYenhn +cDhRKzdPeTNhbzUvV1dJdHpPSEhua2xOcys5VnFqOXl0cnpwV3U4L3B4M0ZBVzU3TGFUMnU2Y2xj +RFdvTmZjdW5MajMxRmtuL2M4QWNML1Rzb0ZHUDc5YnJuUXQyMzYzUkN6WGhpUjdrNlNHaVBoek9u +L01uRFBQbi9EbC96cC96NS93NWY4NmZuOFdmTDhIOXg2dUNBVnZpSmJCUUVZWWlpTTRhQkN2RU00 +M1RYTVF6Q0ZFcHprL085aEoxNGwxY2U2enpGc2VjaG1vbTBJYjR3QklhVEs2SzFsc1V2NWFBQkFh +MXhsckRtaEtFVU0xQlJncC9LeHJCY0dwbWgvckVjU2RZSm94Qi8vNzVOWVdTTEo1MTFzQlA5QVN2 +Zm53NDVNUldZdzFhTTJtby9jUWhodmdhNDYwU3ZuRGp6Uk8rZkNZbWVZbkxYdms5dHc4L1l1TE12 +UHp5d1VTSXVEYW5CN0QyME1HdkdBUk5IT1pjOEZockhmeHFFcy82dkpYSitCcWhxTmtjVG1HUTAx +NVlIZDU2cEE5Nmh1L3FRWS9ES1RPZG9PR1ExM3oySG44UjlIRW5Obnd1SE1hZ04rODd6bUNTeDNj +NVlYT0h4M3JjdWVOYmZFYnI0V1pPRDlSSW40eWhKb2VoL0dxeHgzZnhZYVVMaDZkMXRFdnI5R1F1 +dmIvY0RrTGNlWTVITlluZkF3QW40ZlBXMDRRNkhTbzFLbTdVRFNlKzlGQXM2M0EzZjg2ZjgrZjhL +ZTc4T1gvQ0lhLzU3SjAvVS9mOE9YL09ueC9QbnkrQy9xUC8rSzh5Z1FESkNVTmd6VFh2cDNOSmdR +Y0t5UklvVWhHTUFUeEFLZUFBQUU1MDNyQllCd0NUQUNsbXhZazBRRFZRRVloaUZCZmhFd2VDbWZQ +My85MC9Ed2xpbW9mVDBFaE5oME1kTlNGTXhBSUxuUGJBMTZGWjhzRW1MdEUrNXZ5U2VXVGpBSUh1 +TWRIRkpBaDc0TlZjaDV3M0JZVGhFc09jK3ZBSWowYkxvUlo4K0ZmUWZNY0pQc1FPN3R0YlE4aUxV +emtKazhoNjBJaGJNMnNtRHRTSFQrdlQ2S3ZacGRIRVoraWJ0M2RpaUsxK3VlREhqYmhNb1UvUCtK +cStxOFVkTnZGODF3ZjU5USsreGxhWHozQ0l4MXd1WE9HdXRjTHNJbEoxZWNaVVl1TEpaNWY0OE1U +b3h3OE53dUd6TnlsNE1rOTM3bnFqdC9ZK2RULy95cDExc09QTGQ3bHAzc0dIcHg0YTR0QzdudnVQ +L3ZVVUJ2TTBaaDczNHVzZEhtZ1JuNHhwcnp6MjQwbHRzTk9FNTQvZXYzK01mdm50clJmRTh4MDJm +UGcrZjg2ZjgrZjhPWC9Pbi9Qbi9CbWQzNEJwL3B3L1A3SS84d09vZi9LWndCVUh2SVNJdE1GekJR +cG1nK2ZNWloxbm1xR3g1Z2pjSG5FMGdRZ2tRNERDQ0lFNHZSRVJRK0dBS1ZpRHJXT28vcFcvZTV2 +Rm9ILzhhei8xVGN4TUFZc21FUUVUeXU5NXhWalN2R1hSSFBGZzZyRGZHdnVKaWNCaGg0VXcxV2dl +NGVvaVlzMzNXUzFJaDUwWjREWkh4SjY3MUFhTEJtazAzR29SbjBGcmFMamh0ODY4R0RuQURoTWVO +RlU4SXNlSGVpTFFPemhoYjE5Z3NGNC94TFRmWjdIRXdKWFk2aFFqaCtqdGdkRjZld2tiSm5WMUVJ +czE5cnFibDVPb2NhTnZ6R2VQZVBJeHFOeldFK2I3dzE5KytCMEU4T1BjZDJ2bHdqdithVVY4bDcy +NE1lUkpQYWRMZWRvTE9NVHluVG53Q1lOblRLRS9ub25QU0RIeEdkVmU2MmhFYlhESmlULy9qRGh1 +WWJNR1IvS294K0ZBYnpSbHJjTU9SL2JDaDJOWTFBWS9qc1JoYk4vbHJXbmxzRllmNElRUmx2bHov +b1RSZW52bnovbHovcHcvNTgvNWMvNmNQMkh3N0tQNjgwVzBSRytCcEJZenFFa2JHTkJQdzRJaFQx +RkVMNERnMW5xR2hJb0JzUktaVTdRbTJPTXprUDU2WGxGaUlFeDg0a0cyb3VBQjB1R1FacDBRTmJW +WTNWMUVBaGR5N0dNVWIyTHMxV0ExcUFzdWVleUJyOE04UVNGV0RQdVJWeUVna3hrMUVSYTVHVVFz +TmVOTE03ekpzbGZUQ1FFWDV0UXJ2NXgreFlKb0RQUCtHd0JpbGRkbnYxdGVFOEdGTDduZEsySk4x +UU1pY2NlYldzWFJLM1V3VFlYa2dQQmREdHppaW9EMFJWd0hUcm0yeHpxeFlGWlhoMXllcVYzZDRo +S2Q5WHF0cHk2Zm1aNW1haHdjd3E2SE5haURDZzdmOWRkOEJlbFpjdDk2QnNDbloyTERiMWovR1BU +NTd5clVLTDgrV1Erakd1VXl4OGoyK3l3SDdQS3BRMjN5MllQbm1Pa09BQ2IwM2E5YzZDTXM3blF0 +djhNU0xoemlXcDJ0VzB5MTB6amVZTFhXYzV5Sjd4Q0F4ejU2Y2xtckwvcEJTOVhzL0RsL3pwL3o1 +L3c1Zjg2ZjgrZjhPWDkrRm4vbXZ3RkZHREVRbGMwV0NPb1NBREdLRnhSSWd0TXNJRFZZSW9RRHBk +RmlpRW5VU0JMRFp3a1JSWXpNSWdhUzBzQWJTQk1EdVo0VE5LQWE0Wm0zS2dnakFyZ0owdHVXRkho +bXRjZmJKODhOejlXRktCajg5VG1DRFBzMFF5ejF3T216UnFqTEVJK0JZWUJmVGZMNFh0Tm9wQ1k1 +SU96VmJId3h1YWJqQmg0SGgxZ01LTDcvVGtCTU9ieUZ3QW04aE9sWjloNlhtaTVmQmFFUE9IS1h3 +MXBZWFBMb25Ya0hHakg1ckk5d3drVkFOWDlya3NONkhNaWxMNFFPQys3MUUyOE9yM0x0VHBpZU9h +aHBBNzRZOU9ML1k0TTZUS3dsd21xQlp2UmNIWjdEWDFQMmpTSE1OT083eTJCR1JtTThQT0RBbkhY +NHgwRU9vYXZyT1JTL0pqYys1VkdMUzcxeWlVY2pOYTRZMXF1UHpoakxkeng2WnIxODZzWmJqVzlP +dk9wWWJudjB4aDdlZ1EyZkhUakRseHJLYVE3TCt3N2ovRGwvenAvelozc3lmODZmMWNMOE9YL09u +L1BuUi9ibmkrQVJveURCRll6Y0ZQNVdDREFTdERrS3R3YnBQbXUySWhSTXFKNEQ1d0lJNEFvWTZk +NldLQXg0UUlBMGZBYVdZZVN4WCtNMVVCNy81N3p3YWpEQm16ZkVRUWlzQktkQWc5Q1lRanprSUJz +QkhaN0JwWGFYdUJITW14Qmc4RXpqNUJKTEkvRWxWdzBxcDJmVzRZR29ISGIycUlkSTVWYXo1L2I0 +bFFWdmx1Qm1UZ2NkOGVDT1NTS2s0d05lOVhsT09ISVFBLzdoRkZ1ZE9JUU4zL3JSdmpBNkk4aEx3 +TVNCSDdXSTMwTUxObGpFbGkrOHYvRmdYOFZzcjdvYXc1ellNT2l2ZzFkdU1mQ09GNGNMTWNQdm1W +eHFqRUZ2SDc1YXEvdzRqQUV1TG1QZ3kyRmhuU0ZlMXB3dTZGVE5ubG5YUTBCdnhZUlRITmc4a3c4 +bjFyanJ0MzF3NHRTOEdPclNFMi8rN01XemVITHhnb08yYTlTTFE3bjBRUnpyMVNPUE9tbEZmdC8x +QUI4OXRLb3B2WVhKd2VxT28vbHovbFRML0RsL3pwL3o1L3c1ZitMSndQWDhPWCtLODFIOStXSVdQ +K0VpRFJCSkpRTUNXWnBtQ0lnTXhHdU1ZQWpYTE0ycmFBbGV3WUFBakVpQWlkaCtCUkNyZlFDSW94 +a0s4Sk8wZlhJaXdoNDVGS0t4eU9pYkZrWEJZaUJaUWVvd0o2K0JHTVlUajlHSTBab08rVHlYeXpw +dk4yRHk1c0ZnVVBGcVZQTkU3eGxzY0tuSFd5bk5zQmVIeEtJZTVyUVBMM2kyaDhnMHlCNEhqbmxt +eGIrbW1zY1R6QzR4NUJmVFBqM1JhR3Z3aGdQODZoY01lSkZIUGZaWnF6LzI2eTJNNnUzQnBCZWVF +VmtGNUM3bTg0Ym9lWXVSZytCNFZyL1BZc3JUM3VtVFhoTTBqdVdHSFNiN3JYTzNUeTRIUUEzS2FH +bzFaNjg1TWVHdFp2VEVPbmR6OWpJSmdUTU9FNXJ6SFNmeXd1Q1NHejd4eGNZaHZiZzh4eDFPNUZL +M1p3NGovZTVoUWl2NlNPZnZjNmtYRC9hYnQ5ZHplMkNnVzMwcmgvNlE0dzg1NVJPM0J5bXM5dENY +bW5oai9wdy9QWnMvNTg5eVBYL09uL1BuL0RsL3pwK3VqK3pQRjZFUVBpQTJJRVV5QzVGbElXSnFJ +TThRYngwd0dzMFFXWDkzY1JTSGNHOExGQ3hlNGg5d0FwWlRVWXBGR3ZEaW0xT0FvZ2pONzlJclJD +TThFMGRNNXBaWGN3M0NVSndpWVJQMzIxOUxIMTdDOHR5YkdDUjFxRmRjeitENXlmelhYT3FWUzBQ +OUdvSWFDTmdlK1FsWURNYjNEQWFZbUV3VEdNQWx2L2d3NEZLZG5qT29uSC8rV3orWCtKcEtUTzY0 +aEYxY2pjSVBjZm11VGh4N0ppZXg2b2NlYURhTThoR3EzQTZCY3F3K3ZXbS9pQUV1cHBGWExIWHJt +enJ3SUxZNDh1TURIbldLSzJhMVlHLzVFa3VOOE9KSkxKajFHMFl4NElRUko3RDRiRDlCaStuQ2xk +aDRzYTVHcHhQZjhjOFlQcWNmaHhFMk5aVUxjL0RURDYwNGdIQUltL2dPMnByWDFmeXdNQ0MrOEd5 +ZkdQUm9XSWRuUE1nREU4M0JKdzhkNHJWbWxCTTNQc3RESTNDSnI5Zm1yQmZUTS9ubXova1Rydmx6 +L3B3LzU4LzVjLzZjUCtkUE5YMEdmNzU4SVR5RzhCWkljY0FBN0pJWXVjQWdYUkNKZkFlR2dXUGF0 +d1lMckduQUVTNGcxb3VQYU1DWnpGL0RJdWd4eEpma0FGWk96VkswLzNnWlVRaUdneW04SWRJUWQy +dU40a0tlWFBBWThoR0o5ZkF3aEtJek5QUHdFNmo2M2ZzZlRqL2orVDhBSm1RWU5jcEJwdDZhUkdQ +VnAwN3h4WFo1N2kwQ1RvakVmdk55RUJoY252dmRlTDl5NFEyUk56WHFoOHQ2OFZQTGNZRXpUWFlY +QXlaTlppTDVHRW8vNE1LL09QaTIzeHA4bUpkYlRXTEtoeHM4T3pCaFpScjF5T091RG1Ja3VBanJu +c2xSdnZGRzRIU2pUMko3cXlJK2ZQS3EwOXVwSHBpK3cxYkJpMlVlVnAvTnkrZVptc1dNUVU4RE9U +QU9qMXpWbVR6aUVMdjREZ0ZHTVk4bk1kVk4xM1NBRDN6cHMxdytpNjErYThRVFYvLzByTDh1b08v +dU5DYVhlcXpqSDdwMHVLZ0pWbnRyVUNaWGowdU84aGovWEM3ZnJZVVhicHlKU1FQbTVzLzVjLzZj +UCtmUCtYUCtuRC9sbWovbno4L2l6L3dOcUVsZkJFR0lEWkpaeUJnR3NnVHdIVmdrS1U0ekFBZEdn +VWpSV0RHQWs2Z0NKeHg3ZkNZMHhZcUZRRTFHb0hnMWxEZEV5RkNNV0JyN3A3L3hNL25wbjdCaE5t +QXRqb3JDUUpSOU1MdmJTNURQZU41OGlJbGc1REdhWm5lZStPVHlERS93cUE5KytDb2liM21JblJo +dzVEa3VpY0JlVGE0UkdGK0Q0RlNEWnk0SGpqck1peU8rTmZMalRCNzhPU1JnSlVhaWtBTVA5dUxU +ZW50OVZwZmUrbXc5VE8yWm5NeWtabmZjNFVrc2E2eDNXREFFVWVmZ2ZoTVFmUEtLQTVmZTViQTZ3 +ZUVULy9iMzhINEUvYnlSMDNkdncrQ1ZSMXlpbHB0TzdNRzczS241YmQ3M0dQVDZJcDhhM2NXMnp4 +NDVQVk56TmUxNSs2Ukg2cFJYSHIyQkNTK2V4NkRYbXhyZlllUVF3cU41ZGJuTEFZdDEvckJSazNy +RXBGVmM0S29Ib0pnMERyTWV3bXl2R0o1WkN5dnZxYVVIaS83cTAvdzVmK0p1L3B3Lzdaay81MDk0 +NVprLzU4LzVjLzRVNnlQNjg0VlFBaUpXZ3ZZVE5uTlpWTkNhNUxuZ0FoRzc5UkpybEhWTWhRd05F +VWRTeWExWHFIajU2ZjBLOFhhQ3lHUFEyeGNoM0tWQTZ3alE0YUI0aFNsWURubjlmem9WaTZZaXky +V1Bwc2lKbUl5TENROHNMbStBaU5FUXR5Wnl3WUZNbnhFbkptRVN2RGNuaE14RThJa3ZEK0hBaGtQ +UEt6QnZITXpCcEhZMUZ4ZXhpS3NCREVxc2ZuKyt1UTM1NEdZQUhQbE9ZSVJGTUhJUkVNemxYbTdy +aU00KzMrR1MxeHE5SW1DYzJWL01NUGhzajh1OFBoSVJqbHcwMElOYnJnajg3dkJHZkZjdnZxeFJM +K3crVzROM0hNR1BFem4xUUI1WUsxZ3g1YWNYM0Rpb2lOMW51T1hBSFR6MHhTdytpeXNPVGJoZ3I5 +N1NqOE1BbTl6MGc4OXFVejQxaVNlL1BMNHpqemg2cnkvcXFhblZURGM1a0s1MjVsV1BlVFhKSTQ0 +YzlzbWozenprR1R6cUtoOWlxbGs5RENrL1hENjNoL1BuL0dtUGEvNmNQK2ZQK1hQK25EL256L2tU +aG8vc3p4ZkFta1E4Q2haTUlqLzFsa1RQQmJVWjhZcnZkK0lqRnVza1VvQ0NpZE0veGV5NUlod0ND +QVNRT1JVT2dBYUl6L0QybERoN3JHRXl4U2pFZHlhemx1amdVRGd5clZjTFV2TDhCcU9WWk5pODRm +R0c1UmxmUXpUODhtbWVtdUhyUEtMRmhaRXg3STBBYjcwOEZiRjhzTXYvemVESGdiVTRoRDF2ZGc2 +TGVpcEdNVDF6RURtMFlEUTB4MkdqYWZhckR4WmNFb044K29RRGNlUzB6ajZmY2NoMGVCWkRmbGp4 +Sm9jNWI3WHl0dVp5cTFGTUdoQUgzeTQxRUtMNm1UUUgxdFVwaGx6cWc3T0NzeGMrZk5PR2RXb2xl +TEVZREVmeXFzVmU2L1RRT2x5SXdiUkdEdlFUTmMySmdWZDQxS0l1KytRV1N5L2hVNzk2eW8yN1lU +Kzk2YmVZeFM0djdHTFk3N0pXYm52TENaemRnMGRZR0ZTOURsbHJ4SVpObmRXVlBmeGh2Und3aUUw +SERDcEhlK21BOWJtSENCN216L2xUaldMT24vUG4vRGwvenAvenB6Ri96cC91eGtmMTV3dmh4QVlJ +WUpvaEVMSVVxaEVTRW9oTmtra3FDWUNhZ0pRVWYvTVNtUk5ISSt5cklQb1RNVFBJQ1pBWVNFY3dF +OHRyRHE0Y0dsK2UvMEJjRG1UMlg5c2llSVVZR21pdDNQYW1lY1kxRUtFRXczanlQcjhtOEF4a2lx +RWVjMkk4Qm4zK2xUQjF3QzZ2ZXh1UG54b1U4Y1FOa3hod3hxRC81ZmxyZER6STRSRFVNTU4rZTZ4 +bkVFYjErL0tldzZ3ZU5WaVBWeHk2SXU3TFk2NU5oUU9uN2tRUi9tNmY3K3FDZ2Jud0s1WTZYUERn +eFdlOFc4L1FjTWxWMHpHV2ZlcW9pY3o3Ykw5NnhTWmErUWxXUGZEaDBvVUx3OTBlV29EWlh0ajAy +Rjc4ZWtaakJ2NDlsd3ZYRHMwZTV1SHlSRzZ0TlRRS0x3M0M0RTVEc0J2cVVadjROTzF6NDlJbGJo +bUNFVDJUQTNZeFBOTVRITmxUWGh5Y2N0R05mR3Eyci9YZ1hseHI0ZVlWOVZwamlFT2Y2blEzNXpP +ejQxdk8rWFArbkQvbnovbHovcHcvNTA5NzU4LzU4N1A0OHlXNElpeFFMREFDYWI1bkNpSllpNEZn +S2tBRjB5anJnQlpVUVloVG9HSVJMb2EvM3RjMGdJaFFUbkhOeVJlREhvRU1UVlFWR1JFUnU0TGxr +SnVJckdQUUI5dnpyNFJaajZ5S0xlTWFDQk1zNXVXMXY2UDRpUWFSUGF4cVVIeUlLYmU5bXFwT2VY +MjNuekE4VngreG0yODk0b3F2VWQ2VzRjTWdUampVNmY5OHVMalZSNFFFVkE3Z3lodWlPOGprRmRO +ek5lbEhzYWpUR3A5aGdDbDl1bldhclJZQzZrR0RXeHpLTFo3OTM5MkJZYjExTHNhVFd6eUNnMHM5 +UkFSckR5d0NKem82Y29EQXBOY09FcC8xVHk5Y09QYXJIdm9zSmwzMUR3Wlk2RU44YTNIb093eHlF +RG1UVzQvVEhJUW5manJ5MlJCVGJMaHdZWjFSdk9Lb2c0YmtwaDNZbytmanI4YkFGNnpxMFg5Y2lv +c1hITUZnanpkN1lza25GdXh3NHROeitPbWhHcmJmM3RaUlk4TUdWLzRRdTczMlZYZno1L3hwLy93 +NWY4NmY4K2Y4T1g5bXpKOVpOMzkrWEgrK05Cd3hHb2NJU1JUcHNvbVlrQ1dJQnRoRU5BSkxZaDJD +UE5NUXd0Qll4UGlWQU0wbFhvVjQrMkc5Ny9ZUW9jS1FYTE1RaXpYMjJhODQ1Q1BPd2VGTkNzTVR0 +T2JEaG53NFBWTlBNTitBRHc3RXU4c245emNEWGs3NUNGTXpORlNOR1c5dmFzVFVFSTBTR3daY2Vk +T0VFOFNMWVY3dDRlWHFjY2tucHVaNkU4UVE0ZURXaUlzVC8wUzFSdUlPOTdEbHdMazludUVWemp5 +N1BQWVJDbkc0ZXVEaFIvODBGMzk2Nm5sajRNazhqbkVBUDh6cWR1SEhKWS8rRVFyZWZDY2NWeiti +VXdPZXhYWWdXd3VMdkhLV2MzajBRUjhkWnA2cEFRNXp1STFCN3Q0RGpqbHFVRG4wUVU3NHF3WGNP +c3h3UTZjd2lGTU5pZVVRa3M4UUgzWlk3RmU3WFBUTVZMQ0lWZE42aHVmMlJUL1U1N20rNjY4NE5D +cFgxNG5Cb1BqUlZ6bGhWcjkrV2RNREhHYmF0ZmFiZHQ0OEpaZTk4K2Y4T1gvT24vUG4vRGwvenAv +ejUvejVtZno1VW9DaWdERXBnRW5KL2U1dmZzS1g4RFlwc29JZ1pPSmtOSHV0RTlBOGd1MTFhVGFn +RWVjTnBIaERZRDFTZkRlUWF5OXl4WmJEWDJVclRyUGtoMDBjcEJBQ2NjQVdveDJCbWdaUERXcE9m +WERKNTlJWUJCbmRKNDUxaEdydFkrRG5Yd2tqWnNRU2dVYmc0THN6TkhQQmd3UGt3aVUvN0lUSWlP +clJPREZ4SkxmMXVGVzdHbHlFNU9EeFhLMXkyQWViZGNScHpqTnZsanpITWZ4eU9EZ0l6ZUdobG9q +amNxV213d1M3b1E1OHEwTmVjekhvMnhyeDhVdzQzdkFRb3J6d3QvOXFOaStYNzdEb0hZeHlNejUr +Q0UvYzlPSzQwRWY2TW9kVGErMFZoOWpGMVhQMU1vbmU5WTBpREhTa1J2eFpBNnNld2FLdjd1WThs +ME51dlBvY1RxOTJ1ZkRtTTAzaVNZN1d3WHh3K0s3M250R2p3MFpPbHpucjlCZ25OSTVuL1lVYlR0 +aDloa1d1SGs3cXNOKzh0ME13ZXhiZFh5Njl3WVUxc01JM2Y4NmZYU1ArL0RsL3pwL3o1L3c1ZjVx +YlArZlBqK3pQL0RlZ2ZldGdFeEpzQUZEVEZDK2d4Um9sQ05CQVdkL3Y1aFhISUg3NlJRQXhJVVQ4 +RUhYQ0Y1L3g3TGRYTGcyMkJ3WnhySkZiRE1VcEJEbUt0KzkvLytQLzRSOGNIZ2l6aC9Ea1VheGNQ +VUFRN0JCaUhvMGpHRU04OWNOUFNHSVRscGlHZXVUQmhYV00wQmlFbUxXWHczZHZnTXpMN1JuYzVj +WmRiYkI1cmthNDhJSWpac2VKWnNLTkMvTnkyUXUvWEo3SkM2ZWFYVGdRdDMyRFV5ME1hRDNlUERN +MDNiTWEzUjBHenhqR1FXTXZzK210dS95ZXlkUGVFQkV0TUxDNjhNaUFhbVVLOGN6cDRkT0xzNUpE +OFhodlgzRUtqL2lNYXozKzVNSjdPWlRQQlE4dDRZWXg3RFB2ZTdSeGQ0YkZnVHJVN0NDWFN6L3hG +NFBlb1NhT1hPTElyUlo1bWJVYWgwSE5PUHBtMERkc2VKZmJQSDNvdjhORlBEaVlVUzYxdTRzTFAx +NXdhTDVYOCtGUlBOalU2N20xNnBnLzU4LzVjLzZjUCtmUCtYUCtuRC9uVHpWL0JuKytOTXJiQkEx +aUZBRXR0TWl6RUh3L1lmdEp0aHV0TVZkRCt3NDRnUUdySUhIOUtvR2ZsSkdrVU9KRFVOL3dBS3h3 +Y1JSaERiQmlpd05YM2l5Y0lJaFY4UXJwZjZndHQ2YkxvUUZpRWh3QnkyV0lCN3VtTVNrc2lER1FZ +UStCYXl5REUwckd4VldyQm5nR2o5aGl3TWRVeVg5NUV2ZndhTHAxd1h5Tmx4dC94R0llZHV0eEpZ +YWFjSVFyZS9GbVJIUW5CdnV0d1N0QkY0OWFLalF4ZS9oVVJENTdDK016amg4K250L045eGtIRlph +NzJ2VVFEK0g1RE9kUXJLaGM4RmJrK29GVG1NUmtaS0kxcHc0NVlCVXI4YTRlbk5BUkhyeE44eGJF +SEJHS1Y0T0tnUWM5WWdMUDlkZjNHa2lkeklCTFBJdGxqemw4d3hyZXJ6NzgyTnRjMXJvN1ROU0RX +L1hUdlBqbTdKY1hGbHhiZ3dQOXNFWXVkN0ZwUWk3cmdsMWY3aEpmSEhIeENBTjl3NmhlL0RvNGFF +Z3QxYjY4MXJoOG56L256L2x6L3B3LzU4LzVjLzZjUCtkUFdEK0xQMStLRU54Q0c0RnVVeXlReEUv +ZVBsdWpVRWtGa0Jnb3o2enpHVmh4Q0lUd0ZFY0VOWVptZXB1U3d0L0k4allBWUVMVFhERjh0azhE +WEo0UmhjTDlpb00zRFRISU5WMVI0akNDNTQ4Um5rRkFST0VPSXhOR0xEZGdVYjk2Tk1rL2dTMkhn +VHcxRW8wOENQUVdoemhnYkcxOUMyVmVzMkFnSEtJVUN5NTcxQnhjWjN3OE1ZTzNDbi81MnorZjUy +SXlycnoydHNIMlcyOU9QQWVLV2h5bWhDSU9idlFIcCtyQUhTeUVYZDRNejhWMEtNaHRyM2h5dzZC +SGFpVStoc083MnVRWEYxOWlNYkM3L0hvSUx6eDQxRE94Y0ZldDJJZHorOHc3M0dDdEJ1VERwZDc1 +YmowTnVSd0MrWDVZY0MyRytzWEZKZDFZNHpuc3hDNFdQdm8yeXB4NlBMY1dsK2JGOVZudjlGbE44 +b3REVS9Lb3hYYzhwUGFyVlJ5SGh6bDkxWE9mOGRRY09TenZFdDlhV1BIZGcwV2YxVk9lOU50K21I +QXRwMnYrbkQvbnovbHovcHcvNTgvNWMvNmNQOFg2TFA1OEthRG1Vb1NtK2k2NEJXbnVtVUFRZ0Fu +R3ZBczUxaE81NzBqVGJJQTB2cjhMcndBa0lGbFQvWW9Cb3lnR1lFQ1pIV25XdzFMU05KYXh2VlZD +QWd6K21yNm1NNDhFc1lpeGdzczQzUGJJWlE0T256MDNFS21KNXNRUTF4MTVjc0tHSDAxalNHODJQ +SU90aHZJMkFQRXdtMk1DZ2lNdVRmVWQ0WEpicDFFNEpFNHhpRlVkY0QzTis1cjllR2lqeERIbk8r +TTVSSEVKUDN6NFY2YzVmY09qUGVMSUFhUGhzejdoeUhvWTdPK2hSaVExakQxNmt0eUhWLzhyYUhO +dzZiM0xPbmpVNkhmOTFRd2p2R0xTU1hyLzlrWXUvYmpuMXV1YnVIU21EbkhWaFg4Y21tY3d1ckZP +YjJBUm0yNGNETmJYT09VTVpqblVhTC9uT0xYVzVibGF6T21uL2xxam50Wm12MmRxVVJNTVBaVFVy +Si8rSUtJZjNNcHB6cDRlVGd5b0RrTU1zWEZCdDJMb0YrM0FZeCtPOU04NjEvdzVmODZmOCtmOE9Y +L09uL1BuL0RsL2l2RlovUG1LNkcrUjRtcFF4R21raFcyQUlBcnlrN05nOW1pazc0Z2xNcCtCUXFT +Q2lVOGNjNG9BekI3UHZTa1EwMFcwNXF3THVTZEc1b2FqVGZEUE9mdU9CRVpoMEVmUVgwSWdNaXMr +Yno4NkZBMnZXalFpSmprVHVPRDFUQzB1bUlsUFRpYkdDZEZydnJ1MThGWGc1WVBJTmNKZWVleFZU +dzhGKzNCVVhEaUNWY01jUE5hcFIyeWpCalV2bnVhN3k0azNJaUI2K1IxaWFySkdIamhnSWhENG1Z +OElEVmplNDNHSFhYNXJpWTk0OVZ2TmNNaFpMVmlUZFNjaW5NUExXQTVrYS9HaVQzcEtBeFdlL05i +Z1IrL3dvRDRZN0xjV2JuWFFnTTk2d0tSeW1EZkV3U3M4WFpQbmg1VXVvck0zVThuck0vMHlTTFdG +Rzd6RmNGZUhXcTNGdjVyVWh5UDE0Z1hPbWxWT0dLd1JVeDM0Z3dkMnRmQ1EvUGJiaXdQY09GamtG +SU9lWGZnUUZ4NDhXK3NaWGF1blBaOC81MDlyNTgvNTA1NzVjLzVVMy93NWY4NmY4K2RIOW1mK2Ix +aGlzQk9vNEJZclJsSVhNZmhwMWlhZnJTTldoUUpaa1F0T0ZPNktKanh2Z2hRSnZEdENrY2tJTGN4 +KzhWM3lpZ0hjWC8vQkw0YzBleFRYeHJ2a0p3YnJ6TUdNVkFUNGlkMW54alhVNUR1eTRDRFl6SjJK +aUJ3eFNGSVh3Z2xVWGVaaHd3OHk3Zk1aRHo2cmg2QTFqK2pnRWc5KzVxOG81SmJEbmlmMzl4R0V0 +eVI0OTBiRjNkc3A2OVdqb1pxam1mSjVTME5jZUdaUW44VW5DUG5NdTR0ZlRxMGhKTGtJM1pEVGZ0 +ekJqRWZHdHFmNTlJY2gxSXhmK01YSG4zeEVyRTVZY09WNWpITjQ4UDlYdi9kTGlRVVBqdVFYeTd6 +MURtYzQ4QzAzL21wUStPVnFqdjdCb0llKzE3UndlVTR6UFdqeHBMODRkSW5qTzN3TUE0OGN2dE14 +STRqYlB0R2s1ekNKM2NOV1RmQ3BWMHkxeHZSM1VOQkxjbDR0MVVZTkpvLzFQcXNEZnV0aGtWTWRu +alUrcnF3VnU3MVQ1L3c1Zjg2ZjgrZjhPWC9Pbi9Qbi9EbC91c1R4SGI2UDdNLzhDaTZCSUJKZ2l5 +T3NlMllUVXYxMGI3RkxBbUFFUVR4QTlpcUdvUUFqYnI5UDduZXhKWlNZQ1FBVG4zRVJvaEcrSzhD +YzJPSVF0UDhRVy9HR2ViKy9Mall5ZkJZZjRjeUdmSmpVUW9CeXdXQWdBbEZFYUw0eEdRRTIyTnRV +NHZGTVBuRzlRVUNxNzliVmlPSWpscEhVVCtodytRd2pJZG1qSG8xVnAzc09qc09GVjN2dzVVMllQ +UTRXNndrTzMvYlVhQld0ZlRDb0YyLzZZUzlzRGluejVxeUZ6OTA4RGx3NFlrNTVlNUJaaDIvQ3RK +Y0FtWlI0OUlLWnpPTkg3ZTQ5RU9tbEFveEc3ck1ESjF6YzRXVy9PaXRJc2ZVSFhybkVVS040YXBU +TEhuMlZQMzlZWEN6NzlDSTQ3cnU5ek5RZTEzajZBb001M0ZnUEIrUGJoeDlyOENLUHVMRGJTMDgw +WXM2ZTFtaWR6M1FEbzBOS24ybmZ2TDN5ZFcyTWRSaktxMmZNakN2OHcrRzd2R0pZSjUvOTRlMWk0 +d1NPYW5iK25EL256L2x6L3B3LzU4LzVjLzZjUHorTFAxOEVLSWdpQUdGSUJST0x6UVRpSjNIQTJp +QkM5eHlKMWd0dVR1TVJRS0ErTTZFNDFvcFpVSm9rSDJDRUpyNDk4aUdSV0FoWEhyKytvTGwvODZO +ZmVRcTgvRVNBVUUwd3J4QjcxRUxBNG5odTJJdEVaSmx6OEJoeXdrd1VNSXZ0VUVBTUxBeXNaakhW +YVg4eHkwdGs2aUVjejlYcE01THRKM0pDS29kcWdkR3dobEc4UlZNWC90OGJGRWR5NjBlYjdUTnV2 +RWtpRkx3Vmk1cjBRUncxdFgvRWdodjRJNmhiVjVHTDVlNjdYcm5MUlR6V0VoU3U3UkZEVE55STY2 +N25NTURaUGJqeXoxVERTbWpxTGsvNHRFNmRjc05wVGg0Q1Z4TVR5R09mUFhMSnpVZ2Q4akI5dVRJ +Y3FyalYzeHBVSFBmaUV0TWFmSmxUdTl6NjVMbmM0ZnZtclMydmVORkg4M1Jpam9Id1RUTXg2UEhz +QU83QjRiTFdmblU1dk9BUVM0OThkcmpUcHUrNHdvVjUzS3JITXpqbnovbHovcHcveFp3LzU4LzVj +LzZjUCtkUGNUNkRQMStLSWpBaXNGRndnQkVBSk5ERWJKTU5BQUVHb1BWdGdqbGliMFBFSkNaQzFF +ejdhbENDa0VPdXh2Y1dBUW1LVXFpM05acnBPWUg1bFFWdmxoVE1uSEFyemw0WWlBa3BEZ1Z4K3Zh +QWlPV0M2OXZjRFZqazhSeTUxakFSd1JXUG1xeVgzOTBiTDdscWVMWGdpQUFJempwN0NKRjRjVmlS +T1pUa01Ud2pNcGRhaUVXdDZqRTBUaHhZckNWb2VkVnVqenYrTlJHSDRtcXM1L1k0S05UbXdndWM2 +bFVqam1DVlN3Nng5VjQ5Y01CT1BPSjZwbTUzSWhTM0I0ZWN1UFVkWHBjRFI4OWhTKzZMcDNhNUha +aHc1SkE4UFppRGhma0l2cjJBaTliZXZ6RzBSbng5d1N0TnFkVXp3N3lZWXNPTkYvcXpSbXoxdWp4 +VEJ4NzBEV2Q2WnE5NnhNTUZrNnZMSHJYakNUWmNxVjBPKy8wTFlXTEJ5QlBXdzI3ZWVuSFZEN2M4 +ZWlFV3ZPcndqSmJ3NkM0M2ptSHVZVEYvenAvejUvdzVmODZmY015Zjg2YzQ4K2Y4YWMxSDkyZitC +aFJ3Z0RTbndSVW5JYks5OFJDd29JRnFnalpaVWNTbW9jZ2xaaVlERXFFRUNDaHdtaWdIZ3NSSGpD +WXlwMEkxejlzYVJzNmUreW5lSHI5L2JaNVp4ZE5VMkJTRlNDUWpyZnU4UVpCRFhKZ2RJRWd5dkxH +UVIrMDlNTXFEdmVZUjFVTUMwZDVheVlrejYrRk8vS3NSWDJuYzhXQS9rVnRqdmNzYSt4d2NoS2Qr +ZUgvOE83K1FtbUFqSHBqMVFZTndyV2t1TVFqQlo3ak51VHdUaHpEa2tKOUExV25lOC9iS1hwalU0 +akt2NWg0NERPTnRodHJWSlM2Y2Vvb1hRbkpRK0d3T1h1dlVLNzRlT1lqTVc2ZE8rZFJ6eDA1d09T +VHhMYjg2WVBQY0hybmcwRy94MUV4ZmpLMEgra3kwNmpRWDQ5OXdWd09jT0laVHorVVgzMXExTzRE +cHpWcTQ5Y3RleHRabmI0ejBFV2Q0eENHYzZkL0ZrMXZOZWF0MWQzdkVnakhtdjNqbWZLOUc3TVVQ +VEdMSjJ3UFQydnFnQnhaODh1dS92ZlBuL0RsL3pwL3o1L3c1Zjg2ZjgrZjgrWm44K1VLbzVCNGd4 +Z0xnRklRc1NUV000Q1FHRENqaVZieDlpQkRNR3dJSmtlbjMyRFZNTE0wa1FtOEprTXk4d0xUZ05s +aFJMamsxRVM0R2hNbHpCblVJL1Bsdi9kdzNJUkI4bTZ0eG1pQWZ6UEtKaFNoa3E0ZWdEZm5FMTFR +SGpFUEVyejZJeFhqeUlrNFQxRzZmdGNnVHgzTzFxRjl0cWVWeXVaak1malZxdU1iQXA0bGlXd09q +dzBROWNqdk1zdmZxMGJ3S1hCL3N4MzFFYzQyRUJkOHVhOFNxbU93VHAvUHd5c2tFK2lhZUN4NTcx +ZUd6bVBiTGI2Z0xMNTVacjk4eHlHRm9UM0VsbDFvWkcvZDZhNzAxMzJLZU9RMzYwbGQ4bWNNWlhB +NXQrZlJKRC9IdHVUZ09FWi9sMWpNNXhjU0RkUVpzRGpnODJzOEExU2p0NkpkOStrZHZjbGZ6NnUw +QktXWVBEOXkxVnBkZXdTdWVmZnFoVnZuRXhBY3UxTWdYNG9rTGZ3OTdlZTB6ank5RFRqMmpFNWda +V2EwOUpPYlArWFArbkQvbnovbHovcHcvNTgvNTh6UDU4K1YzbWpWSklNS3cyYWFLUzJCRklFZHdv +dFZRbS8yRUs2QmlKR2NpWkNDTzRGeGlBUzllZjFKV21HWVFpQXRZcGxhc3RlS1oxM0RGd2VRNUE4 +SHJEZEUzUVo4SjVUWVBsejB3RUlXNDRoT2E1K0tMYmNTZzk5MmNaOGo4aXgvK0lBMGdhSmVHYUpn +NmtXZXQrdDNsSWdRWS9Ob0FBU0RXdkRjaUxoelpwK0U5MklnTitlSXlKODdnVUkvbnhJNC9UY05k +R3llUGVKNjEwWjdKeDV6NGxjZGE4MkxCUkV4aTZpTk91MDVlNnh3YU9CTFAzVnFjdHMvV2k0OERm +WUNCVm5EY1E0ays3QlBMMnhiR3NxZUhFNTRNK2xLM2RmS0pKYWJlMTZCTXhMRHRUd3o2Wms3MzFp +Wi90SG5EZXJua0xsZjJxdDhhbk1sRjUvcWdUamowUXEzdzJPTmlMTEZvb29la25KNzdiQTl1Zkc2 +dGVCRGJzM2ppY05LYlBlYVpGaGJ6OU1VSHZxc2JUMnFrWDVoaHdJa1lNTXlmOHllTzVzLzVjLzZj +UCtmUCtYUCtuRC90L1F6K2ZQbEp1WWtVcW5nSkdjbEdGN0ZMTENpd2ZscVd6SG9FQ09TN1FpV3FR +UmxLazcwcGNtY1ljeFdtZUhJeGcxaklGWWZnR2NZK3VXRmhDdWIwek9YMzVSOXNYME9rZVBKYjQw +MEIwU0RHTXlMelJrbU42ak1ZU0V4emhPSFhELzc0MTM0cWRSSVNUTWpTU0hFSXlsclAxS0loMWhJ +V3JHcUIwNTBna1d5ZG5IQnFESjQ4MTJETlVLZTh1QkpQSGdlUy9IaXhEeWN1MzJFUVg3M21pQzdD +ZWpNL3JHb3hiNzFlRVRZUldnTW5QRDdyRStFU1V1SmZMUGprdDE1ZHpFSmtlUEpkRGMyTEM1L0ZN +dFJsdmQ3Z1hCNDUzSEhKd09xaUhUbkZJRTQxMHdXOGVnZ2pIdlZQTGVMalU3L0V3SnU5YWlOaStq +QTg4K3NQY3Z0TVIyS3FSNTE0a3hjV2MycWlPWEU5RjA4dWUrVmpVdnNjTU9iNHhIZHpOSVFyKzhU +cDRhQ3Y0dU1KVHJ6YW8wWnp2dk9MR0h4Z3JXZGl5U0UzVFZrdkI1eno1L3laK0Jkci9wdy81OC81 +Yy82Y1ArZlArVlBNais3UGwrQkljVWV5NEpxQmZNV21hVWNNTWdoRUVVU2xlQVVLcUltYUE3Q0xL +S3p4S3dXQWVxWlo0dnZwMkhyaUZWdVR4RGRuYmQ0OEhGRE4wVEJGS0FyQnpNZG8rZjM1aTRrOHd4 +eXM2aUIwY3dZY0JJTUV6OFVUeXhCWERnVER3Q3grOWFFRUlaSFFZYlhIWit0aEpqbzRjRVZNY0p2 +SGtXZCs4aGUvaGxDUEJoQzErSTJCYzRlVjVyckV3QVhjOXJoZ2MybVdudWhCNW81L2Q3V0xhejVt +dXMvNjUwMkd0WG9rcHM5RUppK3hwTzRURDFIYjQxbU1jZVowNk9tejlXclFFM0gwQ0VaNDlGTnVH +QXgxT1JqVklWN1g0VjQ5ZlR1b1Q5YVp4NG1Zc0tvSEpody9QZm9Tek9xU203bXQxWE01NVZlbndZ +UzQ4V3N4TUp1blIvSDFBYyt3bWhkSHJiN3JzNWcwZ3BkbzhQYVhZL3ZodEJZUG5xbERYcytyMWVy +TFB2bHdwMGIxdzF2RDRWc09od0pPNk1UKzVsT0RXdFAvdC83T24vUG4vRGwvenAvejUvdzVmODZm +ODZkNWV2d00vc3pmZ1ByZGR1UUFqeXdMSlFWQU1nMjJVU0FDUkk2aUVRa3NnU0tFeUlsRUFvMVhP +TUpqaEd1YTUzN3laMDU3Z0pkSDBjREtoeFNGYVRSczFpUERmaVI0NWkwRHpIQWFDaEhQZkFWbHFF +VWM1SnVIeVZyRFd3bDFpQ3NPNFdnZ0hDNVk0WU1kcGw1aUVabzdVVEtsZkpxSWZEeHBFdEdJM1FN +Z0FyenY5dUFOVHc0TWVQR0tPL0dab3owZ1RqekxaYjkxOHVEY1ozRnJoSEQwWmpvOTgrYklmclhn +MXo0Y2lKZTl0ODR6UXRLRDFvSnZnNGpoYzBWa1Y1ZTY1VWsvTDVmOTFobjRFbDg4QnhCQjRrVSti +ME55eU45Kzh3NDN0ZXFGZmZqeUdULzJ3R0c5TlhqQXE0T0QwSzJod2ZUaGVPcW9adkRhZGJDcTNX +ZngvWlBnUFNCeEpDOXM4T0RMUGhxUzAxNzE0VkNONHNKRE45YnBoM3J4aUIvemVPVWg4ZkVtcDdu +KzRkYkRSMDQ5RkVkZVhNblZ0ZkhoM2EyZFArZlArWFAreE5IOE9YL09uL09uT01iOE9YOStkSCsr +RkVxb2lMR2hFNEpMVWhONGpnU0YyZU16d2wzV2VsNUJheWJCZVZPVHRWY0VralFEOFF6cXI5Ty9P +MU4vTStqOTFHeHZnWG9iWkkrQkpIdXNaMDRrSWx0Y2dwWmJQUGtJWGt4RE03eXBzdGZsd0VDc3dV +RHZEYUM1WXNMbzdaQWFTcjdHK056MVNJVU5zUldMQm5qdXUzcVFiSTMxR3V5NVdBeURNNytXNE8y +UVE2TUhoRHBxVUUzR01VNWdWNU1jWXZoc3pocXgxZXhlUS9oTVlIQ3BrN2hoMTJlNWlNTTZzUmpH +NS9UK2NNRU9neGd3cVVHc2FnTkdlUEJSY1RHMXZzcEhxSjY1YW1KYzZDOE81SXQ0cnc5MGhtdDdh +VWl0NVFqdVlpVm9ReHc1R05aYWVOVm1iYkhoUXIva2hzK2JKcnlvdzZFdUh4NGM5RENJMVQ5d3JD +OSszMkd5VDF3eDFBeGJUYTRXSnJXbTY4enJEYTNDMzhNUER6bDhEck9EZ2M3VUZkM2NQajJYais3 +TXVWZGI4K2Y4T1gvT24vUG4vRGwvenAvejUvejVXZno1OHRBSG9JQ1YyQ1pOa2NSM1F6QUpGVVhv +aWtFWVlpcGlqV01VZ0JEbHUvam1mUWZDVDlpZU14a3k3T3RQM2Rab0FQS1poVUFNQlJNeVVuLzh1 +NzhZWWx4RVpNREZZQzdHUnBxaEdYNzZoMEVkc0ZscmFLcGFFQWdEMFdnZ0hLbnJzS2JSUjd5OWF1 +aG51TVFTRndaekdtQzkrR0xIb0NjUzMvRXFsL28wMERNR2xkTi9zSTRQYzNnblZ2d1JBMnptNUNN +aWdtWTBHT1FTVTNQVkt6OCtyZGRMUFpVL0FyNlk5aENJM09Lb0JjODR0VWZkUk1HZzhPcy9UR0xi +WTU1dzdDVmV6NGpOZDRKVEYwNzB5Vjd4NVlPanduVlhkdzl1NWxPcmZPcjFYSDMwQUxNNDR0dmJI +RGhYMy9zRGpmblZyMTQ4dFJkMFk2akJjMzlvT0V6a2FsOW9SQ3k1YUpybVlYYlhqeDUyOW9wcDhJ +TFkrT2xocVZiek9GS0Q3KzV5MFlJNnJPVVg5Y250RHFzOHVCQ25mempnVzUzejUveHB6L3c1Zjg2 +ZjgrZjhPWC9Pbi9NbmZqNkRQMTlBRVpnN0VrTHVGWXBBamZOZFVaclJ4aE95NUlBS1hDRnF1RGM0 +UUNpSVlDVFhPSTBCVWpNMHp6UHhpUXp4TG1JaFd2SEVZVGdDczBZOE1md2V1MGJhSzdjQnUxd09E +bStFWU5jNFpJbW5xWWdnSHZzeUxxNTE1dFdJU0kxQ0p1SnFraDQrTU1PQkI4M1RjUHNJSDE3UHpk +dFRneUljTDNpejNuOVk3amxocVkvUWNZazNPSEFGTi82YTI1eDhjdUN2dlJLekI0L0dtOE9CdFhL +cVY4NW5mSDA0UDk3VTRwTFBQSkdwK3g4YmdUREZzMDZ2Q1FmMmNpV0haOTNIVUdKWjd4bTg4aEFp +emRSRTZoVlhidjF6RU9JTGQvRGh3Q0doQjNoU3ArOUVLeDljY3VzakhneVljZUU1UHR6aGxNOWdG +ckZwVGl5Y2xrL1lmYmF2R25HbkRmaXJlOXJNSHdpbkc5aHdUcFAyNm9rN0REakNsOHQrZDgvRXND +NDVqd3R4RFRyRFErZlZFcE9lUWVsZy9wdy81OC81Yy82Y1AyR2FQK2ZQK1hQK2hGTSs0eVA3OHlV +QUlRRUVyTVVLMERRRktaWkJyUU1jb2VZSXdYcUJOUXg0TWJ6eDBCU0VhSVI1VGZQWDhXSnJCbkta +VDlHSUFVcmhjbWljSm9YVXk2L3g4c3JSTjBOTTZKK3F6dUh4L1pmY0VTdW0vR0lhaUlGQlBIWEEx +TGRIYW1JUWRiU3A1aHc2bW9oQXZOUVF2YXZCT2xmV0hDNk5xVUI4Wndna3d5MjJ1dVNDa2RCdzZN +MlZHcjNSVWljYzF1Y3d2SGx4N0dNc2MvTG4wRGhNY0dpMFMxN1BDRVJNM05vcmxyMndFSlNlOW1E +Vko3alUybDZMYVIzeGVNNVVldUhDTHk0ZGVPcUFSYTM2YjUzMWVoaGVycWZCZjVqa0VwY2V4SVlW +UmpuVlRodjRvQW1mNVdKS2E4VlRnKzg0a2F1Y1ZxOU1iWmhqR0Rvemh5YzhQUFYveVRQWXpGY1RN +T2lmZXEzRmdacG9DMmJ6NnNSeGU1NyszS0JKK2F4bFFseGFyNzRjTm5lM1Y0MzJlQ1lPUG5DbERu +ZDFxUisvUFhob1QrMXFVTHM5clhmK25EL256L2x6L3B3LzU4LzVjLzZjUHorNlAxOFNFTEF2Tmdw +TUVJcER0bVFDMVlpQ0lSSWdSQ29HV1lJcjFKc1BoUG1NQ1B2Rll4d2dnQlhYT204VUNFdkJZc0Vp +cjd2aTdFdGpyeEdFNFNkeUlpZTBFbWtvUml5WXpOdHZxSWRwWVpCYm84UXhOS3Jtc2Q0Rmw1L000 +VWVvOVo2TEE1ZnZCS2syTmN1TE8ydnhKMGY0dXNNRzhacXVEczMwSEViMStNbmZ2M1FtampzK2lR +ZkhCQVVEWEM1NXpUbGM1TUZ6K2RGOC9iQU9kcHpnekp6bmVsbUQ0aGtYT0d0Y3dwTWJuMHlsTjRU +RGlPcUJYMXc1WVZLN2ZXS0o3eGxNK01FWlR2UzhCblhKd2FDRWh3Y0dGVk5OT0JHRElPVVExeDBH +L2RFcnZZZS9WK3UxVnY4OGs3c21VYnVld0dLL3ZOYjZ6bVRXcWtzZDZ0RW5ieGJGRk51dnpxZ2RQ +dnpoV2p6cllXRm8vZFVybXRaREhLcEp2ZUxiRDRmODlaVmVlWVlMOWI2UEUzMWNiQmpNOVhCcXIr +ZlArWFArbkQvbnovbHovcHcvN1RmbXovbnpvL3Z6MjkrQUlsV3hBTFVCaXBiQTh6WUg0UkpWck5i +RjROZDRRSWtBd2NoZ0tDQmRSR0F0d1NETFd4R0NWUmdTQVc1dWU4M0xRN0J5SWdzZXBHZ3l3Zm9K +MzRDZE1Gd01XdU9LYVoyWUpSbEdBMmx0b21jK3c4TThTQlliSGlJU1IvUEZ3WWNjWXNYMGQyaTV5 +K21PV0VLSEtibHZuVnJzaDArdGVQRGZBSWpuRFpIY2FwVVQxd3dpZC9pNC9mYmhSQk0xczJKbURq +MkFTVCs4aGZ2dXphQTlVR0dSVTM2ODlWQnlRRnBucjg5eXl5dW16M0xvSmFIYUk0LzY1SWRYVCtS +d3R3OW41cnpCaTFZT0gveU1obE4zK1dqQ0hHSExnU2VYT3ZWWURHdUpWRi8wRGYvNjVRK0tDbGM5 +NWh6dWhDNlh2dUFhRjliQmFmaHVmWHAvTWRMTHk2ZDJoNnhmZmFHRGVPRTRVejlPUElOSm5iRExJ +UjlPN2RVWGRlZ3pIZE1BM09KNHJzN3FDWGZXcU5XaFlKMkJLeHJEdTN2anc0K0wrWFArbkQvbnov +bHovcFJ2L3B3LzU4LzU4N1A0OHlVeDRBSWdSdkVBdVFES3B0dmdKM21GRXBmR0FRR2tCZ25tMGt3 +aXR4ZEFieXlzcXppc0Y2dW0wQ3pDNjFzUk1ZZ1FTUlVROG1FaVBQSC84cmQvUHMvdEY4ODhRdjJV +VDZCKytoZkgwRmhGd2cySFBXSVpOU2hzTUxock1BS1pSR3oxRXdHUzFkUFBHZ09qTldLRWkyc09M +bHdPSVc4QzdNR2YrTVh2OE1LaFg3RWdaZ2NYWERXY1p1cUROZVZPWStVVXkzTTRQR01NWEh0bXYz +anVPVmdQRDNIaFIwNjgrQjE5R096MzNUNWMyVWVvTU1QdnJnOFI4c1dqalpyV3BYYnp4Q1Uvekxq +V0Y3SEU2RDU5OWQwaFlWNWY4ZUd6V0RnVVIweVh0ZmlIRzJkcXhpZGowUTdNNm1Nd2RSanRjK0tm +enV6Ujg5Ui9vNGNTRGhrRDlxeTd1Kyt3eXEyWDFzQ0FYekhrd1JITlJtL0dZWkdiWHVoZC8rVEhC +YTd0RXdjWE5hMFkxdUZLMytqTVVMdWE5TjQrQnJYZWM3aXI5Zmx6L3B3LzU4LzVjLzZjUCtkUFkv +NmNQNDJQN004WEVteFVER0ZJTEJraENtN2VKa1FyQUtuK0dwZDRGUkp4WEJHdS9xVHNKMkRnR1Jr +SmhLcloxaE9DbU9JZ1VreEZBWTZFQ3I2aTFpaVlHc05QOHdxUlN3d0ROa1I1cGtsdG5Ib3Fic0lV +dThSVVVQYkJxRW55aWFWZWhNRWpGaDRJMmpQNDFlcVp0WGpMOTJ1ZStwNG1QditoTmJ4SUZwYzVy +Q015K1J4ZUdxaWU5OEt3MStHaG9YS0xMemY4WXRrYjAxOWRqR1FkY2VIQ0d0emdyNEpRcjNpNGtV +T016dG1EQTVjMWpLRjNQdXVyNXppQVMrMzIyUU12ODhtUEI0SXo5TWhGWEdyRkFXUGpSSC94UUR1 +NHhvMWE3SWNYTHJwVHM3V0dQSEFRcko1NDNscmxhZitaMzJmN2NWcGo0WWdHeEhIaEJ4NmN3aUZu +dVJFWEZyZ1lEZWZ3aVNrV3ZlczlMRFRwdXo4UWV1RGdVNTJ3aUF1ZnVIcUZVeldxdjFwL2RQZzEz +SXFsVGhqMWdEWnBTeHg3NXMvNWMvNmNQNjJmUCtmUCtYUCtOT2JQK2ZPaisvT2xhWnJnZ1VsSmdC +UVFZTUFsVlNnUWdoSDFOL0hlZW8wRFJESG14UEgyUnh4enlOQnNZRFFQY0h1QkZNOVAzRUJwWEl0 +RkxrSU5oQ05WQWQ2b1dLY1JZaGlFb2c1aTl4d1dBeG5pK082d2dBTWVROE0xVlE3WVlKVmJMSGhn +c3hkSnhBbFRCSG4xaWlVM3ptRFRERnoxc2w4ai9YVjVSUVlYVGpWRlhtKzB4SEtYdzFwNXZDMlR4 +eDVZOFl4VHpiVk9YcGZuR20xUFRIdVhkWGpDcVRYcXlWdWFFNGc2MVFPRE5UQmI0N2xZVEFhenRZ +U29sK2JVRFF2UndtdC82cjNQNmxlTE9YeGFydzc3OFdtZnVQcXV2NW0vM3N1dkh2VlpLeWJNY29x +bmJ3NHkzTUhvdTZ1SG81ck5oV3ZqelREK1VLQVAyUEFoWHpsU296Nkw0VE45eW1WZVRIMzBuZUhF +TFM4eDZPbU5ocGdaRG42QVdTN3IxVUovekcrSVk3OTg5dWtuSC9uZXd5eEd2OEVQUGFqVUlMWWNj +dUZ3L3B3L3haby81OC81Yy82Y1ArZlArWFArL0N6K2ZDRlVRSk5JVnB4R0lrQlFKQk9OZ0FRQW1H +S0l4Ynp2UGxkUVRQQ3RXZGNNeWYyMHJVZ0ZhQ2FCeXdtc3ZVU0xLSEdKU2lIMnVBd2lRUUJjZnJj +Y0ljaEJvT2FJNDVtYzdrZzFIQlR5eXFsWThUUzhjMm9XVzYxRXA3Nlk1R0o2dXdFTGd1R1dpeUR4 +NDYrMzFhM1o2ck9mQ00ybFNiZGYvTlI5elNkSTRuRlhxN3ZmOWRjVWZPRkNiczNTSUExbExMbmhW +cnRZRlJIT3hNQ1pmTGh2RFBYYTY3bitpS1haOHBnWFU1L1VMWTY0T0lOWFBHdVpWRTM0ZE1lZkEw +dDl1TVVMN09xSENRY09KWEgwM0dGdWpiMWk0WlIrMmgrNGlrVmNkM0Z4N0xQOWVtcTlIakUzZkxD +SmFjNTZlQXl4ZmNhcHR6WjZJNDgrNFJOV3ZOQ1BmRDR6S0o3RUZkTXpkWWlSdyszV2Z0UDUyMTBl +OGVqYkdtLzU1RE92SC9yWGd6LzFNZDdoWWpoNFhmTExLVlp4aTkxKzI2OW51QlZ6L3B3L2NRYnYv +RGwvenAvejUvdzVmeHA0a0hmK25EL3g4aEg5K2RJZ2w2SlN6RzBBemlZZ0NNOG1KbVZteFFoa25j +WlpnM0RnV3BpZjdnbWVLSzBsVXZzQUJSQTU5dmdNdFBpYUFnUGhBT3V1eVJwdnJ6M3krNnQrd0pG +dmJ4dWt1V0xDalNCRFkrRVJqOUJoY3UrQUM1SHlpbVUrQnJ1YzZpRU9OUktZZlJxdGJ0anNRU1Ir +TklqbzFXOGZQSVJaYzliOFlxZ1ZSdDhKMEdFa2grODF0OE5CUFBuRktCZStsMjl4Q1JVMkhNUFFQ +T3FYbjBBY0t1TGppMG50dzRlMWNLaGJmRU04UFdKVW5GY0hZakNIM0hoV2h4aHc0c0Z6dzhHckpy +ekFJWVk2aUUxY05jaUh4OWFEVC9pdHhVVU5hWThZYXNVblRHSllaODdhOWhsbnpkbkRVMDMwUjdk +NGJhOXhvZ2ExNVRDK1hudkw0dzBlbnF4VG16ckVzVjRlT0dDZ1Uzczg4MGJTaFhzWERMRGloMWJW +SlJidThLMDJhOG9uL2VOWFR2dmw2MEdFRnhqbXovbFRmR1ArbkQvbnovbHovcHcvamZsei92em8v +bno1SDAwREJERUVwaEEvTVNNVCtReG5LQXBCTHNFa0lnU0ZBbUxlUGlKRW9KL1NOVkZzQUJYbmV3 +OEVueFZ1dlZqQWl1Mk5oemNOTlpPMUNQUld5SC9jN0R0U2ExQkZXeXVuZUVSaGFDaGorNjR1YjFx +OEFlaVFDeTVrMXdpSVFaRDFHZ2tMYkhMTHg1RGg1OWJMcS9rVm5UcnRKNWlLWEF6YzRBTFBhdFU0 +OVpsbkd2bmxGcy9BSjh3MW5uMjQ5WjJJOEtUWitDUldhKzJCUVkwUjdvbUlBSmhPZnVhQkcvZDRJ +aUM5OGxrdFBSamFVN3pRQmo0OWx3Y1dhKzJCUlU2NTVLY1J0ZWdMRHNwUnVhK3V3dFhOcTFjZE9m +VHVzbGFOK0twSTViRkdIOVhxdWJwaE5PZGlpT2puTUl1TlZ6MXo2Ylc5Y0lyZlh0TVBIdFJnT1BU +aDFrdjU4RVUzK0pIUFpUOS95QVdIM3VIVVFXUE9CWU0xdEdoLy9RSkR1VllYYmVpUFB4RFZheStN +Y291dkpuenAwZnc1ZjZwbC9wdy81OC81TTF6Tm4vUG5qZmx6L3Z6by9zeC9BK29uYWFEOU5GMXlD +ZEdHRmlHeEF0c2tJS3g1dnc3cGdHZzJjSDU2MWlERUZSd1JhenlpaU1jQklEWnh3dUFpU0QvcGky +Y2dTTUV1LzZ3ekRPOE5xbmtNWUwxblJHV0lxMkZpd2dFUFVqdlVvSDU3RVdrL3NZaFh3b2lUMFAx +Vk5LenF0VTY5TUdzSzAxaWpzWnFNZURIZzBBRDdjSXdEQTBmRWJHMmJDSGNOcWpsaTRrcXRydmJJ +SEZ3NHdTZDgxaEV0VE9LbTBXY1l3aWpuOE91ZlBQSTc4UFRad2FOK3d0Q1BtdDVheitISGhicjF4 +VjY4eWVGNU9iRmZIWERpMEY1cjFVRlRNT2ladmJETFRRT1p2L1h5RWJzYytJTkYzWXptcy9yVVVw +N3NyVUdKM1p3MTl0c2pwMTdqQVVkNkJwUG5ZbFEvNW4ybUs3WEFvVFlHTG01Nmg0V3g4Q01uWHZ4 +L1hOWElMcnpWWE5XVnZKN2poaDcwUk44OU0vVE1YbmM4d09NQWdGbmUrWFArVlAvOE9YL09uL1Bu +L0RsL3pwL3o1MmZ4NTh2L0FHTXprb2xNTUNRU053SWxSWUNrU0hGWkF3Z3lKUEJaZ1k4QnZxWjRQ +M2tqa2lFMVJoenhOVUtCQUFHbUlZQzM4UzVyN0dOMldCQ25XUDlNdForY0NZenBESHVKRnlIVzFh +QXdJMUFlTldxQ21qcmt0cjdDTTZjZWVCQUlCektSSmFjYTdZSGRPcVkwcDVGRUttL0VlaVE3SE1R +VnY0ZVBPbkNqTmtMR2tUcmN5NGRCYVBaRUhJZkxuTGNkWXNDRlU3Rjh0a2NlNjlYb1AvcTJUMzc5 +ZE1Ic2NESXZsNTQ3R0h4MkVNcEJGTllTdTdoNkRMdTFhcEpIVGhnOEUxUDludU1JWnV0cEJHL3Vj +T0FtT08vdThKQ1RUbUtBMjJ2TzkyckVYdnlMcVM1cjlKYzUxQ2dIUFBpbkIzWENMTC8rcU1WZW4v +L3VqLzVORENXM2VtaFRETnBWTjIyYjF3TjQ0SVhEYzNGb0ZSNzE2aTBNNWNuaDVOQ3ozcnlZNXMz +Wkw2WjY5S3Bhd0owMXNLbzUySzQrTWRUVDJqMVhZM1I1c1dHYlArZFBhK2ZQK1hQK25EOWhzZGZj +L0RsL3FuSCtuRDgvbWo5ZkV2dEE3R25tZ2RROFB4RWpRSEUxa0FLSkJVbEVCZ2hndmdNc2ppWVFx +R1FNNHpseVBDZG1pYjBkU1BNT25JdG81VmFvK09LSWlUU0V3QUFMOGZrUFkrSDQyei84MWNUd1Zv +c1I3WUhiL2hvVUdkWVRrMHNNVndkU3JGZTd6K0lpTGdJNXJQYkFpMUN4TlV6c2lQUk5DRVJxamM5 +aXdhVCsxc09JZUJMUHBWYnJ5a0hGNFhEb2dhTWV1UmhDSDhTSjRBK1BXUERnMWQwejg0U09Bd2VK +Tldwd0lPSkFYQWJYRCt2VnFHOXdlRHNpQjd4NlRCemlxczhhc1lsZGZXcTNWai9GQ0IvM25RanhG +NnkzVDEwT1EzZjc4R005REhUVGZ1cTlYRXlqdnI0MXdwLzYxQyt1TmVveFlHZ2Y0SU9aQnJ5cDg1 +eHU3TGZYZjlCdkZDZHM0cG5ET1V6cXhvdDkza1Q5eFE5L2tGN0RSc001MEc2ZmVUbnFCZmxweTE0 +OFp2NXdHdXJSZHp6SnphRFdNYXgrT0ZEd2JCMDgrVVBrT01HMW5zbGhuN3p5ekovejUvdzVmODZm +OCtmOE9YK3F5WmcvNTArak9HRVQ3eVA1OHdXNFFoRmpva1lMNFFlaW9nSmVvQmpoU0VhbWZZaldk +QUlCaU5CYWdBUUtiWUdFNTdzNFJDTjJDM0NKZ3hBNUFQWlpYSmdJVDlNMVVyRU1ta1BsbXFGbytK +RnVEK0lONW9aSDRVUnJuemdkQkFTWGZENDdBQ0tRYXhwc1BvdUZlTEd0OFYwOGRVZTR0NDRBZlE3 +dXF3K1B5SmRQbzNDczJXckJwZjB1UWlFR2MzQ3BNMks3Zk9MWUp4OEJhSzZEQVFhMW1MY09CM2d2 +WHpIQXplR1RDTnl0OFlaSURIMFZRMHlYdHh6MjRKQUEzZTFUaTZzaVUyZnJobGtNZVBXNUdwQmZE +cnc1ZU15cEdSN1lIWUEwWTY4RDJ6UHpZdXVUM3BwdlhlWVpYRCtxRXpIdGh3c09lODNEak1jY0RJ +ZkZmdjg5aGI3QTZabDZjQzRXYm5HR2IzbGQrc2gwTU5DRkdHcXhGMWQwWGIycDFSc291T0d4eDM3 +eHhLY1h1bjk2K3Z6TGNMQS9CODNYYU5QaHJrYjg2WXNZK0pBRE5sam56L2x6L3B3LzU4LzVjLzZj +UCsyWlArZFAreitEUDEvK3A4RDlkS29nQ3hYck9USDRpVlpRZ2N4cEtNREUwNzJJQXByNUZLQTRE +WlZJc1JvaXVTS1kyenp5M1JtWG9LMFRIeWx3d0ZDeHlxbnBmdTBCaWQ0MktOQ3ZNQ2lvSk1KZ3Y2 +RnBjakVLakFpTFFWUFA4LzluZzBqTlFoNngrcHlENUhEN2JCOVN4WERCWkI5aWExQk5LS0VPTXVS +cnB0d2FLRFlCbUlkWERPSXlKeVplNEJCTGc5VWx0dnhpMjRjYmNYQmhYbDhZeXVkeUNRTWVJcFRM +QTRjTFhtK0M5SU40clBIWmM3aDhoaGNPbkxsd3BSY3VPWEdDVzVpWUF5WjQ1Zks4TmVtVHVQN2Ez +VnB4WVRXZncvTGk2aE96d2xMTjJaZjRWNk9lbS9OZC9XTEFwQll4NGFjOWF6elRUeWFWUjE2eDVO +TlArT0RIcjhPamh3WGV6ZGxIc3prTWprdDdyS0gxY0h4WVlKQzNCNWc5NHVOVXZ2cEFEMkR4VEQ0 +ODFhRFZwanJveVYwK05laXQvTmFJM3dNZWozaWVQK2ZQK1hQK25EL256L2x6L3JUZndNSDhPWC9D +LzFIOStaTElWV0w4dE51bUlUUE5QdEVJcUZubUJleWJBSUd0OHhNdVFJeFkwWFNOZlg0bk9ZSytJ +aEJvdlFaN0JwUkdFWFJ5WEFNSUFJWUNCZDdGbklqeHhrUHVDckYxMkp0RDVRYkJ3U08zT1FMeUh5 +VWoySkNqSm9HVjZkVlg4dVJUcS9YVzJxOFI4SWdIUDN6RWl6czEyZHUzTTVxa1ZqVnJPSjRjSm5E +RHBPbHlxOWxhWEtoSFhQbjF4RE5jNm9rR2E2aW11OFJWSzBIcFU0MGdMbEhBRFpjYTFBWUxNY2t2 +WHQ5c3FMMW1yeGs4MDA4NXhWWm5hNjgyNUlOQlB1dGRZbnFPZDcyQXdmZnF3ek14emNPaTd3Nld2 +Rkc2T0hMQWJJLzY1YlVHeDNtTGVQSHg0YkxmTTBNZWErM1JLL1V3SHV6MkJOdmx4by9QNWQvSVFY +b0hOeDd0a1Z0OWFzVXp6dTF6K05JcWJxM3poZ2dYY09BWWY0Wm41YzR6UFRldnRod29id05HL2RY +Nzd1bGhrMzRjdC9LbXAvUG4vSGs1NTgvNWMvNmNQK2ZQK1hQK25EOC91ajlmZ2dBT25PU1MyYUJ4 +TmdFckFkRjRKcUhuMWlBRFVlNzJBUTJJd3BEa3lwNUw1TTR3NGdCaUgwSUIwUUE1eEVVTTRwRHF1 +eldLSUNnWFE4bkZwT1pUcUlQbGFtQUNwcExQc0ZkOW5zUEkxUDI5YVFNK1dPUzBydzJGQlM1N2lK +cG81U1JzSkJlaldqUVFQcnlZVDBPT1lJS3locW1Kc3cweTdDY1EzOFczRHorRVlNQXZObnllRWJE +NUNzeHphMkQxSGQ4VkxrNlkzcUdxRHZqMHlxOWZOSWU2OEdnZnNSSm9jektuZnVERkhQN2tkM2tt +WC91a2J2cFFnL3ptY08wNXc4SHBiUWN1OFdlZDNPcldTOC9rczg5YkZmV1pnOE9jL1BCYkk0ODRj +SWl2ZHZ6cXZVTk5yZFl4R203dDA4K3VyZVpvSEE3bTBoZjRZUFV2ZnVGSFRqblN5OE9oTGp6aExZ +Y0JBOUhxMVM5R2RXQ3ZPeXllcVU5TytidGVmV0xRRTMyb1c3MDR0bC9PZU9IMFEwZjJ6cC96NS93 +NWY3clBuL09uM09xZVArZlArWFArN05wcTdxUDU4MldqUmNEYXJBaUxnZkk4Qk54QWtPSUJKVGFG +VkJUV1N3NlVQUkpyaElTRXoveWFLb2FrMW11QTU3NGpDYm5XV0MrSDU0RERCQ3h6K2YxbnhoUWZx +VEFhTlNoTXNHbUk0VUFnR0pjOTh0cVBSSU9BWUlURm5KaHloNkFUYkF4MnpVU3d6L0JaNjdNNnZU +R3dUdjRJNW1wR1BNenVHa1FrMXNNbW4yR2ROeVJ5TTQrYVhYcGd0RG5tN2NHeFd1WDRKdUxMaXo4 +OXFGbnhpek94TXE1T3o5VlB0QldhT3RUYStoaGFUblVRRHhIRGpzc2ExRDdyeFpOZkR2bGNOQ1FH +Yk9LS1phODZDYzVCMlo3SkY0UGZKYTVjNHRvUG4zejZwbVlZMU9hdENqNXBCRWR5TXFzMUZUVGpX +T2Znd1MzZS9CcUVQVERqMG9VdjJHaFpmbnV0VncrTS9lOGs2S0U0OUZ6OTRlWjZhNDhZNHR1bkQz +QTdGT0RBQTcycWxVYXNkMUNyTS90UFQyTGpRUC94aVVQNDFNa1ArbysvK1hQK25EL25UMWpFbUQv +blQvbGQ4K2Y4T1gvT256RGowdlhSL1BsQ2drVkFDQVNRUUJZem5JV1N1aXNLQ01FUWdDakZWK0FB +dVZkRUFDaFdmSHM5VjZnNWVjUXdEM0FNZW5GOTExQkV5cThnbnhYc2UvOS9mUkFaSVo0SUZhVVkz +NzFwOE5rb1p1UWhWRjV4eERUYU9PU0pTVUF3YXJwbWFGaWJUSFRpcWc5dW1Ld2hJR1RMWlM4c01H +bSttQVJqSHd6dXlKZFRIZUpZb3dkdHRMM0Uxd2JCakJPZmlWTmVzY3o3amtjaTBCZWZ4WmJiWDZQ +TEJaTTNRSDVGUVk3bTlLc2VEbzBlYW1LbzA5MEZFMlBMMzRNQWp2YS9CN1RlaSt0M3kzMld6M3I1 +WURIRXBTazR3c045Rmg4V3VhenozVjJkMXN0anJiY3U2bkI0dU5PUE9zMWJUN011Yy9yaEdmMklx +Nitld1VtYmN1QVdmbHFLNGU2QWNJalNBazU5aHIvYXhMRmNhck9lTnVpSHpoejI0bHFMQS92aFVK +LzQ5SjhjdHg3bnRDTWZ2REJhYnorZTdZZFA3ZWJwcXMvbXovbFRESFc2dTJDYVArZFA4L1BuL09t +N3V6cXRsMmYrbkQvVk9YL09uLzlVL2ZreVNZVEF0S0VDV1V6TWFmYUpoZ2dsQmRZZENBa1U1aElN +NkJZR25LSVFESnc4aWlWNkJBR0FPQTJwUWNXMGwvR3RKeUlGK1k0TXVEeERQQUhBYURBUElWVndH +bWpZeHlEeXFRcytOUmx5MW13T0VHdmtWRytib0ZGTXEzYU5oYzBhYTlWQ05PcHo3NkVpcHpjVjZz +U0pIT3BuQXJua0pTSi92UzBPVFBMRGpkdHlnVnNDZFlrbHQ4dG4rK0dSaTJEZGF4U1hPYitpb0E3 +ODRxNWlFZ01tLzRJV1RPTExUOUJxZGljcXovR3JqM0txVFY1NXhKRUh6eldlZVRVVWo3Y3VPQXJY +SnpqeGlGL05ldFdlcUZWY0YzN2NQUlBET3B5b0E4OEduczNoUis3K0tnYnQrb01FQm5YQll6Lzl3 +S1FITVBzdU44M1RvTjdCYXA5NS9iQldiQmRNTU9BQUw3d2dGeDJvV3c2Y21NYzdMTldpUEdweGFN +TmtqKy9xd2JQNjlRZlA1WlIyWXREVE5DN3NtVC9uei9sei9wdy81MDgxNjFWN29sWnhYZlBuL0Rs +L3pwOXlmeFIvdmdqU1A2WHNpOFNJSkV3Sis5ZTEzallJWENFRFpsNXdTY1FBb0c5RGFsalBrU201 +Q3lIZDR4TGJkL0VWb2VIaXdtSS9FaFdpS2Y2S1hZR0VEYS92c0JpYWh4VDcybmlEc1h5WFIwMUk4 +OGJJSUVieGVrZ1FyZSthSWFjTEZyVmFDNE4xWXFpbFpwVGJuSDBhYW85R3drUGM2bkFvTURwdUNj +cGVZalVucHdQSFJYVG01U1lxK2V5eFhnM3FjNWRUYnMvTis4d01jTmtuQmlFUmpMcnc2TklmOHpE +NmJ3WDB6THpjY2hJcjdFUUNHL3hxcWg0SVVXOXhZRi83aFNNeEhOemk2N05jOEdXY2NlQjB1Qks4 +UFdMb0UwRzc0NHRReGZRTW4rcUZ4MFYvYW9KVHJiVFZBNEIrNUlCZm5YUnFYaDU3NE1lem11eFhs +d1BlYzJad2VNRW5QODd4NGg0OTNWcDh3d1dIOWZSZ2paclZLSmUxZE1CRE5aekxIdHFtUTN0OHQx +ODk5bGhMa3owTW1rUHZjQ0wyL0RsL3lqbC96cC9kNDVvLzU4LzVjLzZjUCtmUGorclBsMkw2ZjNx +ck1jaFNtQWE4SjFtU2lna0pHZ0cwSVBiVW9FZ0RJTVZlVXMycUVCaEdrNUd2S0RGaXBqY3h3V0pm +UkhuUE5WekI5dm1QYU9YK3k5LysrZndFNy9CSUEyNGdCZ0VLOWl3eGJ5Z1dQamtKRzJIMkdrU2dZ +ZWI3WEQ3cjFLb0dNWkZuTGJ3K3cyQWY3TVNxZVdxMGoramtSM0FOVTRPMFllTEM0eGxlMU93U1Z4 +eGlnN3N4Y2FGeEJPTzdTMC9VNnJOOGpBMlBaMFNITTRLTk1FNWdjam5VdlAyd0RqYUhSdCtHd0Vn +d0JLNFAzazZZZ3drSDRzTUFTOS9HOVlDaEFXdDhkd2lKVlVQQUZ5eFhrNTR6aFpxckkvcnd4Z2Ez +K2k2ZXp6MnNQZE5idXZSV3hjSFRnMUFONG51R2I0ZVV2UDRaYzN6NnJuYTlnOWxiSVBGNmFOZWcr +bUZPYlRqV1kzVlVNL2JJNmJKV1BlN3ExRzk4eW1Vdjd1RHhIQVljMXBBd3F4dlA4S3BKamRhNDVI +ZlBRWFM5cTIvRW1UL256L2x6L3B3LzU4LzVjLzZjUCtmUHorTFBsNGZlRm1odU53RUdBQklCbFVE +ekpOWlFSUUFGa01aWm94Q2dpRllDMzhYd25UaVFwUmlGdUNQSFhiNlN5T0RXaVM4WGJJcXhEa1p6 +RGhOejNrcG9rc2IxOE5CZzRoWGJRQ1ljZlE0N2pKMFRUeXdOaTJqT1FQREFyMmJQNWZGV1FZME03 +RmxyZ0Jrdjd1TExCWk1oanBqRW5QcHZuOXp3NHRlYkxyR0pRNTB3RXFQREtnWTk0OHVKMzVvaklq +d0IyKys3ZVhsN2RaN28xYWMvYWlJNGI5VGtGMHV2bVZaZVhJdVZIdDk2ZDduVldqTXhxTnBjaEFx +cnVlWlZtN2NzVE84NWJ1U1ZTeTJEVlZFQUFMSzJTVVJCVkQzNHBJUEcxR2ZhMGlkOHdPTzVPSExE +bnhnT3JlTVcvNDlCdjQ5dzVYUlhmOGJsU0EvdVlGWXI3aDFjRG5Gek9IR0FpSThqdW9RVkpySHQw +Vk00NE1ZSEk4TUNCMTcwOWIyNWNJRlAvYlVQSi9wbTBKVjY2TTFlZThUMFhHME9MTGp3WXgwODlu +Yzl2c3I1L0RsL3pwL3pKMnhxblQvbnovbHovalRtei9uem8vdnpwVmlKTEJKSTh4RVg0aThnOFZl +d05saEgxTWhRSE9EMmlFRnc5aEt0eE9ZUXB6RUlRSlQ5Q3JJT1FUSDBBU1pPZThSRkVyQmlJZ1ZK +ZnFlYnFQb0dRRXg1TlE1dVA0RnJnQmllRzdDTG85R0VSV3dNNGFkNEFoYkRzd29kU1hCcEFBR28z +enppRVNhdW1xenoyWE1jeEtTM1hoUGd4NWM2WWNFWnpCb0poOGJJaHl0cm1FUWVuTVNndDFkY0Ft +a2ZVdHV0a1VNZGNzdUZUNWVjMXBpTFFhOVhlTEZlSGdKVUsxNnRFNU14aWRSL1hFM00rSkFmbDNJ +SDEvRnBEOEhEcDRkRXF4WnhmRFpIRDk2NHlBRlhuOEh5akMvaHpVRUZEMDZ0WXhhMWltVXZuR3FH +dlFjY1Flc2pUSWFheE5LUEhBREdjYW9INmxDVDUrWmhkL2pESVRkTTNlZGZuSk0vQi9YVktaL250 +RVZuZXF5MzFzdXRaKzcwNklJQkovYmd4ZDF6M0lzaEYrM0IyeHkwRUFNZVhwY1kxc0FwdnUvcWg5 +azY2K2ZQK1hQK25EL256L2x6L3B3LzU4LzUwL1BQNHMrWHhoT1M0a3hJWnJHRVNYQUdRekFpQ1F4 +dzZ6VUpLUUlLTEFrU0dJNVFORmh4bWk2SGRkYmJsKzlIbXJWUEU3K20yWXFDUTE2aVZoVEJpdS8z +cXhIbmJwMUdJQ2o5T1ZFUkhhRWlwOEpndEI0bzc0WFVRUlN3cUpsZ0VLSjIrNUJkckQ2M3NYSllK +emZpRVFtN3V6Z3VUUkluMzA5OGhGZE9DTTRjSHVWVXE4OXdXb01MK1JQM211dHdrbzhwNFpCTFRQ +dnNWN2M1ei9DRVU3aHc0ck1EeWRzYkdDcEVNVDMzTDVXWnMwK1A1WU92KzJMUTY3bTY1VkF6RERq +QXBaNktweS9lZU1HakZuUHc2RGROcVVsZkhRYnkwUXJ6NlJPczVtaktteU94MVM2WEd1a09uelVv +VTFrdnIxeHcwd2pzOHVzdjNMakVmK1pPd3c0a2VUMVhMeHhpd2tjejNpREJiSjAvREt4bFVIbmt0 +QWN1ZUIwZ2NsbFBJM0RvcWVmNFU1dEx2M0d0bjc3alJmOThkOGtQSzV6dUxzL2txRzljOCtmOE9Y +L09uL1BuL0RsL3pwL3o1L3o1V2Z6NTh0ZTRMazJRUUhQOFJJNWNHeXQyUlJLMFJKcHRQZktBUnc2 +d2ZuMEFVRW1zRTBNOERmUThNVytkWm1odUx5VEtqUnhDUlVaekdFUUlvN1ZFSlFhamFwU0JGREd0 +RndNcGZVN2NHcUI0RGJldnd6UENreE5PKyswbENGaHJBSEUweW1XTjlkWlpVL01pWGd3Q1l3NXIx +ZUdPTnp5Nld3KzMrc1NHcmFJV2crQndnVU84RXBRNVdQUWxYRjlmTkI5K09XQ1JGelpZTk4rUUUy +OXFaalJjMkdzOUxoeW9oUG9jZEYreVYwMTZ4YURFUjZnMW52aHd1ZXRCNnIwYXhQREdCUjQ1eEph +bjVtSUNlM29nbU1lRldndzlVSSs5NmlCTyszMW5FcU0xMlFNZjNkbG5pTjhlRWJYbjR0aFA3UGJR +Qlkza2tMdnZhc2VQN3pCWkM3L1AzbUtwbmViRlZLZDgxc29ERTMzalFGMXlSWE9uWTNOcXc3ZWFZ +S0VIUFJUenFlZHJudGxqUDM3YlQ3WHJmNzB5Zjg2ZjgrZjhPWC9Pbi9Qbi9EbC96cCtmeVo4dlA1 +VVhyTDgyWlFLTkFrb1NBUXhrQTBXY2lnQ2NZQWpFV3MvOVpBMlE0QVZqenU5bkU0SG1hbWdOSVdl +YWRIazFJRVk2Z3UyRkNYQURVRC81SzZpeHZHM1FLQTBnQWdKQ0ZPSFh1UEFSbUh5S0Y5K2JKV0ow +eVdXUCtIQTFSZ3o2ZnorLy82KytiMmE5T2JGN3dPREVIQk81NEZHWEFhdTFMczAzSjU4OU9GSUhF +VHE4OENwK21uZE5Kb0xHSkJMNVhEVXQ3bkVrdHM4UnllRVhEMjUxRndQamVOc0dyenc5TE5UTXZQ +N2o5L0Rzci9sdnIzenF4Rld4dThPc052bnc1TFBheGFJVHZIZXRlTERvVGY0Ni9tckNvNE5DMzh6 +QlQyZUVLalpUcUVFOEhJbmhna2NjK25ESmE3MTFPRFU4SjJyaWhxWDZjdThCR3RPY0ZuQ3NCdmx3 +YVM5dDRVY3Y4RkdUcTA4YzVzUmx0U2FQL1hRSUkyMkpxUmFIckR3T0lXK2Ruc1B2K2NNQXA4OGhm +SlJjckdwT0RIY1l4SERSQVB6ejUvd1pudWZQK1hQK25EL256M3lmUCtmUCtmUGorL09sOFg0NmJi +SElzeENoTlFNeEkwM3hnakVvd0Fxd3oyZk5FUWNBQmZpSkdJa0tKQXh4UFBlZE9CUmtML0x5RS9H +Sm8rYUVDUUZpK3VtZllCRHNtVmppZXRzZ2hwKzI3VmVzZFVqeDJZRFpkNWpsRWs4Y0E5SGlJUTRl +ZDNrSlRNNktSS09RNlc0ZGZwQXZkM0VUa1NiQ2pqZE4xM3pmMVl1TGNtUU9GZ2RRdWJmSDJqVHZC +QTBiYzRycEdaeldXeWV2V081eVdLZTUrSVV2dVErUGdRKy9Pc0FVY3NPT1kzWEpyWTc4V2tENGVu +alVEM0dJekhNaW9RZWY1Y1c5TzJ3dWN6aW1DZnZVcTVZYVZFejZ3Wi9EMFJVZTc2SUg4L2p4Qmdk +ZXNSMHc4RGw0ck1HeGZqaWd4TkVyOWZxY2NaeFo1enZObXJkWGZYcFo3dUhDclI3akZGWjhNYWFl +d0dHUG11MkhBMWRxZFBuTzRFeVBSLzNBdnpzODBjTDFSQXdYcmVHQjRaalRXemMxd0t1djFaUmMx +U2hqNWpxYzdkUDhPWC9Pbi9Qbi9EbC96cC96NS93NWYzNFdmNzZRZ2x4M2dRUzB5YVNmK2pWQjg0 +QkwwMjZkUkFBREs2akN6WGx1UGRLSlFnemZZNFFyQ0lrS2FOR2FydUVSNVlGQ0RMRWhWSUhXSVZn +T1FrSjhEdzJpZUxBZHVpTVoyZUlSOGlPNDcwT2UzT0swc1pxaFRvZUx1c1Z1czFzZllTSGJZV0NQ +SVg3andHWWRFdUhHQThMbGxSTWU5Vm12ZVQxNE5DSUdPZzV3aTJ0cm1JMDRZdEFidUZhYjc5Yll4 +emllOWJuOFBzc0pQKzU4Wms1aTFqUFlhMUE5aFlWUTFVVTg3dkxEckFmeStvd1Q2L1JHUDlRdVI0 +MHBsdnJrSW5ZOThSWUtMOWFLYTcvZU1TZSs0YU1QSE1PRkV4empVTzM2MmJjNzhGc2pCa3pXd0Nj +T0hzM2hRTzRPUFZPTC9vYVg0NmU5OWwyKzdEdnU0WWRaZnZ1QzV5N1lZQkhIaFhQNThPQXp6Y0Jq +RDE1Y09IV0pLUjZOd3kwT1UxcWpIbnpUbS96UnkzR0hRN3pLZ1RNeDZNa2JOV3NjVXVxWVArZlAr +WFArbkQvbnovbHovblEzNXMvNTg2UDdNLzhLTGhFVGljUUtFMVFTcEFsaVNJSTh3ZnlVanlBLzdW +b3JNQUFhNUtkbndmMTF0RVlqUjNKeGdWZUVad0JxT01EeTJrZU00dG5IYUVnMVBOTmNKSG1qb25n +eEk2d2JTTE5IRHNRVHRBR2pkV293cndtTVFUUklJQWgxMjZmWkRnR0VNWkZteU9QeW5USGJMTVRi +UnpneDNPWHhQS0s5ejBTaE5udmtSYlE0R29sSGM4UmdUdzh2RjA0WUN4K0ViajA4eFlkdkhPS3Zo +dEUvT1FqS2V2UDRZQTcxTW81ZlJaRGZ1b3FIZUhIVlBmalFHM1hwZ3pVdzRGNGRlaXlmdGI3RERx +Kzg0di9GRDMrUU5mYW9EVDUxT0t4eEpJNi8vcWNwT29KTlBlYjF0ZnJSTDgrdGdRTmZPQTNHRytx +dFJtRG9JR3o4NjZHMXNNS2oxOFV2dGpwOS83UGYvTm5vUncrOW5aSkxYL0VqTnM3aFVJYzg0dUZW +TFhKWlp6MDlkbzlZUFF6d0FvczVOYXFuYnpWcEs1bzV2UFdVOVdyakF6bjB6OTc1Yy82TTl1ZlAr +Zk40bXovbnovbHovalJnbkQvbnorTC9pUDU4K1IrL0k2d29nWUJIZ0UyRUZZTmVVVVJIK0VUTmZN +QnFoalZBV1ErdzVHSnBCUE9WVUVMUTBJclpYcVM0QTZPNDVyUWZNVVJwSUpaaHpkZTRSTkI1UmNr +RHUxcmtNeHdVR284RUJNSnUzbm8xaVdPZk9PYVkxem9Fd1dBZmZ1QlR1MlpZTHliY05haEcyNmRH +bjhYSG15WnJpTmdhWUEvUnd1bkF3UnVqaUt2dUdsUmNjWEJBeVBiRDZwazk4b2lMWDhLUkN6N3hp +VXB2eExDSGVRaFFmUHR4NlZEQUpVUEF5Smpxc0JjMmRmZGdzMDZmUE5jM3Rha1ZEdndTUHFQN1hY +enpuc09MVTdYaTJTVzNOMVZxVlpNY2hFNVg5dURlb1NCUHRhQithK0NMQWQ1TVNHZnU3VDhUd3VV +N1UxaUxGN25rVmlPOTJOZDFEaFI1RGZYUmpGN0RKYSs2ZWxqaFdVeUhCV3h3V0VjSE9EYUhlLzdC +QVY3dGwwOGRlTEllVDlXTkh2R0N2T2FzVjNzTzErUEVCZlA4T1gvT24vUG4vRGwvenAvejUvdzVm +MXI3V2Z6NU1xRW9JQ1RXZkFuN0hCR0d3Z1JUdEVUQUtCd0k1Qk1nWUpJcXhHZDdGVnVoeVVHRVFQ +b3NIc0V6QUVDS1pVREVpZk1JOXZ2RVJvWjg1cG0vdjJ0dHdBS3pmQnFORk1OaFFhRFdXd3RENXpR +T0FUQ0l6ekFPSGxoaElRb05JMnhEZkxFMUxMeGNJOW9zYTNHZ29UNFRKcXd1c2NVa1BvMWlRUFhM +aDJzR2hVRWVvaUkyY2ZHREczZ3IrQnh3T0R4dTVQRmRESTIxMWpQY2FiSWV5S2tQOWhNQkxEalFN +NS9WSjc1TEhlS0pCUStEeW9rM3RiZFhzT0c1QjdxNGVGTlAxcnhwUWoycDkzaW1IYnhaQjQ5ODV2 +WEhFRXRmNGNHQkhOYkJEMWQvaGNWNjlUcHdjS2gvZW1Xb1Z3NXg4QU96UHRrSEx4MkloeU45OHVi +czBmYlg5RUM5dmVTUUM5ZDRnazgrdktwSmY5VUFoOTdxdlQwOTJNdTNHbkhvRFpqdnZDTW0zc1FS +RDA1NDNCMnk1UXdIOXMrZjg2ZExIZlBuL0RsL3pwL3ltWjgvNTgvNWMvNkU5NlA2OHlXQkQ1SUFy +eUhFWjNGL29qVUVCVXFETlFNSjFqQVZVU2dlWWZZaVM1TWtCZHdhaFFOcW41ZytpNGNrQlN0TVV4 +QU1nOEk4TXhRbkw2emltdmUyUVh5REFNd3JWQXp6aG1KOXI5Q3RnZG5RV0hVVGxmak1SMENhckZa +Y21MT21oaE5MUFJxS2FERnFCaHpJQzdQOWFwVFhCYmVtK2V3d2NoZExEaHdUcGd2K1lMczE5ak5M +YXhkYkRudmtKQ0RyOFdTZXNPMkREeC9tY1dXL3ZoQ1RXdkFnbDlyMEJEYVhXbUNIRFFkaVd1TXpM +RDdqbXdqMXpOc2tuK0h5dTk5aXUzS3dIRmZ5RWhrRDRJUFEvYjQrMDRoai90SFdsMkNIVlU0NnRB +ZE91WEZ2dU11cGY3U21kdXZWYXJpcmczN0VoVXN1dWZGSXN3NXFjL2pUdytySFo3ekpSd2YrTUlD +RHVmR2hYaGM5NmJXNjRiQ082ZlJXdnZiZGM1cVhIeWI3MUdoZCs0TnI5ZUJGTEZqZ2hFMzkrcUUr +Ky9FNmY4NmY4K2Y4T1gvT24rS1lmN1ExZjRvRnkvdzVmODZmSDh1ZitRRlVNRUUwVmZGTVpvT21Q +bTlwdmtZWVNBVllZZ2sxSWswOGNQN2FHOWlLREJnaWxOUWxUdzJxK0I0QzhpcFk0V0tJalNoRkVw +OEd5a05rQ0pZREpxSkFtcUZnYXdtWStHcENNV0V3NTVtNG5kTndNZUZTaTFyOStvSzZ4WU5SdmVy +ekU3dm5QWFRzczBjTTJPV1ZBOWtQcGtkMG1tRS9UbDFQN3VjTmtWcndUZFJFMGVhMU9makJwWHc0 +SjFpZjhhZXhydWJBeVNQMjc3T0hVQmhGVFdvd0w0K2FZSUFURitxQnV6M0RseHllbVNjUTY4Vm1a +dXNjQW1MRHpvQTFxSGsxOXVDd244QmpzT05QVE90Z2dWazk0cGp6REJhOXEwSHhCcWRlNEVNc05l +Tlhydkp0enBCSEwrbEdQK0QrbXgvOVNnUWZMazVYZEU0N2RFaHI3bnFsbjNpRjJYUHhleGpxZzNY +eVdrT3ZzT0JWTG5sd0M2ODUrUEV1aHJyZ0ZBY244b2pEbkhqeVBEd2ZMcnkzZC9rUDBvOW45YzJm +ODJkN05uL09uL1BuL0RsL3pwL0cvRGwvZm5SLzVoOGhLckVTK0k0SXBsQ1V6NGFneEk1WXZ4NkFE +T0RzMVNRQS9SVHV1MkdPNkNSaFVFQVZLQTdTNU5Nb0RkQm9kMDBLV1Vlb0FwSFE1dnJKWHl4RmE2 +Um1FeWlDN1JkTGdUQXEybUFlaE9mWllVU0VtdHBVdVVxdXV6ZEVzTUtIYUhuVUs0ZjQ4TG1MSXhk +QjR3d092TG5ERG84RzQ3QW1Gd2RuaUxkV2N6VWZWZzJGRVRmcWhWdCt3a2d2Ym4yL3YrZVFjY1NB +U3o3RHZCaStxMVUvNUdJMjYvVUZMdlhhWjMvTkRiZTZjR0NkUGRaYUE3OTF4R2tOUEhvSXY0TU5K +L1l3Z0QwdWE5VmlPSkM4SWJJT2gvSS9oLy8zZVlaci9mVU1SK2JsN0g2RG9IR0FLN3pDUjh5R1Bm +RGdFajZDaHdjWE1Qb1ZCUnBnRU5qZjY1RU81TllmY1drUGZ2UDRoMGxmcmNXUnl6d3Qxai8yTTVj +TEJqSDFIUlo3NVlOYnpCNDI0ZTh3cVIxZStvbEJUMi9xaHJ2eDU4LzVFMjUxelovejUvdzVmODZm +OCtmOE9YOStaSCsrSkxmQVlwZG0xS0FNbG9iZklJZzJYTEdhaFdqZkZXMjllU0Q4dEM2aGdnRkhK +Q0JpZlJQYTVUV2ZocHhoeExkR3pqYlFYa09oL2lyZm5hZzBUaDVDTXBoYmZwZjU3a01FZzNvbUxx +SCs3Ui8rYWt4bGFBUnhxRVZqL0ZXMXorTGhBUWJZUzVZRFNHN2tNNEQ0Y010clhacCs5WGl1VHZq +RXhXODR1QmpJeDQxbmVDQWkrT0I4dVA2U0pva1BTK0tmQ0h4MjJVTW9haEFmOTNpVXo0RGRzQjlI +c0xxcjNWNGlzRjV0NnNjVndSR3l1eHpxaVVIZnpJUUwreGpFR3Q5cFJaMndNU2hlWUpGRGZlSThC +K0hYeEZZL2cwWUh4eEZlVXU4WnpEUDR5Z216V1ErSGZBNUNsM3Jsc1o0VzhCM3NOK1NDMFNGTGt6 +RGdGRTV4NkVjdDhNcWIrK0dnVlcvbzhFWDNjdGh2anhwcVRMaHczRU5aYm5qeDFFTUJCdk40cGJk +aXRBOW1mNmpnVGs0eDhGa3Qwd1pNNmQveEpWYTVtai9uei9sei9wdy81ODlxYnY2Y1ArZlArZk9q +Ky9NbHNVVkFDQzRBNERZbzBIUERzeHFVb0xMbUFnQ3BNY0JZTDVsaHJnMzJXUTZnRkl3MHhjdEJn +RWgyRjh1YkJPdmxVWnlCQk9Zb0p1c0l6M05Eb1VTSFZPVElsWEdGK3RlcnJDMlpZak1jNFJDbmhz +QUl0N1hXSUZCOThGc1BuenRzWWxkY3lLeHc3Tzl6Qm9NSDN0YWtZV0lZeEpPMXg0V21FQ05PQ1N6 +Tk9ZRldUR3F6MWg2OXNFYWQ4cHFYTzI4aFl1Nno5K1ZtY3MvRUpIeTVjV0MvQy9lK0U1YitxemNH +ZlROY1JRcXY3L0xCSTBjUEg3amswbnU5VVNlTTR0bmp1WG14MWVOQWNBRFNDWTNoUG9mSzVSWGZX +MGVZYUlRNHl4ZnU3V2NlL1ZDVHZUalJLemhhdHh6K0tXano5dm91UHp6aTF3VHEwMGQ0YVpHcGZZ +Y0xGdnZWcTRiNDRlcldLeGpvRkViNWFWRU1OZVhBUHF6NGdkSFZReHZuY3VKSkRJZVNPSExEcVc3 +ejVZdzI0VmJ2L0RsL3pwL3pweHp6NS93NWY4NmZjbVhNbi9QbkIvZm5TMkRCQUVBTUFkaWt3UnFZ +Z0VlaWRXbjBYZjRENlpyWVQ5NDFoMFMraTJPZmVlQ0JSQXhSdGRBMDh2WUFDakFpaU53aFlCK1N4 +QlhMZnNUN1RuRFdJVjlSQ3RFa3NYeVh6LzRPNXZCWHllWisvTHUvK1AyZi9HLy8vYmVtTXJNYWtW +d0J5WUZvK0JCa0g5S1JqV0NOVklzNmNJQjh4R29ZM2p4bjNJcWtCdlVkUnNOZGZFYUJWMTRZTlV5 +OUJDbW1IdUJLVHZoOGxwdDUxR0EvN0lTT1d5Tm11eUcrZFdLNzRMY1hsMnJ5VEsrSXIzdUlnekFK +MzE2OWhzTSsrT1dUaDBaZ2NkRDU3STJoT3NYRmlXY3U4Mm9SVjMzZWtOQUlET3FHVVc0ODZTdSt4 +Y2UvSHVNVTl6akJEVzd0aHcwbjF1UGFzTThjTFBoVkx3TXhrM3E4bmRKYk5jaXY5M2hUQjRQU25Y +M3F0VjR0MXBwM2x4dGUrVnppcTlsejhmUUtCalY3Ymw1ZCtLUnhhK3FuYU9iMHJuZHFWRThPMll1 +bFZnYkZtLzN6NS93NWY4NmY4K2Y4T1gvT24vUG4vSW5meitMUGx3WUpxQW1LUVN3em1oU3dqU2NJ +d1lqTlgwa0xqblJFS1VCaDloQ3hPQzRGSThJK1FFUHk1WE1IVWs2NUZOV21XVnNEZXVad2NFZXdo +aUFTQmcwVlV5RWFpdEQzVjRlMTNnN1lxM24yeTJmQUt4ZWNjdFNRQklVc29vUVBhYkQ1cTJ4N1BO +TmNZdFFzZC9ONFJMejQrTEJmL2JEaVM3MUdhOFVGL25ER0RISVNySXNZa3VQV1dDOE9ub2xNUTgy +TEo2NzFhZXdkZEUrRHZ3OE85VERpKy94NGpZRGY1bm9JR25CYjQ0QXJMc0toQ2M5Z1lSZ2NpYTky +c1J5ZXpHRVB6TTFWbnNXSG4yR3MwVGMxMFpUK2VhWW1vaGFUb09HMHgzZjk4RXd1ZUdFM2J4OTlH +TmFvODhlLzh3dVpsNHMrY2VxNXRmYnFsYng2RXkzZVozOUFpYTIzNnJCZURNK3NVVS9yc3Q0Vkhx +OUd1TlRqd2szTmEwNVA1UzFQZElnN3NmUXkrVzdOZDZkQm4zRWR6cTZQZW1xdi9QUG4vR25BYmMz +OE9YL09uL01uVGMyZjgrZjhPWDlhK3hIOStjckNJOStkcWZ4MFRGenVDdlRaVDZ3RTBZQU15a2dL +cVhna05tY1AwcGhRWE1RckdtRENWakJ6QWltZS9RNEJZSkJDdUFxcEdlUld2TU5DVGptWWpMZ2Vi +TS92d292ajBnUjVPNnhOckh0bUQ4TVN4YlUwUk1LdWlmTENLU2R5NEpSUERzMDNENE05OE9CTG83 +cldYcUpBc3RocVpWanhDUUMyQjllWHhQVU1ENjFIWERGNzRIaVQ0QkpIYy9WRGJIaHdLUjdPa3U4 +YW11dE5ySWJuY2pPUFhQcEFZSExENzY0ZVF1MlFOempmK3FLWDhzS0ZWM2pnRTV2UWZZWlBmR3ZG +OUYxOHVmQ21YbmVDVml2c2phZCsvT0VKZnk1YUlIcDgyT003WEhwc2o3Vnc0NUxtUE0rNDJxMzMv +MzhFbjdXNEVzdWJNSHVDKzNxTmwrb0hCdk80eERPZUhPU3dpRy9lWWRRRFVUeDlsejhtUHg3RXRN +OXpPR2pGWGdjY3ZQRGpSVTQ1VXM5ZHVJVUZMelgxYzZnOW5JbVBLN25jNTgvNU16am56MkNaUCtm +UDFqTi96cC96NS93cDkvejVjZno1RWdnNFFCQ0hYTTBSQ1BFQUdFQllwM2wrUllIQnZEbklUN2NI +RmdCL0JVMmtocUtRb2xDQXJSV2ZtTVdTVjFGeWVZTWdyN1dhckJDZkVRNG9ZaFdyRVBHWnpIZnhE +RTBVWHg0eFhSM2lwWjRyM2o0TkpBckRnUUMvV3EwVFd3NkhpM3JhSkJpc1FXSVBzRFQ3YXRFVTN5 +dEFuelZKUFhCWW4rYS9tY3J3WEM0aWxxTUdsbGVEMU5QR3kyT2QvRGkxUnEyd0V5RU9yTmZVMUhX +NThXbWZ2TVFIdC9nYURxZmUyZStBVlY5SDMyVG9KeTd0TWQ5YTVJZXZPTXFMT0E1QkhCSXlUTzQ1 +TU83Z3NNZWNHTERuY0w0Y0VmVGg5cndtcnlIcGpIaGhVby9hMUdTTnk3eTdlWU5SeGZDSGg3N0FK +WmQ1ZmJGV1huWDVieUhnY2VEZzJiOUFCNC9lbVhkZzRoWi9hcld2TWRYaUR6THorTlFiY2V6M0hI +NjgrYTR1ZlZjbkxMU0dTOXpCYTc4MVRDeHZEZXJnY3hEQVBIL09ueDN6NS93NWY4NmY4K2Y4MlNG +ZTZway81ODhQNk0rWFFCYllxR2dnSk5WVTRrRVdFcEdPSUVYNG5XSU5rSWdBQVZNSWcycVd4b25o +TXlJVUF5QXk3SEVIcG5rTnNUVUVLUWlUWDBFQVcrdFhGQmllNElnQk5xVEJKaDlTTklqdzFORWh2 +N1hxWWdvRzdiQ1dhYTIzVjJ5a3dLZytCTHFJQTRiM2hCTnVUSFNYejNMQWh4L3IxVldCdXp6SElm +TE54Y3pIR1c3Z1VMZFl4SWhQK1FuREdvY0ZmdXp6WFR4MXRWZnF6MkRPaSs5eTROcmpiWnNhNWJC +V3YrSEFMUXhpV1c4UURaeldtSk5mN2dyT2dTR0g1d1FVSTkxem5GbXZGbUtOb0c5dDRyNGRHSHFO +WTNYU0RFRStCOHZUQndjSFRMaTFIdStldytRdG9UN2JaNTI5NGxnUGh5RVczZENtM1BRR055NzEx +MXExMlVjSHVNTWJudnovS1ZtRFU1cDFtZGRQTmVGTFRIWEJCNU41ZU9EVUU3cXdEczVvN3ZpQUh3 +YWN3T1BOVTlmVkl6aHhJSGl1WHozWWFCdm0rWFArbkQvbnovbHovcHcvNTArY3pKL3o1MmZ4NTBz +U3hnRFVUOU1LRUV3eE5aOE5ua2xzbnBEYlNBQUJCd1lBc1RST1FZcVFGS0dFZ0VUeFl0S2J0OWNh +OGNWR3ZrWXlrVHhFcjJBeGtNYTB5TFhPZWpFMGp5R3NnU0VIeTVIempDOVpDeGNNalAxWHYvK3Z2 +d2xhZmVJaFVUeENzOTg4Z3V5MUJqNVl6U08wWmlNT3ZLa0JWclhqd3FIQ1RQYllYeDdzTXdlTDUw +UWhoL3pXbU5jRC9ER0dITzR1SXJBT2o5YmlYbFBkQ2ZveDJmUC9HWVhqaXBSb05ab0k0SUVSRm5s +ZGVMYmZnRTB1b3JRMndqd2VldGdRVDRWRmROV0wzcWpKR256SWJhOTU2d2xhVGpXN3hIZHZyVGgx +K01GcHIzN3FBeTB3Ukg3OTRtcUR3eHE0eXllOGh2clZxci9pNDBrTjl1T0lKdXhwcjVsUEx2dHhZ +ODQ2L0lqdnViWFdxRTN0THBqcEZ4WjlNSWRuKy9XK1dzR0xPeHc0c2NiaFlYMFBlUnoxOExET21o +ajBhaFVMcjNpYVArZFBZLzZjUCtmUCtYUCtuRCtmTVgvT254L2JuNjhhRDRrMklTcWl1aUFFSFRF +ZlFZQXFoQkFaU0FFdWU0a0hXWXIySGNFYXEwQnhFSTAweitVd1YrSmNtcVRwK1g2QTVRVlE0OHdw +WHNFVmczZ2xQTTA1Z1NrUWJtdThFZWtRNDYvLzRKZS9DVURzUjh6UHIxR0lCMmZua1Nxbi9Bd3Bw +dlhpaW9VRGpVR3VBOGNGaDhQQlh2dUt1U2JRZEkxUkl3NTgxbUMxeW9zak1XdUFIaEE0MVI4WVlG +UWp6dUgyekdlOE1vditHVXptVFpQNDFqaE1ZVk1MZk5icnBWL3hVSSs4OWhqcTdFRWdmK3NVWHp4 +Q3M4Wm5JaVZXdFhrTEpSNHV1MTR0VDl5My85UFpxMVYvMjBkYzFMeGl1R0JVci9XKzR3WU8zL0Vt +UDMycHdSMlB6eHVpcjhHaU4vMDlkSEhrZzFNYzVxMFoxQWVEL1BMUU5FMDVyR0d3Umd6cjlOZmF2 +QTA4M3V5aFhiMFFsK2J4QWFkNTljRG5rbDhNQnlZKy9QcUU1elNDSjUvRnBtVzkrbWJ5RzlicjNm +dzVmejQrbWovbnovbHovcHcvNTgvNWMvNzhIUDU4U1N4UUV5akNZaitoSzFqaEJsQWFxaGlKaUVN +akpBaVkyOGNZaUVHbWU4V0ZUSi9iVk1ubEF4SVJEQWFjUXQwMW1sQ1FwV0JyeEhaSEltTGM3ZGNj +YXpTTEVNM0oyWUY0QnZYTW5EY0lmb28zaUlTQjFHK3ZKcm96bU5vcUJ2amMxYTU1MWpDUDVsbXJX +V0pZRDVQMVRHZDlPTHNHNEtma1c2OXhZc0p0RHQ5NkFadDZpRkVPbkJCUmpXbFBlWU5SWG10eDJ5 +R09IR0lURU14cTF5dlB6Y09FWC9IMVMxNTNPYXlGejFwNVBIUEJvRjUzV3RGREluSmcrNC9CeGRk +bisyRERBME16a1JwZzFRZmNXQ2NuOGVwMzhkaHJYL0dhMStNY0hzY0hYUGpDcS9xcVQ5anhUUmZX +NmJzOHpDMk9RMFJ1ZGVBRUIyTGpuVDcwbE1ib1F4d2FOWWRic2FMWnExaytleDEyZUhYZ3EwdFBl +QVFPc2V4MXlXMjl2UTRQbXRPcjlvRCt4Qy9YdURYa00yZWQrUFBuL0RsL3pwL3o1L3c1Zjg2Zkhm +UG4vQ25PUi9Wbi9nWVVhS0tRZ0FDUVFHd0FBeGh5YmcwQ2tRT1VoRUFodnMwVWtBZ1ZvUUFKKzd5 +Q0JzNGFGN0NQeWI1UHc4UnhBV3hPZ1dJaHVqL2grejE4elpFWFhtOGdDQUFwbnFVeGw3ZkRXZzFB +Rk9MOU04WnEwblM1MWFJdWV6Ulgwd25FR2h6SVdjTnBCT0ZvRElMN2hzTzZHcklFcThmekNzOWVw +QnU0Y0JBUkRKNWdFek1HL3Z2bm55Z21IckhnZ2ttTlJJOVB6OFgzM1p5NDV0VkU5RGl6QnU4T1U1 +aXR3NUdjOXNIbHJRNk1UR2NRc3pyZ3pmbzNrY0ZhL0hvSVEzdEkrSG9pbDU2SkhUN1Nnd2VQbnVP +Mjlab1hFNjlNampmMTB4djlHZkxxblhuRFd2RnhwU2ZpUE5yNWtubThxY2QvZ0EwcjA4T0pSOGJ3 +dkljYkxjRXJ0bWQvOHVzL0hkM0pud1AxOE5DN0d1eVh5MXIxdGcvaTRETnJyNSsrVzRORDYrblEz +WVUzRjN3T3hCclpYblhLRFlmYXFoOTNOY3lmOCtmOE9YL09uL1BuL0RsL3pwL3o1MmZ5NTB2ekZD +eXd4RFpvamdSK2tuNGEvL3lmeTJvY2t2M09PZElWb1NEZ0pOWUV4R3VNOWVJaUMvbWU5NjkzaVJz +QWdPeVRtK0Y5RndzbUFrU0FRdXhGTWxGNWF5VTJNMWJ3UkFNM3pPNXlSaHpYUERYQTYwMkd0eGcr +UCtOTDlvdnBJa3g1WVZDenhoQ1NXTVdnM3BqdWlBMHZKejUzVFZDUC9USFpEYzJDTXp3ZVpyV29s +WkRFSWpZQ0VsOHRtaWMrWFBqQUwzdzQxRHg1SEJyVzI0dWY0RDhzOHVEVlFhcHVlSzBqUXYzQkFS +SDRES084K0dBdVBYME91cDhZVkwrc0Z5UGl2TjdrZmpVeHZ6blA5RkZmR3RkenRYMHo0T0ZoZm9l +WStqM0RqN3Vhb29XTEk1K0xJT1ZrUmpGeFFwTTRreXVhdXZsZ08xN0VnVmtlMm5MWU1sOFBTM2Zh +OEZsc2ZaWlA3WFFBRzl6ZUNqRVpETGpCaVY3SmczT2FxQ2xoeGdIK3JZWEJuTjQ5UEg0TlJuMVhv +MHVmWERWbzlTVTNYc1doV3pyQXNTRSt2TmJNbi9Pbk1YL09uK25KL0RsL3pwL0pQWC9Pbi9Qbngv +WG5DNW11L2lUdnMwM3VES2ZBYnh1dVVNVDRhVnR4a2xvRHVDU0tCczRnV0d1UnFuRDNOZ3U0Q1Bn +STFEQWtJMGhlQldnbVVoQkViSnJqcDNmeEhCNEVTblExQTJKZ3MwOStwRHpqUzk1ZU1LYlltdWMv +bE8xUXJ3UEhIbmRyNUZSenhIcVlQWWZCOHpaTnpUQTFodWJBanpNQ0NPYTdhNTRhWWE2Z2lLOE5M +RmJpOFNZbkJqMHhFa0E0dWh4cWdndWZPQlBISHZsODEyUVk5Y0xlak9zbC91QVgweHFjNFZCZW4v +dG1qQ2pFTm9nY1JtczhjOEZmY1JJclhtaEJmWExxT1Y0WkZTNGN3R2hlci9XY2dYQUFFdzdsc0Vi +ZnhKUVBScWEwRHY4NDBUdHh5b1ZhMmlmcjhRZXpBWmUrT0hSbzBSeU85SWY0OGFoZThRai9iLy93 +VjZPZjZzcG4rTVN2a2VXeFh5NWNXK003ZzZuRFB2emtvTGw1YzdoWG4xcjZoMTI1eEJWcyttdTlP +WHpoVFc3ckUrTk1icDJEM2J4ci9wdy81OC81Yy82Y1ArZlArZk1aOCtmOCtiSDkrUUtFNkJTS0xJ +czFRQk85WVloQnIvR2F3UVFLME53a3ViMHgzNEZVakNZQmJSQ0hwQm9MTE5DYWhVamdLbXFmaVZa +T29Kb1hLZmJJWTYxbjFpTVVEbmxqMENPRUVDTDR5MTJSS05KQVN2OFpZakgvNG9jLytIYm9JRTB0 +QkdhUEdQYjNqWU82UFBjWmVYZ2lWb2VEdWd6NGlVZXQxdnJNb0xEWnJ4N2MySThUc1dGaERBM0Nw +emtjeFdSM3FOamZwbHNucmozd2ltVmZUSEx6OW5vRzF6TytwRjg5VE15NTRORWZRaTJQT0JVclBO +cDVkWnJETCs3aFVRUCsxZUhPTE1sOWRlTEEvMmNXL2hoRGZmaVQxNXFZNS9yampsczFNZ2tNNHRH +RG1ub0lFYVIxMXF2WndRb0gwVll6OE9JYUgySWt4OXZCSkw0REdBN3pQYmdZemw0NTdLV3ptUEJp +eWhuZHZoMmE1cm9QVnJuaHM5Wm4ybEU3M05iSm8xWmNtTmRmbitsVGIrRGxFeGVPN09HQi9BRncz +TW9KcnozdTlDNEduUG93Zjg2ZjgrZjhPWC9Pbi9Qbi9EbC96cDl3bUsvUFBySS84LzhES2hDUlM0 +eGd4U0JHQXpVRXlacUlBQWs4OTkxbFA3RHVtaTZ3WVoyZjhwSG9Ea0FOYXAvblJPUnpUSGpDQUZC +ZTVDRlRYT1pWQ0NPSjRhMkJ6Ky9mRUJHbk5TNjEyTittV2V0MzVKa1RKdnM3SjVjbVdPK3VQb1RD +eUNTSVp4REV4eVJYWXh1dU1ZajB0c0F6Ukt2SklkZTlhdkRNSFM3TmQzQ0lKYTc2WWRZSXZGbHJp +TXVnTG11N1gwenJQUE5aWHVJUml3RHNrNXRKNWRKTGRjTm0zbUZGT1BJUWk3OHl0Ny9tVmpmTzIy +Zng1YUVIdlJHanZZSk5QL3dIMnVJUWVjV21MN2lGUmN3YWpnN2tVN2U5bm9zTkV6N2xWUi9OV2U5 +WENhb2JQUGNnMUNPOWtJdHVjeWpkNVEwUlUrdEJ1ZlhaTS96WnF5N1ljTVpzK3Q1ZlVZQlBYdHlw +SFhmcS9HYXlNeWxzK0lKTFBKb1hUeDd6YW9aVFQ2UEp3NGhUY1J4bW50TzBZVTZPMWdLRE9vMGVK +dWJtei9uVG1EL256L2x6L3B3LzU4LzVjLzdFN1VmMzU4dFB5SklRclNBV2E0cmdDQU1HeVloRXJr +SVpWRENBQkNaZ2Q2YXh4a0NReGhHSkhQWWpHcUh5dElrK0s4cCtGMkFWVnNGYXkyaUVSV1NFSU9Z +aitPOURUdmRYa0IxcThOWUFNZWE4emNpNGVwRk1HRzJZdWpTRGNCeEs2dk5NZmVyV1hGalVHRFBk +R25zMVdoMXc1OUM0Mk9MaVVBNTN6MnNpNjVBdkJvSEE1SmszSzRGMkRTUUVjZnZjdlFZVm00RGdn +MWR0MW9yZDRibjQxc3ZoTThFN1hQUk9yd2lhSVBGbnFOdG4vUFV3Z2g5MjNQck9vSENvbHpiRUZJ +dTQ4U3V2ZFVRcG5oN0NCWWQxNmpCUEgvb3VIMHh3NEZxdDVuMkhHMmZpbEEvN2NPNnUvL0JrWEMv +dzROZG5ZRmFER0hMUXQ3ZUVQUlQxa2piVlFCOXd5VU43TG5IaHQwNWUySmtPRHpIZzhaNCtIRWZ3 +d3FCR2EyQ0VYMHl4OEI2RG5vYm9rSGI2QjRCNW1LMXh5V3VkL3NNUDcvdzVmODZmOCtmOE9YOWFw +dzd6OCtmOGFjeWY4K2RIOXVlcmpmUVE0WnJQVEM2TEpQVFR0eVNJUWVKMzl4TTlZb0czSmlLOEJp +TGFkNE1vcmJQSDI0T0tVOFBFRkU4dWNUU2dRblpwb2xod0lSNnB2aVBVbXcxRmlWMkRpcG05Ynhn +SUJXWkRreW9lQlh0RHBGWkRUczk2d0ZSZ0RDWXZqQm9vdmpsTmdjbGRzOVJEN0s3RU9BeDRNelRQ +WnpIc1VYOEZiazd6TlZjZCtMRXU1cjZoZVRqQ3IwTUJOclcreDloNDRzQm9iZXFDL1FZTzRWSS92 +T0lRcW56aUVMOWV3ZkQwK1BzY25qN3J5L3VEdW5YaEN4ZnE4WmtnWWZlV2lDN2d3V2R5MzNvSGNn +ODcrZlZCWHYyVG8zMzNuSEFkdXZhcVJXNjkxSHVjMFluTFBwZzloMHVPWjN3SkZ6LyszVi9Ndkpw +eGhoT205WmF3SEh0dUhuNmNPQ0I3K0tiMnUzQVhiYndkdkRCRnd4ZFBicnpXZ0o3RHFIN204eGwy +Y2N6SjQ1ay9YT1RCczlIK2wyY3gwOGZUVmY0d3ZHdituRC9uei9renRjMmY4K2Y4T1gvT24vUG5K +L0huU3pLazJ3U2c1bm1tQ01Fc05pbzRvQ3BxZ0NLY0N5NlJOdzdtRGNBMVF5d05BWUx3RklBbzRL +eEZCSExOVzB0OFJPaXl4b0RMVDl4eUtseWhtaW12di9JWDEwRUFMK0lSK2hqMDY3ZjhGU014ZUc2 +b2xiaHpPTnhuQklrSm8rSEFJano4dUlzcmg3dDhtb1Y4enpSQ0xXSjU3ck0xZUxBZWY3NkxiVDN5 +UGNPZnY3cFdOMTRZekg2TklrajQ4ZFMrcU1OK2ZCVzNtbjNPWU5DNzlFTzljdmdzUGpHcUh4NzE5 +SkFnUW9QWUdSVDNxZWxxVThjM1ExNk5kQkpSL3AvL1IvcnZUWXUzTDNDWjEzUFk3SVZmVFBYMG9C +UlhibmpVMk43U2xMM0ZneXQ5azE4Y3ZiY2Vkcmh4SVUvMWFZak5qUGl5NXRIVzE1alRyeUhnUW54 +OGlRMlgzTjV5MlNPSEhzQWl0blZ5aWtVRDZsVy9PcG1QRnRzYjgycHU3OFN3ejlydWswZS82Y3Jn +cCtqNDl1T1VKaGpaZ0JWZjZwdy81MDlqL3B3LzU4LzVjLzZjUCtmUCtmUFIxc2YyNThzRVVmcHAz +Y1kyNHIxQkdVQVFBRHdYeUZyemlGUUlvVXNtc09HNVorN1d1MnMrOElxWE00M1U5R3NpWXVUUVNN +MFRVL3krSWRJOGhDUEdXeDdDZ0FFK0RZVkxEbkhWMUtFaGNCQVFIRDZMYWRpRFpDS3hoekFSL3N4 +L1RWeDRpTkUrZUNvUXVZem0xQ0R6bXNFZ3VJSmJmcy9GcVluVVljNTN0VE1RZk5scjNINjVjU00y +UHZDS0kvbnhaTCs3T1hpK0dkVEJkUHM5aDQwWk5Ob2FuREdwV25EaEdUNjdWNThKcVQzQUM2em04 +L2tFQlZlMTROS0xQLyt0bnd0djF1RlRIZmdnTm9MRnB6MTZxQjYxV3Urd29qc21ZWmhpVmpmZThJ +SjdtUFJhYmp6QXJ2NFk4SzJYK29VYnRZcGZjK0ZYN0w1WjFBdDlsdE04VHNTREYzN3h4VlVyZlhp +T1F6amdjbGk0NDQzZXF3VVl6YWxKSEw5U0EwdTU4eHozNnBlM2I4ZVlGbGI1eEJTSHBuMVcvL3c1 +ZjNidi9EbC96cC96NS93NWYzYk1uL1BuUi9abmZnQ05LRzhFNEUwSVdDSVVpR1JCYkNZU2MzNmFC +dHdlWkFrR0ZKS0pCQkdLRnhzeDl0bXZZTTFTbUNab3N1ZHlJb1E0WXFhYlJ6amlFUUJMY1NHWE1N +UTBFS1B3RmduWGV4TTJKc0pjQ0REVURyYzgxdG5uSi91TUV6bGM1bm9ZVmFRaGxEaHV1SGROTU9U +NWwzeTNIbmJQeE5kazlmaHVMZ2ZVMWE5V3dtNU1nOGl0THkvcWdoZS8rQTZXVzk4K0VjMjNjYlhi +WTE0UFhEQ3IxVnM4L0RXbXVwcVhRWm5LSE01Z2dGR1AxWU52YStUMzJlV3Y5NzJWZ1VOZjVZQ05t +ZlFhaHk2OWhsTSt3c1lMWFRtMGNhNSt0VmxuUDJ4NmpHODVpZHBGVCswWlBnaS9RNCs5UFlSZnZY +REs0WjhBZDFudmdsa00rUHdIM0RIZzlTRmFQUnhpNDhSbk9ldUpIa3JpT3VUVUFUTWUxS0Zmak9j +N250V2xMN3lpSi8wRHpGcTR6VGs0ckZPbnZIZ3poeWQ0NEowLzU4OXNtei9uei9rejM4M05uL1Bu +L0RsL3pwOGYxNTh2NGtZSzRXaUNJazI0SzhaelRRTklRWjdWb0o0aFFnTTFsUGdWUTl5UytZbFlM +SFBBaXdNY0FoSFU1NHF5WGx4NTdVT083NFptSWtKQjlpckk3MllUaEFHWE5lYmhRVVFOS2g2eXhM +ZlBYQTJLQkhNd1orNkUyNzNXSUVvTzhTdElPYXhuUnFPNTRVS3l6MnJFRHo2SnZPYTFWMXdOZGpH +bStEQlhKQm5ISDE1NldTc252T29RaDRCODkxd2NBbnJHOHlzYjRscWpIb0lVMytXTlJOL1VNSXg0 +clVVZjRHWktjZUhXYjdVUnF0cmNXeXNjek85MzVHdXVpTzN5RWhvZTlKeEJxdy94MUl0TCtjelJr +YXVITmszS0lUWU0rTUN6bURTaGp0UjNHTnBMd3g4RWZ0MGpoOTdWUmtjTTZ2Zm04ZUFQR3pXSUs3 +N2N1S0VCSEJkakR6dzh5YSsySEtiWGl4ckxRYUJXUEhrdWxyWFZBeTNSdUI3cERSejFTN1ZwSDd6 +V3lhOFgrRzMvMURsL3pwL3o1L3dKeS93NWY4NmY4K2Y4T1g5K0ZuKytrQ21oVFlMNzYxc2tXVkRC +SUpyUUJXUTREZFhJbXNuYUdzRmVZQlNGQ09zMVJRN3hrQzhuTWxMY1BkZEU2M3h2ek9ZeHV0OUJv +SWwrcWtZc0RJcUJXNEh3YVNBaU9oamRteXNOOHNhQm1EcmtnMWw4VFNGZTMzR2htUVNra2VJakda +Nkk5M0lRbUFFRFVqVUxOcC90ZFUvTmR4R1dSakdyb1E3UDdkRVVqWUFQRDdnenhGV1B0WEJxck56 +dzlMRG9kekhVejV6RWpCUFBQVk9YdzZ3SG9yYzUxdHRyanBqVWI2aFhqK3hUWXc4VzYrRlZBMXpX +NTdBNmZIcmxkOUNaZ3c1eTZOd2U5Y2VjeHdVOGNrZjR0d2MyZWMwVHZKN2JwMDY1OFUxSFJBNURO +SE5tTkVkWDF0Y3doenJZRGRyVnczSWlKcnowZ3VOdythWVBkL3ZsYUg0Y002VWNhclhIZmxxRW80 +ZGc5bDIvMUNDUDV6REQyY1BGSG5GY2NETXdiY250TTQxNS9uZC85Ry95REdkeTRvc0dtbi8rbkQv +bnova3pXcDAvczJmK25EL256MmZNbi9QblIvYm5xdzNRV0dBc1ppQUV1QVJ6QVErUWpacEliRzEr +Qkg0RkV0eFR4TmVzWnd5a2UrYXlUMVBjU3pUaU5VbE9SWW5STndrVmpzYUxoeGlOOXRmTmhFRTBt +czhBaWlXYWJ3YTlldUFRajNqays5cy8vTlc4TGVnZ0FnMFIzejUvWGUyN3BoTXJYR3JHaStiaUNs +NXIyeXdOTVFlcnZScHBQMHhxY21rZUhESDE0WUpiRTlXSlQ3V1pKK29JN3BvbmJ2T3FHVmR5V3Fz +V3o2MlJGMi9xL3padXY3Z3VlNzNOWUZCNy9WUEphb0pMejFxWDRXMkwzdEJEK3lZK3JOYTVxNHN1 +N1BkTVhJZWVnOUIzM05TZzhPbTNpOWpFY3RqTEg5R2VHTTNCWVk4WU1IcW1KNHdsSnh5SmZaajAw +anA1NFBnSGI0anV1VnJkOVZZT1hQdVB5UE5yRkxjZlY4eUZGeGg5RmhzR2ZJblBTT0xyQ1Q3c3Mx +Yk5OQUdmWHN0dm5mMTZYRjlZTDE0L3c2RnVmMEE0RUt3ejhNRS82Y2ZwQ3U3NkRYNEh6dnc1Zjg2 +Zjg2ZkxQclc1ejUvejUvdzVmNG8zZjg2Zkg5V2YrUnRRQ3oyVXhDS2tFSUdOQUJFZDRwQm1vNktC +c3RaYklQczFRTE1pd2hPSTljQUFLbzcxbXN0UWpGWWl6QkdsZlVTSUZBYVRKd2ZIbTlrOTAzQjNi +NGZrVnBSNVF2WlpMVEFpMzNQTll3Sm10djczL3UwL2k3bGhNT1F3RDdlODVueG1JbnY5cEE0cjNP +SlcwRENuenNPTnMrYlhiRTB4N05PSVhNZVgycDdHZkVrTWdoUVBCazNHbjhZM0wwRkVGQmZYT3Zr +MW1TaDg5bHdPV1BDazlwajdoaGcxbWJxOE5jR0JocXVSU09YMHpKcUhMK001V016THdSamkwd1Bz +bnNFT2N6a1E4NjkrNzVkeVY3dTFudE1UZkJWMDhZU1BlMjY5K21pRjF1eWpKVHd4cmhqRUs2ZnZM +Z2F5ajNaZ3BydjNCclhYSEc3dGcxT3Z4YUZGbVBCSEMvRDQ3bGNFckJjWERqbjBRcDI0MDF1MTRw +NFAxR0tmZU5iSjRjSmJUSHE5Rmx2UDFFdTc5bm5ldDBFOTNPVjFDUEdCM00wSHMrLzJ6NS96NS93 +NWYxby9mODZmOCtmOE9YL09uL2JCK2RIOStWS0laaUhBSlBDSzBLUVk0QWlVd0RPZ3JSVVl1ZDRR +dGNuMkVhaDFCSTQ4SkFJdmppSXJkbUpTWEV4R1dKZkRUOUZJUWxBT2c4dWhhWVk4aWhDVGdlU0JU +eXdDZ0Z2UjV1V0Q1eGxmc3g1RyszLzA3Ly9GTjNKZ2xBdEc4ZUZ4K1o2bTMzeEVkc1JxQ2l3SVZJ +YzhPTEhPUEdGNlJnamg3RWIzYVpwYTVMQUhKaGcwVVQ3UDdWV1BmUGJJTGFZREtRYTlOWGpDcmNh +cFVVejlLRzl3Mlc5VXpOYkJqR2ZDd0l0WStKVWYzZ3JoR1Y4alJOOWhaQ1p4WWRNN1dIQXRyOC91 +K1BJckNub2luKzgxYUE2eE54N2hFVWV0NnFJajNNaVhudDEzZGFoTFg5V3FkK3FUVXd6N3JXa05Z +dFdnUk8wTmpKck1NNGk3V09wWGs3ZCtua2RmcDN0eHZWR0N6VHF4bVR6ODNyeDExc2hyci83UnZX +ZnFkUmpnQ1JkMFdCT0x3Y0Jxc1ZmdmNPVXdxSTROTlhxTDZnQ3RyM0tJSDJldGRmNmNQOS96Tlgv +T24vUG4vRGwvenAvejUvejUwZjM1MGlBYkZHaURBSWhONExmRXhFYWdubGt2SVRBQytLdGFSUk1B +UWhUbVRVT0ZJN2FpN0pmYzViUGlGQ0dXM05Zanhwekw4elQ0ekF0WHpIeDRDQXNCY29tbFFVaXhG +bG1LbExmRHVyNVIrazkvOGo5Ky8yZS8rYk1oUUZ6WXhITW5MRVNwejV3YTFDNG4wMVNvN3ZCcEtO +SEFGc0VlWGx4WW56ZEx4eG1PbUFVbWNZbGNYSFB5cVFOV3piWlhuaDRPWXNhZ0Z4ZS9ZbmhtTDh6 +Mis4ekloQ1J2Y0FmNzh4KzJXeCt1cjNhSGdSejI2VkZFZEhubEY4L0lJWDExK2E0Zjl2dE0zSjdY +TEoxelo2TCtXb0NZT0lKSDdmcXE5L2lRc3lhMVZ6M3A3L0VCay8xNlJRUDZpVmR4V3plRDZCT2R3 +ZTJ6TlErbjN5Y1BjenFRNEpDTDZLM3ozR2MxcE83VG5Wd09DQWFSQTE3WVhONGFxaUh4VDJQNHg0 +M3ZZdURXR3R6aVB6VTZyQStMeitab1NVL2tzWTQydloyRHhYZDkwbHQrODJZTUJuR3R3eGU5OE1q +OE9YK0taOHlmOCtmOE9YL2FxNTc1Yy82MGJ2NmNQeitxUC9QZmdDSlBZTWxzMEhqUFdyeEFGbXVV +UXRPb0ErcTdCcHRIakwwK0d4cExFQWoxRE1rSUlUcXh4Yk1ISUVZUUV6bUlOUStzQWpYWmM0MlRq +OUI4ZGxrakp2STBXdzdQZjJMUUwvbU1GRCtkK3l0aWR3TU9SQ0ZIVENiKzRmLzAzK1RPSURIS2tZ +Z0xseHJnY3BlWFlHRHpYZVBrMTFSeDdZVTluNDlzYTh3Ukhrelcya09ZOE1JQUo4T2J0MGN0K0hm +dm9jZ2NlQmNiWDJLb3dYZVludUgvaFBqNXA3M3R3YWZjZXVOTkdkNzFSczNOVzRPcWx3NDh3N1hj +OWhPMTNMREMxSHp5aStQTmpQWE5weTZDVjRlYWZMYWUxbHFMNy9RbXAvN0M2SzRldkludFFMWGVQ +blhEUTM4T2hmYWlHbVVrT0dnS2ZoaGdFWStoMVkxLys5UWpMbHptcklPRkVjV0h3enhNK3V6dThK +U1AzdkNGVHpWWVR4L3cwVEwrZXVDS1FmL3F3Um45TWE4NjlNZ3pCNFErNEJhSDRlVG01RWlkOCtm +OE9YL09uNGRwL3B3L1c0dnY4K2Y4NmZQOE9YOStWSCsrSkNrUUlBU1EwRS9XaWpaODloTzhZcTBO +cUp1elYrSEU0czRJeEdjb0VqaGdrUUlVUXRvc1B6bGJveWdHODB5elhVaFduTHlhTEdmSWVEc1FO +RXhCY3NITjRJZ1JDOW5XR0l3Q3R6Y05DR1crUC8yTm43bVpMekdodFg2L0d3N2tJMG9kNHBtM2pn +QnlZRjFPR0dCU0MxekVKYWRuQkNXZVdnemthd3dUbHljaU4rREdIMHppcWhsSHhDeTNxNGRDUlNs +VysrTHlXUnpZOFNMT001Ny9Oa0NEMjNUN3ZSWHgxc0lkdDNnaERxSzByZ05lejlSQ2RPMExiY2ly +QmoxVXI3NHpQUTRaZ2dFOEZ3OFBPSEpaVHp2NklyK1k0ZlV1TmNQcWd0UGUvbUhoallybk9NYUR6 +K3FGM1RxNThHQ0k1VC9HeGpOdHdhVUhPUEtaWWF6SHFXZnREODJxQ3liYVZ5c2NjdElkTEhxVG5s +elBmTWVQZU9IM2JTMWUxSUlEK2NTQ2c1WnBWQXovU0FDZTVDMW1mUENJMnNSOUR2SHZzdzQydU9i +UCtiTmovcHcvN1pzLzU4LzVjLzZjUCtmUGorelBGK0pjeEtnSk52aHV3ZlBUNmlPMmt1STVVSWdC +akFrVmlIemtLTDQvNFNyV1BrQVJvMUVhSjc0WVlsWHNCR2EvZVBZUWx2aUc0dXdSazdnMFNiNm5P +Vy8vb2ZTYm1PU3lQK05NWmgzQzVQcnVmdUwycjRRUnNHRXRFVlMwM2h4WUo1NGFYRENvaVdEeEFM +ZGE1RXZUanVRMjNocDd4Y2VkaGhZWFRKcHJyakVaUmswOVFNVERoWFc0d3F2WXVKQ2JJVnllNGN0 +OGV5WC8rNEVqc2MzN1RGRCtGVFE1Q1FHZWl1d3g5OVY3Zk9sUERVcElOYWg2MHZlcmw0blY0Q0pF +Yjkzc2dVMHNPV2xKUGNSSnZERGczNkVBbDc2ck13ZkM5Y0YrRjFQZ1RXOXJIdnM5Z3hrVzYybENy +bXEwQjU3YytvaFRXT0gzSFQ5aXltMHY3TXpRUTBCTXRlUVBtOHRqdmIzaXc0QWIrKzJoVTl6QnF5 +K2VxVmR2YTNxeDVGRVBUdkhwalpkZXFrZmQ2bGNQZlhwT2c0K21UNXNYMng4bzZuSE5uL1BuL0Rs +L3pwL3pKMXp6NS93NWY4NmZIOTJmTDgzVkpNSXc2YWRWQUNWUm9PRzVuM1JMUGxEbUFFQjRTWkRN +bkdLczFWQUcwUmdFYXBhZm91VTAveE5EUC8vQnF1Y1ZqcjN2RHdqeml0Um9oU0JBa3dsTHNSVVRv +cG5jY3pYMU8yeHFJTklPbUpIakxtYmZUQkdXdHo4R28yZ21BdVV6cjRuSVJUejhha205eUQ5RDFx +RG1jSWNyKzhTeVR6MmE1Sy9VaWNPYktYekxLYmNZakk1ZlFzR0ZXRFZBK1dpT0N2QTVWSjVSZzhL +a0xrTHNQeWN0bnB6TUtoNXhHZmp5MlY2YWdGc3VuNG5RV24xVWR6SGdqeGx3STY2TFJ2UlpQZTdx +d2FGZTZMbTkxb3VsWHZuVTRPNVNsd3RHTmR1dngvWVNydmpWUWdWdHZSclRpK09OdU9HbktjYXd6 +M2Z4N0lNSHgzaUhXWTIwYjAzTmhWT1hPVGdabGM1Z2hZMnV4SUZCclB3QmRQWHloQnh3TXFnaGhq +ZFlPWUF1aDNwYytnT3I5ZUg4clI2OVU4LzhPWC9Pbi9Qbi9EbC96cC96NS96NUREek5ueC9mbnkr +SkpMWllrUUpvb3AvNmlja0FJcVk2MERZaTF4elIrV2QyUFpQQVQraVA0YjZtZVJvUjB4MnhDdEF3 +ZDJ1dFkxNnhEYURGVnp5aU5CT1p6QzVXeFBtV245bUl5M09ESVNwZytCMElIZDFEcElqL3cvL3dM +N1Bla0Z1REVNUGNzTXBMcUkvWXY4Ukk4aEFFTWwwK3E4T0JJNzQxbXF5ZW1Qc3c0elRtdkV0Kysr +d2hiblAyOUkyR0h2UU5rYnlFcTU2WThVMGdGYlc5UHNQdTdqcyszUTIxcGZITWNqbHg1Y0J6R1B6 +VjcvL3JjSXRqK1FoTS9oajBNTHNUbWIxcUZCK24xdENHWjdEMTBNS1p0MnRpNDRnZThBRXpibXA0 +ZFhrbWpqMUVMdmFqcjYvSkJ4T2VYUC9Zb0hpekZyOXFrb1BoZkJmTGtGOXQ3YmM1M0t1ZC91UjJ5 +TGpyTWZ5TTc0Q0J5WDVyN2NlTk91VTI1MUlML21IeEhLKzBacjFZN1MwOHZHTmUvNnhSdng3eWls +N2hKSDlZM1hwMTZ3TjlKdDd0TWVEMGg4bjhPWC9Pbi9Nbi9QUG4vR25QL0RsL2RuVFAvRGwvZmtS +L3ZwZ1JlUnFySU1SWlJMaEk4ZHc4MFd0QWl3V1lvTDBkMERpZ3pTbk1xT2l0YmNFR1ljb2hwdjF5 +RXBTNzlYTExveEVsQnVGSTFVaWZ4VU9DR0Jyc3AvS0sycHIzQm9XamU1SHdsNy85ODI4enh0YzBX +bVBsMDFRTjA3ak9hNnJHMkcrTmVBNFRJa1d3NTJxSEJjSHFnMGZEMWFrNUdtTHZJNll2K2V5WnZQ +Z2hjaHlLWjlnckp5NThaajUzUXZBTUx3VHNicDk0RHhkdjR3N0NQRDlzQktBdnhPNTN0T1hDajFy +eHJBZHEwV2RZOWNKZVBPTlREVERDWXkwK2lMRzg1TEE4ZzdxYnQ0OW8xWWVIQ2hkV2VPQ25LeHlH +NThOcWprajFBaVo1WGJRbHQxaldGeTk4aEY3c2hyNVlMNFlMRHJuVjNuckZnZzFYYWxWRGM5Q0lT +MDAwUUkvV3l1dnFIMkR3d29GSE9XSHhYTHg2aURaeDByanF0ZGFibytqbnRHcTlDMDRZY2FQUDR0 +RWRUZmtEWmY2Y1ArZlArWFArbkQvaG1UL256L2x6L29Uak0vZ3p2NElyQVFDSTBoQWlVNVFDQkFH +YW1BU1RVR0pBclFGR1lJbk5JZFlnRE92RVVvd1lSZ28rZ01Rc3Q4OXlWM3hpSVIvSkNGSTA4QldV +QXN3Qmo0eU12Lyt2d1FxN09ma1VhUkF2d2gwNFl2ejRkMzRoTlJuaXE4dWJBZnNxWkxFeUxpNThj +cmE1N3A0eEd3RjRaaDUyZkdpRzNIZ3dyellpVXBlN053TzRNaWN2bm9oVHplWU1BbElMVTdyMEJY +ODRzbGRPR0YzbUsrcjN3ek14OGF3dWRYcERJWmNMcDNBN29CbEE3cnkxT0ZNNXlPeUpIZzZMdFdx +R1ZVNDk5VjFPL09KTVBqVmFnOHR3ZkJvUjAvcGk3MkdpSGdiVlg1OXhaNTg0K3VxQ0Y4YUtXWHdh +dzdYY3ZvZXo2eE10NFZPOTZpTnd0ZENKQXgxUG5xWFBWM01QUEc4YTdkVlg2MkJSZXc4bTJIMnVE +OVNFVjJ2eEk1NTY5Qk1PZDF6VG1ucmQ3Y1doWEhDTHFXNTZzS2I5RjBkT2g3alBjTXlmOCtmOE9Y +K210aHZ6NS93cHBucm16L2x6L3B3L1A3SS9YNElWQU5DS0J4eFJtb0VBaFhrRDBPS3M5MXdqbVFI +cDFtaU93SkxiejlBTWFZODFuaXZBZ1dBdGdnbWRpT1VHMGpOM3VSNndYeE5Mb1hMQUp4NVNyWDNH +MjcvbWRhS3gxN3doSHd5YTV2ZVQvZFR1citrOU41RGo5NURiSk9iMVV6ekJkb2dwUC96V2k0Y1hS +c1NCdklobUdrTDFXVDJ3eWVNTkY3N2c5cVlFdHpXYkp1T0JPR0hXQS9YaXhMelkrQlJMWS9HUkEr +cEVTc0RpNEExWDRoT1AvZkpydXY1WVM2UXV0Y25sd0lKSkxUREFqTU1jU0plemg3Q2NGUkFlck5j +N0FsT0wrSGlqQVJqTldZK2IxSDRYa3pDaVdIb2pOOHhxRkJOV2QySlVBeDRqNHR2ajhQY2NoNFo1 +Zk1IaHMvckZOM0JRVThEcnJqLy82VS8vNXhpWG1aaWsrcUFsV25Sd1IyOVhzN2RGNHVJSS96NnJO +VG83YnZzSEdXNnNFVU5PK09HMVJreS84ODR2OXNrdGhoenF3YnNZTGpycUlVbWplSWwvcmw0eDFU +Ri96cC96NS93NWY4NmZlak4venAvbURmbGdtRC9uejQvcXo1ZUpGSHFFRXlLZ0ZoS2t3TWpXaEJy +VVhSREZBd0NvNVBiNHJDaERYR1FUa24wcDhnU0FLQVpRRUtJUjRibDhKZDA5QXIyOW1xd0lPVm93 +UXlIYjg0dzM4b2dMZVpyNGpPZi9MOGd6UW1SUWYwMy9DUG41NTZLUklCNUNOQWpoVDlPZk4weHcy +Rjl6NHNnemZGbW5UazJCQlRZbVU1czlPREtJRVY1NTFlVENGMkZyTW16RXJta0d3L1RBVWJOWW1p +bU9mWEw2cnRuNGdzbnptTXovWGw1R3dCbmNla1lFRGlPL2R5MFhET2IwVnF6bmNIait4VFY5a3hj +MnVKbEVUWjdKNlpLandtWitQWVlGM3pYT1k2em52ek93Rm45cXlkejFWVjYxNmgwdTJ1TVk5UGJr +UDY2KytMRGh4andzY0xub0JGNDVQSWNEZGxxQ1ExeTlaVUk2eHFuNGVQWFpNMmFTQ3hmMElhNCtx +Z0ZlYzlHWncrczRnd01uZENLSE8yNWNQWUQ5L3lHSklaZmV3S2pIL2lONWVhM2hBZm54VVgvb1lR +OEJsM3Jtei9sei9wdy9yWjAvNTArMXpKL3o1elBtei9uelkvdnpKWm5FSmlRRFZCRGdCTkFJYXhS +dERoR0FTYW9JelVlS3owRFVOSjZMb1dERUFrV2d6TzFTT0lJMTI1RERaODBKaVcrWUZDSU9JY252 +RUVDcWRZbHBFUDVoaHFHaTY1QmZYWW9sVU9ROXdubU13OHppSWNyYk11TDF2Q09DaFBudTFvaWxS +dlc3dkJXQ25aQnlNRndqeGJjSGY3NnJBMzhHY1ZURVBleGdrOXRhUTgxcU15YzJmZ25BYzV6aFUy +d3hYTzBoc1R0WTlBd3VXQW1rL2ZPTVlQRWpoZ3RmdUN4dUl2RmN6dGJ0c3pYdThPdWQ5WVNsSDNn +eER4ODgrcFZETGdmaDgzOUk2MUNCUVQvMHlscThNSi9QRGhEejZwRERNd2NtcnA4RDgva1BsK1dE +aTBiRWJNMjRkOEhFYVBnamNyejZUcmNPQlBYbTRLU1p5NE1Qc2RTR0g1akVzUlpQdEFBL1BHTEto +MC9jOXJERWh6cndaeitkT1JUZ3hLWGVXZTlYTHNSb3pYRGdnM1pvQWpiOE9ieGdWMnQ3SzY0WTFz +eWY4NmVjcmR2bitYUCs5SG4rbkQvbnovblRtRC9uejMvcS9ueDVreUFBRUFRaktiS0JFTUJDenhW +SUhIN3lWWmdrZ3BvVEZFaml0c2J3NWtXamtPcHVyY1QySWwwakZCQkFiNEsyQmxuV3krKzdQSUFq +M2pQeC9MVXo4dXcxeElVWmRnVWpyVzlMZklmZEd3Z0YrMnRpTVExWVlIYXdtTmNZRFkxZzN3WmNZ +b2dOaHhqd01RamNjQ0JmSEJqeDVUbHNoQmlCSHllTVlyUitPZUFrRFBnSTVUSG84LzlYWlY3OWN1 +SEVZYUJQUkt6cHZtdTJOWGlSeno3RE9nSXBqM0xVb09wVEowTVFFNHg2MFY3RDI1cmg3bDM5WXNE +Vnc0Q3c2QUV2NWhrSkR1dHhIQzZ1Ti9EQW9rNHh6TU9QQzF6aHN2eUpEWmRZT05GdlpqSEVocis0 +Y045ZTRpU0g1Y1hIYTJMOGwvOHI1dFJiYjV0d3BRODBTNi95MnVNN251aEE3ZldFUHNudEdYN3cz +ZWQ2THA3TE03WHFIVjdFckYvZzBSZDhlME1Vbmc2WDlmS0wyUU1UdG1qNjdiRDBiUDZjUCtmUCtk +UGErWFArRk1QOC9EbC9HcjdQbi9QblIvWG5Td0hJVUJDU2JBQklZSUVFMTRRVWRVQ3NFUmdRSU13 +QnFkRkFFd09nRElwY3hTRGVHc085RFRJbkwwQ2FoWFROOXZ5OUFZbkdQdUpBbE5oeXdmcU1MMm1z +L1czY0kvWXp4T0dTSDM1NDFDdUdJWDdOaFVCTjhyWkdyQTd4Y0FGWFJhWkd2SWdqcGpkTWZ2K2M4 +Qkh2cXBEZDhZVnNwTHZVVXB3YUJvUDgrRFlpM3R2cjBndHI1TU0vN21BaE1MWEJvckhtdkNreHpC +R3llc1JWdjRNRlJ2aGhsaHMyYjBEa2U4YVhiK0tYd3hYRDNnV0Q1N1NpSHZ2a0pmNGVmdmhVcC9w +cVRtdmhnUVdtMWl5T0dBeG1IMnk0OTFsZlBXY1VlTVRBTmJ4TUs0ZDErdkVZNC9tUDhWc3pUWm5I +Q1dPcUdTWWFjN2NPQnIzdzFrWnVNZkhFa0hLSW9WN2NxTDg5aFVzdXVQUUdWcm1zVVpPYXhhZFJN +Unl1Nm9TQlRzVEVSOTZXM2g2MWllV3d3NmVlUGpWOXpkNzVjLzZjUCtkUGF3eDNlT2JQK1hQK25E +L256L256SS92elpVTHh3R3NtVVVpbzhSYWFBNTZJZmJaZUVaSkw2THRpZlFmY1hrTlNRQlJndjJJ +TWQ4U0swU0lKdzNkckVlZ1pFUUVMcUVMRTk5eTZDazdlRHA4N3orRElOOFJVdVBXdytoMXhkUmpp +TWhYenVqdHMrbmFnQXg2aXdvZjY4dmx3NDhzaHdGeGlpcDI4MTZDS1RtTjZ5TUdRZzRqeGJpKzgz +bVRnU083bnJkWVo3QnBudmYyd2g2UERhUjFjNWNnOGZqUmNiTG5TMk50dkxWdzRWTGZZT1BNR3hB +R2xGdnZVSXpiaFBlTkwxaEtHdk8ycnRlTEw3V0JXSDR5NHdKbjhCR2tQRGNscm5YcGhrY004NFZy +Yk9QRFhlTERSR0g3c1VVY04xNXpXTTVUYTFPOEF5YUYyZWZvSEFVM0NCVE0rbU05NjMrbWpmM0Nv +UzF4dkRMMXhoQXNHTlR0d3JMSFB1dWpzOE1pdkIzcHRUdjcyUUU5Z0VWTS94YUo3dGVFSzE5NGM0 +ZFlhYTlzenVHa1FMK0xsSUQ4ZG1aZHIvcHcvbnpGL3FubituRC9oblQvbnovbHovcHcvUDY0L1g4 +emlKMXVrK0FrZEdBMThqSE8xMzBJa1JVd0h6dWNHOTVNMHNBZ1JCeUFGR2U2RUtDRndtbWY0aVZz +eENOQTRud0dXdjhhdmVUVkwvaEtnRU5oZzhSTjNUSkx4L0xQUTFpZ1MyVFdvdlJydFFwQ0dwS2sz +WUJNSDJXcFhEN05GSUc4RFJqWGlRKzFxUWJJWVJLTnVJaEJmWTRoRlBYQVFrWDNJVnc4eHFsOE5l +SVpUZlBPNEUwL0QxR3hlUFhLSjBlWlo3N3M1OGQzeDRVMURCLzRxTG5qRlYyZHFQSU9xQjI3UDhL +YVhjQnRFSjY1NW9wZFByK0JTTXd6MFlyMSsrRDEyZGZ0c25acGdwcFVjR0dkNjJQemVPQVBDbzI5 +aXdGazk5QzJUbkdyVlAyWlNHM01ZOXVtZmRmWllXeXc0MWQ4Y2xvZEZIbjFRb3pqcWdVUDk5c21y +N3U3QnR6NVdXeml1d2VDcHhseTRGVk1jZmJXV3p1Q2duZWprcmhwZDMvV0F2cUtKNDFSKzJIRXFw +OTdnNC9uRHdadSs1Lyt3R2U3NWMvNmNQK2RQWS82Y1ArZlArWFArbkQ4L2d6OWZ5QVVjV0kzekdj +RXgxQ1ZFc3FaNVZqRWdUd0svaDYwSVRVRytPMU1aaWdLNnpWYVlvUUE1ckVla3owQzYyK3NDVXZF +MXV4aUVZWTlZaWxXOEFtdEU4L0pwbExjV2ZpbzNZRmE4T3BsSUUycFFlY3lKcWVudytDdHRzVHJV +b1NHYWdIUTF0c0h5NDgxKzJPUmdMclZZNitCU2cyYmFEeFBSNEF4V3ZPTEZXeFZyVzR1NDVVTXox +V0NkV1ByUUp1dVJDNmJIRE5leXk0OWptSWxPWEh5b0V3NjV2TDN3SEVZY3lPVndzRmNOY3FnSlJo +aHltQjUrUTkydzRMQzh3UUtiZXNRMEx4WnpHbkxvRnp6dGF3M2lFcjg2Y3NtTEF6aGg5cGxKOGVB +L3NwY1RSdm5nSm1hRzlGeWREZzM5Z0ZIZUhFclhXM0hFeTcvV2RYWDV6cHplT3RFTi9idUw0NjRX +dFQzYS9mSWNrcmNQZDk0a1dtOE5YR3JnbDd3TnVtZE0zcHBoOU4waEpTNHV4YlFXTjU3QkpYYTA1 +MEM2eTl6OE9YL0tOWC9PbjhiOE9YL09uL1BuL0RsL2ZnWi92aFJXMGhWRlFENGo2UkhNbHpRQ0dH +UWkwR1kvOVJLQnE2SmxYdUl3TkFGd1JkbnZib2hCWUFnbUhEOFZ5d0VNNEM1NFlBa3hWelRnaWtC +Kzh6QXBZV21PTmRZaUV5SElxRUh0YmVPSUFHbHF6TnlSNFEyQmVlWlZJK0ovOHVicEVSZFJWeERX +YWdCRGFEQmM2b0JMay9BQ2wxeGlxWVdKY0VDMGNxc1JOK0w1REM4QlBXSjdNTVBtd3B1MTNhTytI +bGp5dWZUd01ZUTNDODhiR1RIeElXL0ZCS2M3NFJNeHpLMURUMkdGMjNPWTNYdkFxcC9nNWRWamU4 +VFNDOWlDNlhpakcvTTlNQXlmOFcrZFdIb2xsL1YwQXIvNisxa01QREdXZnJVZm52dERvUnlvRVdm +aWkwdUg5T2hYRHZRVFJyWDd0UVU1OWNZNk1YQVBneGo2cTE1M0J4VHQ5ZENJOW00T3YyS29EVmIx +MENDTzljZzl4cnJCd015SWIzeXBSV3oxeU8rWld2RnB6bWU1eEpDUG5sMXFtei9uei9sei9wdy81 +MC9yeE5JcnVlYlArWFArbkQ4L3NqOWZCRWJNTmdHaWNJbVJrSEZKUFFmS013VVFnVDNFNnJ2bXVT +Um5RTUVSZ0RCRkVaWTdBU0ZGa2NBZ0VUaERmbzNJZFNERmZScnpKWmpFaDBIYy9nUmVZU0hQZnV0 +aTBDTkRuc1M5V0hBUUpkTHRxMEVScEJsd2VITmtUazNQb2ZHTU5PU0VpSFQxbWNlSHZBaEVyR1lR +aitkNEVaZFEvSDh5d2FOR1lsZUxtdFFBcjd2bjF1cEQ2am51eElBVlp5NDE0QXVQbnRzbnJyM2wr +ZHRoZFlOZzRjS1pPNkhnQkFjTWhWdWloVGxHZXVOUWZubmd0ZGRhT2NYM0RBNTRDQjJIY09zRlRH +b1AxcXN2M0wrSlZYd0hQYVBRRmJ6V08vaGkwb3VKVjcxbFRqbng3N20zUWQ2YzlIQ1h3OXNjNXJC +Ty91UTZ6cnBmTEw4S29KY3c2SzlEMlBmcXpqcUhoUnJNbHl2UDlSbFdjVno2Qks4NllMTEhPcmpr +MTRjWTlPSlZqL1JDVXpnMkI2T0R4alBmcTNVODBvcTllSUhoMGZ6MThlS1ltei9uejNJNGY4NmZz +TTZmOCtmOE9YOG03c1dhUCtmUGorclBseVloMEVhQkt3SU5LYm1FQUxqaU5NdEdZTk9nQTB0Yzlp +aTA0cGFRRUNTWHcvTVk2ZlpWQUg1U0Y5UHduREFWa3dQaHNEeXh2Z1liREJyc2VZV0hFSHVzZ1p0 +QmllUzlDY1cxdHVZbTBtZlA4LzlIcEI1Tjhod2VSQ0s5UTFPdElZaWF5Ynk5RENvdndYaUdDM2xk +VE9HdnZ6MkQxenA3SEdCd3FMdU4wVGcxMXl5TWpPTUk2dGE4UDN6RXN0WjNNWEd2dHRhRUV4d3lE +cnp5cTgvQm9qNGNWb1RXNllrTGgzRHJEZDd0VC84T3A5b1pDaDU5SmlLMU16Mmh3cVBIc0xsZ2Uy +SStoeUJzZU5VWDhjM2JMNTljOHBnWFI2L1ViSTNmaDljWE1RMzFPMGdaa0hrOGg4bFFreHJGc3NZ +ZFpyWERxVzgxbTMxMGdpdnphc1ZURHp6N1lmSGN1aDZlc09OTWJ1dk0xU3MrODRwMXNLdlZHeUU1 +R1JFMmwrL3F4cXUxNXNRU1UwN3o5WjI1K1hQK25EL256L2x6L3B3LzU4LzVjLzc4VFA1OEFlRURv +Z2hGc3kwT3liZklZQW9nS3k2RkE2V2c5eGRTbU1sQXN0anVpckFISUNTTDdUa1JBeW9QSXdHdW9Z +QXlCYkI1cTNKenZvdVBhRGlaSXFKOEV6VFM1VVlVOFQxN0g0UEJacDg2dkdHQXhWQ1BlZ21RbVZ5 +YTJiMEdmSnJmaHNGUmc4SXJObkhKYjA0dDRtcXlKdG5mbXZGc245cmxOQWV6L2I3WG9Iankxc09j +WGhDL1BRenBya1k1N01ldEdscVQvSERCakMrNTdjTTFZYnRiNzdPMUxtWmhLUG5VNmE0VzYzckF0 +WC95bVRmd3FSZHFzODQ5UGRkVHhuK0w3VHVqMFZCRmJoN1AzbXpwSlR6V2lka2Mzcnc1Vkh6SFMr +TjQ0NE52dGNucmNJSFZuQVBJZitlZ1RnY0pYaGxEbitBVFgyejcxTlFEUmc1bXhiZWF5b1U4NXRS +dWorZjJ4QWVuclJwTFgvUlhUbk0wQ0tjKzZRY3R3R0dkUHJ2MzhJSU56dmM2VVJQKzVzLzVjLzZj +UDl2Zll2QjgvcHcvNTgvNUU3YjVjLzZFNzZQNTh5VTVvSUFvVWdHQ2E2cEZoS0xaUkFLZ242U1pH +Ym1BMkdOT2NKK1JhRlFZeE9RdWFZR1o4eHdvQldpV2ZmbDhvS3d4SncveldXc1BISElxbk9FaVpJ +WGMvdmZpMWZBS0ZtRkVRMlJpYUJ6UkdNUkJ4SnFxWnNTci84bjdETFhaaXllMXl3K3IrSWozbkRD +c0kyb0Q4VENVRDdYSVUvTVZrejE0RVZPVGd1djQxalF4aUZrOWhLMitpT0UrNjVVNSs4WEJpMXFl +OFNVNWFoNjUxVlhCcU5WNmYvMHZUM0llTG5maUVGT3NDdE5uT2NRa1NIbGgxaWM5cU9FcmN2dndw +eWYyV0p0RDdMRGdYeXphd1IreGkyOU9IUHpCMnNNSFJqekNwYy91RGo1WCt2RVd5L0RaUVNlRzMv +K0h5VUdBTDRkdWVuSDZ3QU96NktYNjlRVlhlTUtMM0E0RjM2dGQvVmNIekhCNTg4UE0xYVg2ZUVN +OC9KZ1hBdzZZNVhVQXFjMW51ckhPWFF5WTFZOURlSEZiSGFoei9wdy81OC81Yy82Y1A4V2FQK2ZQ +K1hQKy9BeitmQ2tRb1FBQnJYQUZQSVUvemJZUkFZS2FGeFM1a3RuanMyY0s4ZG13eDdPYXBrbk5h +MklMSTBTakJtVmlwTUJBRUliOVJNMEk0aEtaUW1DejNpVXVvU01FRHJrTXNSQXZIaUw4dFRkaURL +UjZya0d0UzhPZUd2d0xWOC8vUjVOY0x1SnRQWVRsNnNGQlpQSTNib1dMTjl3U2MrYXVqc1M4T3V5 +RnFRSW5LQU0rTWR4eGt2dzN6NkE0RXhNT2VEd1Q0K0h4TUovWmVzamh3VDRpZHpFb3NjSlRIaGpK +eGFCNmpqL0NnRTA5ZW9BYjg3RGs0THNjOWhLcFhxZ056M0RZcjhhSC8rZS9wVkNmL09KVlIrTHFI +KzB4VFE2MDY0T1l6S0JtLzQxQlRXT0l5NFM0eG9PNWFnU0d2L3E5WDByL0dFU044dmpPTE5iVFhj +ME5senJrazFkUDFPS09JN3lxcGYxVFAxM1lpenU1emF0SlhEamdwQWt4R1JJT2RlZ0Z6SExqVmMx +aXRmN3FRSy93bUVQenRDZlcvRGwvenAvejUvdzVmODZmOCtmOE9YOStKbisrTEJSWXN5VVZFS0FV +Y01NaUNRZ1pFSUExM2hyQjNCVnZEYk9XVElVZ1ZBSG1OTFlpQU1neis0aVNRSkJwdlhVeDZKRWt0 +emtZbVVMUmlQT1RON015cG5ucjRJWE5Yamh5SU54UWwrZEkrKzQvLytqN0gvL3VMMzR6S0lJMVJR +M21pUndIei95WHhOWXdlRXNlRG1DWFR5MzRRcTU5eVh0N3pHbUdaOWJpSW9mR0RYWGloSERVSXE5 +YWlFVERESFdwRnpkeVcxY00vZTZ6MnNSV1g4eDlnak5nVUp1WTFqSUlQZ21Hd1BFdTU1UHZTL3Fp +OW9yRjRXQWYwY01QcjdWNDFwL3dmdnhhSTdkYytvNEw5Yms2Y0NGM2EreGF6K0gwbVlFWXROcURC +VWEvYW9CVDRwZFRmdjFpYUhtdHpXRndnejcvK2c5K09mMWhJTmpGdGs0dmZKY2IzL2FwRlFhWThL +TzNQU2oxVTIzVnFQb1pCaS9tNlZBTTJxQk5lOFZ5cFIrSEdkZit0VEo3NVZXaitMaHhBTm1EUzNN +d201TUwvNDkydjZidStYUCtuRC9uei9sei9wdy81MDk3NTgvNTg3UDQ4eVdoRFFRak1CREVWSkVD +RUhFY2VUV29QUko3cmhpZ2ZMWXZCajJobUFkQVRFSkNMdUlKejM1WHlMODFHZ1c0T2ZrSlgxd1k3 +RU1Fc0lwR25EY2RDdllXUXVQRUlGQ2sxYUJFWi9RN0FSQ0t2L2FPbUc4RTM5WGp1YVlUb1hWeWRw +UkE5Y2l0eHVDNXZUQ3J6WFAxdXlOWFBUQ3FJU1orcTlld0J4NTVjV3MvMGVBL2IxWHVrcCtBNE1T +dHVQWlZFT3JVVUhIZ2N1RXFqWDNqUkY1cllMQ2VRZm92Yk1GSzZEalY0eHhHdDVmQTBvTVRQYnoy +RTZuUGVtYzliTmJTak5nRTNyVzRvUk43Q0F3T2ZWQ2JmekhMV3pOMXdLWTJtSEhPUVBhcUZWWkRU +WC96bzEvSmQrdDdLS2haNzkydHIwR3QrL0h2L0VLdytQOUI4dXNBZW9jNzJHaEhiWXpzczU3cUUx +TjRlOU0rT2xTWkdSNWNxOFZhV0h1QXdTc2ZiZm51Z2xFdDZqZnZ3cmQ2MWRwREF6NXg3SFdYUnl4 +ejh1dHhhajFzOGxzL2Y4NmY4K2Y4T1gvT24rcUFiZjZjUDQzNWMvNzh5UDU4TVFUeWlRVGhEUGFU +NWozL1I3V0NtVk13QVBZZ0I0a2FoQ3pCelVrcWdlK0tkL21zc1pwcVRZMEJWSXgwb2tKQ3pIbVg5 +V0lxRERaejhCQ1p2TVR2c3p5R0ppbk1IbGpoa3N0QXZEeWFDcXYvRUZ0K2d5RFZwVjRHdGM5Zmk4 +dmZvVUZJcklBMEd5ZkJlbmx4b3Frd1B2dStCak14aW0yZHVJOW9INnc0YUtQTTR4NEdRMDNxN3NH +Z2ViaXlqOERVR2RIZXZzUzVDeTc4OUZCaU9QblVxM2J4dlJuQ216eDRJdFNLS20rVzdvSkgvK1dY +TTNrdmo3eld5VUdNaGpWcVlIaTE2ak51M1BIempDK3BSMC9rZ3h2ZWloWlA4aEV2WE9KWm93NmNF +VGplOVVrLyt3ZkpkLy81UjZuTEhJNE0zUC9Kci85ME1PaXhOZktvM1FFc3YvZytXOWM2OUE0M3Zv +c05oN1ZpNHhEZjFSKys0S2VWNnN4ZExlcHcyU01QamgwRXZ0TmJEcFhqRTVmVmozM05xUjU1eEdn +ZmM1amR1dmx6L3B3LzU4LzVjLzZFZC82Y1ArZlArZk16K0RQL0RhZ0htaWtZSVNqQ2M4TzlCZ1ZP +WXFBVjRMUGdLZXIySVBkNXMvUTFqYkxlWGpIYUVDQ1pwa0lEWHZNUjVCa3NHaUkyc0pyc3MyZCtr +aGNUb2VMWGhITEFicDBpZmZjVE50SEpCYnVmMkpHQWtMeEp1V0cvdWdsTVE4WDFMMVBaMzFFRDFt +Uml3YWttWE1rSFZ3NnFxODNRQUUyMlQwTTBUQnkxMkdlL0hPYlVyWGxpd0J0c2R6ZEhiQlVoODlq +bnVUbzFHQjcxUmZpWG93ZVdBdzgybUJoSTNlcDNxRmxyRDBIakZQZlpkM3NJUWc0WDdQcGFuTmJL +K2RUeC9PdHJjSGdiRXpIZm52THZNRlpyZW5CRExQbHhxRy9pNkxONW54bVVtSE1BSG1iN3JlMGV0 +VmdQSjl4d3VhdkR2S0YrYjVUMDF5R0xPMXEwbGtuMWhnYkYvSVAvOWIvTGVoalZwdmZpaUtsZTli +ajBTcjNGVUU3eHFHODVnTzhaRGF2RlpWNGRlSFpRaUtlZVlIanJtM25ZN2JQZTJ5d2E5SnptK2NT +QURZYjVjLzZjUCtmUCtYUCtuRC9uVDkvbnovbnpNL2p6SllGa0NpTUNud0UyU1N6RWhud0pBVlZv +aS9ETVdnSkFoQmlTSWhNWTZ6Uk1EckVSeEdBeDd4RmtyMmNNTFovUG1vQlkrOXlaU0Y1emlwQkRR +ZkxMWThEc1VxQzRoQXM3OGNtSEhBYUFVeE9lZmMrYkhIdGdKeUp2VUdDcUNBMkVmVHRVSEFMM0dT +Znl5VVVJM2hqWWI5NHdMNDVuWW5udXJvRTFnUDM0RVZjakdVbXROU2hzNnBaRFUxMXF3YVZuK1BM +ZEdseW9vd2VQZ1R2WTRNQzNRd0JPYSsycmdPWEVCOFBCQlR0YzRyN0hqMHR6VEd6NGpndS9sKzdl +dXV6VmErSnZmK1IwK0tsWlh1dWV2bi9KdmIyUjB6MzZ1WmgrRjEyZHNPSE9JV0dObXVnTE5yVVk4 +anBjNVdKNDJLMW5BSit0RTBPdi9jZmNUNDhmM2ZsZGZHdkVWQ3RkVzZmMzVhTUhsTnd1Zk52RHhP +SGtlbWFVYStaMU4yY043VHEwY1VDRGVEQW5sei9jOEtObXovb0hUNzFqei93NWY4SlZQWXI3SHYv +OE9YLzZQSC9PbjlIUHhady81OC81Yy83OHArelAvQUFxcUFEQWVLaEl6Wk9VR0FRMUw2aUFBZ0hu +S2xoMzM1R0xkRW5GQlVZT3hwVVlXTEVSeGhBS1owcDVFVWhrR2lndmtjS0VUSGMvbVRPb1hPTGJa +eURTR21RakUxNXh4SWJOV3dGdklEUkxiUlVPa1drSXc0dnJnbGV1RHJYQUtxWUd1dnpFTHhlY0dn +RUxicXdoT3FZckgrcUF5VVVRN3ViaHdLTzYvY2U4c05XZ2NMdkVFOE5kUHR3VHY4L3VjTUZidzVZ +UHczNHhYV3IwTytvNGdGZGVCMEpNOHZmL05ieTNUanpLaHpkcmVraFVyT1ZHUC9IbDF3akVoS1Bj +NjdkYWk4ZCtIS3ViQVdHM243N2toWTBoY0VGbkRGbWo0VmdjRnd3MFJlam16Y1ZvM29pZHJzb2pZ +emlVeGE3cDljMGFkNzlMTDEvNmZ6RWNYUHJuRWo4OXZSd3cybFB0Nm51MGQzUDI2WTFhOGtic3JS +YnI4Q0NHV3N5N2NHV1BYc0JRL05iQklpNStIQUk5YVBWZy9wdy81OC81Yy82Y1ArZlArYk1hbVQv +blQ3RS91ai96anhDWmtMVE5BVklpdzd4QWdyZzBRZ0tmSmJYUGV1YlJlRUFWaENCQXJSVVhHUzZB +RkdLUEFoQnZQVEROcVJHTkJiQUxMajlGaXltWCtDMkU0YTN4MHpXOC8xOTc5Ny9qMjNZY0JYemVt +bjlBZ21BaU9Vb0NTWVJFQkJoQnNJamovRktjbU53YlFXSVF5QS9nQitBeDRwbEVwaisxcDQ2SFBB +RnpWRXZhZDMvM1dyMjZxNnU3MXRIczh6MXoyYlpCRk4xQm9BbVFJbTRGV2dIRFFXUnk4Y3hYQjNM +TmxTTkZWQkNjYUZaRjBzZ2FVR3dDMWFENHNVWlVPT1BEZ0kxZDV3akxtdzNQY0JHTG1MaHk3MEVu +dHhZWkZoandhczNGYi9rd2NLSlo0Y0NsMzZ6bU1nZVRwdFhZejNoTkxOeUd5MnRLR01VVFF3M1ZS +Rjd1UnJBZEg1b2JKakZ3dzE1OTQvdmR2OXphRCtMQzd1b0I3Qi9PcTRGYXRUL1UyTmNmWUdHalZ1 +WWRJbmpGZ2JqVzVlM2lwM21xaHhoaU9xQmhrd2NzN0t6ckQvdTlvWUpQYkQxU3ZIS1ZqOWcrNHdh +Zjlxb2JPejdnMEt2cytQUE1GNjdsYUs4NjZuTjQ1SUJmOWRjNytwcGZzZmpsUjAzWXVLYlA2WFA2 +bkQ2bnorbHorcHcrcDgvcDgydlI1MHZmQURCUVpEL0JBbGtqRHRrSWlMUTYvdG45UkN2cFhnaTJw +c0VBVVRERUtvaTl5RUt3bjk1VHpBT3ZBY1JBcnJoc0ZGVXpJdFBRRkRBQUQ2UGtrOVNSYXE4QnE4 +UVVHSGIyQnI4d3dZMEVhMng2RUZodlVUU3ROeWcrSTduRDUrWnNEUzdOMlFNQk54b0dmblB5aDVs +ZmU4eGJMemZtelhsV0tKejdpZ0xiQ2hSL21nRmZtZ3RtWE1Kb0wvN1NDUGU1T2JzY1NoMGFReXhj +cVpVWWJWek53cGNoSHM3eHg2L2N4QzV1ZDQzWUE4UGJtKzVqVDZEeWhzTkJvVDU0cVVEbDFJYm5T +MXo1YVd6OHMzVkE2UXZyOHJDdWI3eDlramNmYXV5dXR2b3RCOTN4OXpFV0g3NGY3ODBTWEhxR2tI +M1ZBUWFmN1hOZzZ3bDFzdCticFI0Z2NwYUgrRGhSWDNVUUcrZlc5QzAvNGVWNHRvK2ZpUFZ5c0Vl +djZuRnpPTU05R3o3d1pxOTFXTVJXRTNQcHZSTTdibkE2ZlU2Zk9KOCtwOC9wYy9xY1BxZFA5c2Iw +T1gxK2RuM210K0JxRHNrcHFLUTlTNWlSSkwwSklBZ05iTDJFYTFZSnVUU0NoRFNKaE5pd3RZOWZw +Q3NxY2lWdnY4OWlORTZhN2dxc2VlMHhrSWdVdmhXWFQzaVEvZ2owTGNXejN6NllKR1lneDk0S0ZM +RVIvaEh3akxkZ1lHT1B0MGh3aVZueDQwZE02K0lqSCtGKzZ0Yzg1c1UyRDc4Y3pmTmJQdHNZaGpu +ODRrY3hZUmJYTTg0SlFkSEZFRnVqMlZzQk5hWm4rOW5BL3dqMHdXeW9nUWJvV3kzTjd0SUltYnMx +QS9mNElFcSs1TjdEZ0UrNThXK2RRRjNaZDNteWNZRGdWSTQ0Z2VzNUlKL2ZFS1kyOHEwQVhBN2l4 +RDBiZmpVcFRLMmZQWEI2UXlSM2ZQQWxKMDJPUTNid1JhQU9wdU5PVGhVN24rTGlTTjU2Vng2ZXJY +dUdHYzk2UXcvTDNXSEdMbUo1NS9aalRlQlAvS3VGL2ViVk9Ea2ZqL0swM2o4UXpPSHM0NEhCcHox +RWJONmhpY2Z5TFZkKzVRakQ5RGw5VHAvVDUvUTVmVTZmMCtmME9YMStMZnJNYjhIVlhBd2pta3VT +ZzVKSFdNQmJCeDVvaWR2czhtWklBMmgrWkhFc1VUWWF5VHAvZkhrekl4WnhBV1NQUWdHa3NIeTdL +eUN3RmEva0pRYUhnaEc0QXZaTlR3dW5zUlNBYlE2WWF5S3hrV1VQbnc2UzdoUFhIQnNGNFJOWkdp +MUZ2OEVubkREZ2htKzVpQWxybXVxS3hYOUVlSEdSckVId3liNU5iNDFkNWk1WEJmZVpiUVhNci93 +MXFaeGdpaGp2NElJRHI5bC9kZU9URHcyQjYrUjFNUXg3Y1M4L3ZuSHV6VWtPenNOY2dXYThOMUVQ +UjgwRHA3cW0xcGVmT0cwY28wMm55ZURDb1Y1aEZ4R2ZUNDBOai8zODhnOXo4N0d1Umc1eHZNTXNE +L3dUcklOTHJuenladzkrMk1EdmdHRExoNkczaUk4dlB0VU05LzVSTm50MTBSOTQ0QnVIWXY3a1I3 +K1RXbG4zSmkySDU4VVNGM2Exa0xkWTZvWWZGM3Q0OEF1bm9iLzBLUXpxUWtmcXFjWjhpbWt2SERo +bXoxWjk3T05ISFYxcU4zMU9uOVBuOURsOVRwLzhUNS9UWjdpYVBxZlByMENmK1NWRVBtZ0VTU2hL +QkhxRUVBZXlPYktPRUFrSjZET0hGYWk5ZkZsekIwSXd4Wk93eEJRTEVRaGlqMUJ4L1BXdlpCR0FD +SVFrdWJPM2p6OGtpSWswUG4zT09CdjJrcTlnK0VxajNxWEI0SlFEbndxSXBPNkZ3N3htVXpSdkdE +UmFpNjRBOHNOUkM2ZXg0T2lCZ0J2NVJvUTMySGdycGJqbVlDOXZiT0d6aGgvckRpdVl4TVNGaXkw +L2NQZmdnbFVjamNlZmRaL3J5NzdrZlVNVGl5VzJ2WEx6Vi9HNDBQeFA4OXc0L3UyUnN6azFnVThQ +T0lEYlROYngvUEZRZEdEbm9MNzRhcUsycVpkYUcrZlhIbXZ5VUF1MWhObWwxZzQ3Ni9qQ01hejI4 +S3VwNWNHR0xjNzFwQndjRE9Mb3o5VHFQUWM1eTlYVkE0UG83T0ViendSQnRLMWx2MXZ2bVYvMVZt +dSs4UUZQLzdEQ1IzSEt4eHdmOGpMQzcySEFqYnp3cTViK2tGQnJ2dVJrTDA3TXE0Zjg5VFN1NVNO +UCsvbWRQcWRQT1UrZjArZjBPWDFPbjlPbmVkZjBPWDErWm4yKzJNU0FRMkEwT0FmZUJuQU1aRzFj +aW1kZWtwNFZKY0srQzFoQWtjQ0hTMURGUnJBR1Vrd042U2Q0ZS9vbVNneStBVVFTSEpwQkl5QkZv +Mm1na29na0dDV0NCQTBxbnNJanowQ2N2ZUs3TkxXOWZHYjlNTWtiV2U0YTRwc2ZmRGRGN2JBSFpq +Wjh3Vmk4c01KcHIvank3bUZUUWNPaTRIekNBNS80OHNjZnYrSnFObnNWc0x6ekdZRWVwM0Q0YkM1 +dktpNlcrT3pnYXMwTUJXWW5Qdi91RGladlFqUytROVc5d3o3NFV2L3paYS9hYUJENTRWd2MyRnhx +SmhmMXNONTltaE5XZldEZzF6NDlJcVlhMUo4WUVkWEZ4YTlMczhLcjd1cmtyVWw2NHV4eHhwODFQ +ZVhOaXIydHBlR1owUHFWaElqbWVrTy91ZFJDZklld3cwb04xWkl0Z2FpTm5PVWxQdXh5RTFkTitK +ZURYUGhpcDI0d3FJbjZ5WXMySEJENFVBdCsxRTQ4bjhXUmsxemx6SzQ2c2laSE5lRy8ycHMrcDgv +cGMvcWNQcWZQNlhQNk5LYlA2Zk96NnpOZndRVmFFd0VqZ0diZ21LRkNJRndBamdSUkpBWFdoSnBU +TW40cWxvUzlRRWdnaFQ2L0NMWEdsMFEwVEFFaFVJSUt4TC85RXRSODRpQlFITDdnNDFlc0ptOGdF +RWJZWE1SczJLdHg3WmU0WFBtR3c3Q2ZTRFNYZVZpOUxXRGJnUU00UDhhdmdNUXFaOGlHbHowc2ZN +Z1ROdnhhazZjMXVKQXZIbDQxajN3MVBveWFFZzl3NFVtamlZZHpzWElJWG1Qd2ExNk9jRlNnQmc3 +VUJuYlllZ0RoeldlTlNDQytpZ0dqdXRqREord1ZxRWFDelRyZTJCcWFVZDNoazRQNnNZZExma1li +VzB3NThzbE9ISmpFVHkvZG5EcG9WbXQ4ZXlZa0hJaVBkL0g1ZzFHOXlzdVhQRzVkanZJVER5LzZp +MTl2QU5ucVJZY3dnZUpaZkljWEczbTdzeEUvc1M0K214NEc4QkZ6aFp6RDhuQ0k3YzdldXZqVzRX +S2paOVdPVDNIWjh1V2dWanM5aWlmMWw0OVk1cWJQNlhQNm5ENnQ4VFY5VHAvaVRKL1Q1L1E1Zlg1 +MmZiN1l3QUd5TlFCd2dtaTBOcmc1VGdWd0I1YU50UmJKVDgzc05SWXkzYTBESUxEbWthakMrT2sr +Ynd2T2gySVVEQko5VmdETlRVQjhLaER5Sk9JejhqUjNHMUpUNUxya1hHenRsWnM5N01XMVYrenVj +K2RIdkY3ZU1NZ3o0NGdpcU9MUlNPTHdTNXp5NUI4bjF1UmlUVlBMRFo4YVJHTW9JTjZzNDFaVHk5 +a0ZFMzlzOGFaNE1PQUlQbnZzWjROVCtjbFRESFBpNFRpUUhWcUhHMlkxZ1pzUFRTT081bFZQTll1 +OU4yenZEU0VmTWNWdUw4QnVYby9Bd2M2UUEvOGFGUVl4MnJBUjZQbkVCNUUydmw2QnRaamxhOEJP +alB6SnkzNE43YzBOL09id0lxWWVVZ1A0NE1JVkVUempOZm5oVnE0NGtKODVvcFNYUHZBR1NaMkQ0 +WERDNW1zTWNpTld2TnBEUU9LWWQ4a04vL3FKZjJ0cXlrNTk0TUdCZk9CL0JQcjgyblhDN3lFS1Az +K3dPR2pVMHg0SG1EVjU0Rm90M0tmUDZSUHY4cGsrcDArNVRaL1Q1L1E1ZlU2ZjA2ZTV6NnJQRjQ0 +NVlDQVkwRzF3Wk5pRVlBUnBKdXZtQmVmQVBrRVVYREVRWnA0UGlTTERzNkFLWmg4eWtDVlo4ZHV3 +ZnNXd1pNWHFIdjRrcGtqdTV0a2lYencyZkpnbkZIYnlnWjlmZG9na1RtdXdQdnV1Tm1kRFdMQm94 +RHlmclRnZENJTkpudFkrQ3BRL0pGdHpJWjl2dWZlQTBteHNGVVF1TUpyekJrRVJ4WGVJMlNzK2Z1 +RVJreDk1c1hkUThPT1pjT0RJb1hQMkdzUGMwNmhQcy9LaGtlUVAyMy83NDMrWjVpTUM5U1FzSXdL +OVBXTEFwajdxcHRiMjRWSXNOWGZoMnZCWkR2Z1VYNS9BcG9mNGtvOTZ1L0RHVHQxYVg3amo2MFJx +THc3OHRYNjUwdERteWpQdStGSlRlK1dPSC9pK2pCTWI0YmdjQnZia0VENy9mL29mL2ttNDRJdnZm +bDNEMEx0NlQxeHZtUGdsME1ZUlY3NzJxaGQvZmVQVm1yTEJnMzE0aUlEUFZrMnRxelZlOVk3ODVh +VVdzS2dWdlBneEx3ODg4ajk5VHAvVDUvUTVmVTZmMCtmMHlXNzZuRDYvRm4yK2NQcEZCRmNJbTEw +Y2FUWk9GUjlRZC9iV0pNVUJwd3F1c0VCcUpBQWxLVEF4Umt4WERPdm0vRVN1VUFocTRZRmxhMDVq +MjI5ZUFXQkRGcEg1ckhCczBseVhqQWFEVlZMSWdJRndGUWR1UGpXYi9PU2d5SWI5aEtsSkVPbVpE +VDl0ZEhGaFJqWS8xalZnR3ZjS0psZmtlNU1CQTk5STlpd21XNTl4NWlJYSt6U3M0ckNSRDI3RXgw +dUU5MzZnNEJVK1BpdE9senp4cWxacWtzUHUvZUF4MlBJckJtNDF2enYreEZhemNIZStlN0RrZ0w2 +NE9IR0hRdzAwcm56VEV4SG9hK2JWMGNVbmZ0VE1Yalp5NGRObmEzekp3UjJITU1mbTF0UEF4NHMz +UW5MR29VWjNlY1poQkgzKzhNUUhUdFdsQjJ2cWRibllRenh5RlE5MnRqLysvbmZDaVZqZURoR29l +UnlJVGFCeU5JY0gzT0ZBemNQUDRjUzNlckFYQnc1N1hMRDFZQ2wybUEzNXdpUG44SHo5Q1lkRHNJ +ZUpQdURQSHZINGRJaE9uOVBuOURsOXltLzZuRDdkY1RoOVRwL1Q1L1Q1MmZYNWdqd2JBYXNRQUNS +YVpIeGNBOUFhQXUzalZBQ09KTklpSTA2anVRQk84MXlDSmN0WEVjeEpGcm4ySUZYaTR0c25BWEUw +QXFJMHBUV2s4K2xaZ1JFc3JyczlGYmhuNjIyMEpxOUk4akw0UjRSRFNDUDRqRXlGem5XTmFBMW1t +T3pGQnp2QzBKQjRRVFE3alZTOGNpRUVQUElKQyt4OUM5QWl5aHNmOXNMRDFpV2VQTVEwTHpjOGV4 +YXpCNmxpdThzdlBOekFxVnpodzVNNnFaZERsVzhISzh4cDdIZWVpck1jOHllV1hJa0FCaGpWRVRm +RXF1a2NUdXJ0Z2tXTmcrTnMyTUpHeFB4Wng2VSt3bEh5ZmYvREFRZmV6b2duVGtYQVRzUFduay8y +SC9zaGZaREVmeDdPL2VOdWJ5Ymx5Qjg3YjhkZ3dNbGYvZEZ2L3VJdmYvOVhjMGpwQlQzc0R3ZFl6 +ZUhhSEd6d05pZnI0c0xtNmhwYzdXT0hESEhEcmxjTXRjU0IvZkRyRVRIVWdUalZSbCs0TngrKzZH +UDZuRDVobXo2bnorbHorc1RsOURsOVRwL1Q1OWVnejVjbVdUSTRzWWdraERKQ3ZrVFltdk9aYzRY +bkdOZzB5SHRCa0tBSTd1ejRiZkVVWFFKdEhrT0NDRUdPR09ZMVk0dU1LT0RoWUN1V0lpa2NFVWtZ +Z1FpeG4xZ1FKaDRpMkxyNDV5ZU5lY00remVmWFpoTTkrMkM5L0R1eTUwUnYzbDY0clNNZFJ2bVpK +eVpORzRFZUJtdTRnY1hlTk5uTnAxRnZ2M3g3V0xIcm9ZQW5od1YvZU1oaGMvUG14SktIZlhnMkI3 +KzhZY1ROTTk0eXAzWXVXSWxURStMZmZ2UDh0cmw2T09HNFhMR1ZVMnZPUm42R2VZY0wzdVNFQS96 +MWtDSVcvdUVRVjN4OTFvTk9MSGJ5ME5RRTVNMk5ldUdMeUlqTlp6N1pzZWNETHc0SDNPQTMzTjNR +SzBTT1UvNHFVRFpFaVdlOUpONmYvOTZ2cEcvVnhTSFRRMUkrZU5hanNNdXRkVlUzUEtmbmI0Lzlh +b0JEc1dHVEErNGk4TXZSa0RNOHVGVXZ2Y3FYT0FRS1gvbE83eDEzY0VjNzF6UFQ1L1E1ZlU2ZjAr +ZjBPWDFPbjlQbjlQbTE2RE8vQmRlRGdENXJlSTc4bEdvandvRFVtT2JySEVqelNBRFUzWjRLQkRp +Zk5ZSjdDeDNSSGhnRTJHK0lpL0NJNFFxdEFHbGFhMGRvZko3Z0pjcWZnOEd6aGpBUUsxN3VaNjl3 +Q0VWY0N5cDU4eHFsd3g0WE1ibUNtVUFQWjBmM3d1QU9nM1djeVJuQkZZRW1yc2pZdG5IbFZXeUtq +ajk4d3liUDVIcjdOSXNDNHFnQ2xhYzE4VFFIY1ZuVHNPYmd3cDg5SHdjN3pTcStPUEx6REtjMUhE +Nzh2WDZKTFg4eFlPeEJ3RjRjRFNpSGN1NnU3c1NuTC9oWGQ3bnBrWXp6QzVmZnZDWmVEMHYyNVZC +ZnFBbEJFZzRmYXFBL0hBRGk0MFFPL0Niblc4YzVidmg2RHVxbkQ0aWFPTnp4RHplZkJHS2Y0VzNO +ai83alB3MDM5dmpEU1d3NWlhc2VmTUNMYXhpdHVkU1ZjR0RtQjFmaTh3T3JyNEtVRDdVeDNNVlFm +L3pJSFJiODZSOXhjQTB2UDRhYzlkZjBPWDFPbjlQbjlEbDlUcC9UNS9UNWpPbno2OURuU3d1Z01N +Qnd4b0htQnNCbWlWalhNQXFxK1JFT1JBWEZHYURBQ2E1cCtKYUlPWHMwZ0FKVlpId3FJaElWdzV5 +OUZiNmhFZTBSU3hJbEJrbHRCSEhoUkdSRWNYNkpSYkw4SVZXUnJObFhFdVRwSi9xLytiUGZ6bmVv +eFpRUElqdnNhNU9MRDQ5YytKQVhmd3FLTy9OeVFhNDk1Vk5lNXNURGdhSjZJeUV2YTRybnJRNmY0 +dGlITzJ2aU8valVDRDZYdURES2x5KysyWXI5dlAxNlM2NE9QVTNNRmc0eCtWWXZmTGF4N2NFWE8r +djg0VXdPOW9tdHNlQXJkMkxESXZmMHlIRU9xeWJ1R3ppOVlzMXZBR1ByTS83a0JpdStXaThDOGF1 +amNjS09vQnhzN1BVaW9UZmY4SDVyUFREMWtDRzJ3NERZTmI5Y2NJNERiMzdFd2FVN0llRmRQckN4 +bHo5aDZ6SHJjTURIYjJwM1BXME5kLzFEQmovdFlYSGtvQmI4aVFNN3pCSG8xVDUrTG82NERtazF3 +VnNQWXoydExtTHlNMzFPbjlQbjlEbDlUcDgrVDUvVEo3L1Q1L1FwRjV4L1puMitjQ0o1QVJrS3Jr +QVNBYUJOSmdsQk9FQXNjamdnTm1EOHBOdDVRTklVSnc0RjFIU0s3QTZFUnJmbW5tSmVndDVlOElV +RU5pNURnV0NzU0JTbkpHa0FsOExCTENtSnNqVlNuTXNKeWU1eTBBQ2EwWURSR3c0eC9CWXBSWUVY +b1Ixd0lGSWo4QzJQajJMajI3dzlpb0Y0TnVMZ0ZTYWMrQ3lPdmRiRk1WOGVEZlBpNFp5OVBOMC8v +cFUxVyt0aW14T1hlSEQzNVBWd0FwZG00MS85NFBlOWNkend3VmR0TlZjUEZqeTFNVk9uMjl1R1lv +TnJnNjBEUU8zMUJIdTFZYU9lRmFtY2lRMzNNTGh5Z0ovUE5QWEZjemk3NEdPUE0xOGJ3UkUreE1L +RHUxN2dxL3pKLzRuMW1yM2YvdUZ2SkRlSHVGemh0Y2R2U1lPUGI3MGpIaitlNVlIckNrazlZWlk3 +ZkhqbUI1WitoWUVOVy9tSVQxUXc2aWVjNEY1ODgyclZBMW5lY09vVmZyeUp3aDM5NEp1OVMzL0JO +MzFPbjlQbjlDbkg2WFA2bkQ2bnorbHordnhhOVBtaVdCSUltQU1naUlDS2JRTlJNR2JuUXFvQ1Zq +QnMvSFFQdUNRSUhRREo4Z1VFWXRrQ0tnNGJaQ0hZQUJaSkVlalpTRVFSRFFraHFZV3lqa3dKR1E0 +U0pQTXJVV0tDeVZzU2pTdUcvWEpFMnRPWXo0QlRYSFpJVlJSN3pYZndLMWQ0RkVVZTFqMWJneFdS +T0hGQXdTdDNXSHd1SjU0VkUzN3h6THVJalErRFgzak40UWRlZDBOY2Z1QVRPM3llZi82SUJ6WmNQ +T00xZktYQjN3V255ZVFJaDN6NGV1eWYzNllHQzV2aXRBY3V0akNwRVo3RnRNZUZTNDFuSHh2N2lG +WHZ1QnQ2SU0xK21QbnpEQzhPN0RNUHEvcm9JOC82elI2eGNkbmVxYmpVWDI2NFlkdTg4ZmRmLzh1 +LytOS1QrbFVzZDhMTndYYjQrWFVBd0NKdlFwTkgrNEZkeFFlYitQTEhpVnJqVHU3dWVxQi95TURx +RHpXMWNIbUd6Ujg2K2xJOGVQbTNaai9kbEdPNTA1ZFljUEl4ZlU2ZjArZjBXWTdGWVROOVRwL1Q1 +L1FweHZRNWZYNVdmZWEzNERJRVJuSHFsSkVOTGtsS21pZ0VRQ2F5T1REbnAyMC9rU05RWW0wd3pj +RzN4SUZFc29TQmt1QWpwSWRZellOSThka2dReFBZaHdUSldZTlQ4UkRoelVuSXVidTFDcGVOT1Uw +QXArVGhRRDZiN0xsQlVIS0J4ZUdBUkxIays0em5yOWpOeVlVTkhQSVFBemQ4ODhGT3NSVlQvdUx3 +MDZKLzlJTm5kdVdTdmNHZVg1YzQzaDVZTit6Qmp6dGY1YU5DMVRqTnk3QmVlM0haK0N4K0QwZkNm +TVpyc01DSysrWW1kcCt0NDVQd0lvaHJTTGdKcFFMMXpFN3Z1T0RCdS82QVFiL0FnU2Y3czM3Y3cy +WE4yeFc0cmVOUGZGeGJyeGpaNnpHY3lnVW1zUXkrZi9pNy95aSs5Sk05NXZRSzRUcnc5Qk8vZmtz +WXZHTEpRVS9KQStmNmtpME80UEVIZzd6bGdDTVlDRnB1NXZEaFVPSlBiK01kUnRpdHBiK3Z0NW9E +TzdXekZ6WXg3WUZMVEw3WTZlWHBjL3FjUHFmUDZYUDZuRDZueituekdkYW56OCt2enhmSkV4ZENP +TXgxU1FIa1htRUJ6VmtTUGhBS3g0R2YySkhoczRablEvU0t5a2JoN0pHb3ord1ZEQ0FBRFFsSXlG +d2ErVUM2S3lBYlY1dFdrZ29wS1lta0tVK004RXBRb3lMVWNHRHdneEQ3Rk80NUZKNDNIRWdRRXlZ +RlJ6Z1Jwb0Z1SU5kK0hNbEpubkpTU0x6QXc0ZERnSDgremJHRlEvTVFrRWFTZzZLd2F3T0UyM2Qr +RGZtSXpZZDlPT1hiMnk3NXd3cUx6K2JMQ2YvcWg0TU9zY1NSTzM3dHc3Lzh6SG1PMEl3VFM3a1NG +Mzl3YWtSKzVPdHE4K0FiWnZodzdtQ0JJYlcrZlBoalF6aDZTNjJzdzUrZWt1K3RHL1lRQjF6dXNQ +S2hIdmlXb3p6VXhqNjhtdGVENXMwRjB3MjJ2bXJpRGhmc2N2SHZJQWhYYjFyekQ3Uy8rWU5meTE1 +NDRDTWc5VXJjKzZ4SDdWZHZ2SW9CVzMxYjE3UHE2cUR6WmtlT1l1R05UMXpxaVFqME1Lc1pIc3N6 +MzJMaDBSNzlhUjZmdE9SNStwdytwOC9wVXg5Tm45Tm5la3ErMCtmME9YMU9uNTljbi9rYlVNQmpm +RTRRb0hBQ3RrRVVVeklJdGhsWjdKSEtSaEJOemlsZ2lQdllJT2FibkwwU2MxZHd6YWZKelBIUHIy +ZFgxelFMWExEeTV4L0NTcWdDNWQ5bExhVEY3L01QaVYwS3JoRmc5VGFnUTA2ZStkYmcxbjFQdmdM +WGFQYkNCRHRiTVJSU1lab1RJdVZiUWVPQ3J6YXlkY1dESDQ1aTRhZjV5Wld0WnpINHd4RmJPZGF2 +V3FTNDUwK2VMdmE0K3lLNEcydzBrdjJ3MnQrYXdjRlhHMXRzOVk0NHJtRng2WTRUKytGekVJbXB3 +UXpyM3BMd1Zjeml5WUUvM0dsY1F0TDA4b0VSdHc0RDNMZ1RzZjF3c2JQdWdnOXUrZkdyUnA1eENa +ZWVzMDk5Y2tqZmdPK2JIM3czdWVNM2VHNWRmL3VLQXZHb29YL0liYTk4OVl0TDc4dUpiUTZBdS9P +RGJ6amx3aDZIT0xIdXdnM3VjQzlITVdEMWJ6N2FoemdUbDYzYzVJRW4zTWxGVG1vdEZoOE9CSmM5 +Yk1RUmQvcWNQcWZQNlJNZjArZjBPWDFPbjlQbjlLbS9QNnMrODI5QWtTTTU0dkpaUUlBWXBMa09G +TkFFWnlQUklranlnckFUd0Q1MjFqV0V4SzBydU1EbUNNdGJBMjlJRkZVeGdVWW9MT0tKQTZ4QkFP +SjNqaCsvZGNvempCckJmckhoSVRDRjZVL21EaFFFeWMrRnZJeHJFUEU5dXpTYnd2RWR2KzgyZkxC +RGJrbUdRVjVJaHMxbk1jU1NJMnh5SndDZkZVRkRpSzlvOW1qR2orSTFyTEZYUU0zUkpqVDQ5Y3cy +aDhiZDRjS1BmYmo3Z3ZzRzNIekYvaG9QYm5uaUJsNitQZ3BhWFpzWGZBVEpKM3UreEhTZzROa3dw +d250Z2ZjakQ3Z25QSGQxMXNoeWhhZUhHRzV6RUp4ZisvV2Qzb0NERC9oZ0tHLzhxNzhlNVUrL2lP +M2l4MkRqcXdmMkVMaGVoTjhob1MvYTV3NTQrL0Zpcmo1aDhlWkgvNXJQUVhnKzlaY1llTGFtbm56 +cUdldFAvRHRnejUvK3dZUDZ5c3VRaHoydytzd09mNjBEbnVGdHJ2enBIZjAyZlU2Znh2UTVmVTZm +MCtmME9YMU9uOVBuMTZMUEYvL1JUQXFOVEp1UXhBaWhpc29aNE82ZGx5Q0g3TzBUd05BSWlEUnZy +NFEwcmlRQnRCY2d5U3BhQllZUSs5cHNFdmVUdjNsem5vR0gwMWNVMkZhZ2toYkQyeUMvY3BvTndo +VGFHcUxaMnVPbmUydEdEb3c3V09TQktJWERSM0Jsdk9helBPQ1dreHo0OXhsdVB1ekhpMWh3aWtO +NDF2blhKSXB2cnprYzRjeXpYQnRQNFJRMWdyaTVpbGlPeEdnUFB1MFRoNjNZbnZIME5NcjEvb2tB +UnBpSkNrWjQxY3g4aFNjL3ZsMjRnNnMxRjFkVDJRZVB0eVRtOUFiKzFNUkJDMDk1SUFnY3hPOE5l +M0FoTHF4OEV5NE1mS2xKbTU0TmdacUQyWnVXOEhDOHdXQ3ZkWWVDR29tbGx0YUpYVXdjcVQ5N09j +TGs3aDlzNjEvNzdQbUwvL3pQVTJzNTZ3MXJ1TVVsc2FvZFRYakdpWnhnWWd0dkJXcVAvS3pqWEk1 +K1JUYXNCSzlmRGV2NnkzNTU4eUUzei8zRFNtNzI0ejV2MEc3QVBuMU9uOVBuOUttRytzaGVjV0Zz +NzFtYlBxZlA2WFA2bkQ2bno4K2t6eGZrQ0FnNGtVa09pWnBiZ2dvdmtPOHZhd1JyYkNYcnVRWGxS +K0VRelY1eWJIeFdWRUg1MDFqdTdFUHVOUWV3R3JteGtQS0k3QmRmaEN0T2hlcW5mYytLd3JlckJD +cVFSQTBObTdVakNGYTVoVFFGdmF1aWh3TldSU01LRGRkQk5MREFJU2ZQN3A2UmJRMHVqZFhtVTJB +WFBNMm51VnVISGVjK3UvTXBGeGhkOW1nMm1PRDN0b0F0YnZsdHM0ZVQrNnhXQkNlK2tXWTVUTmJs +WWw1ZW1vRzkrdUVpNHJ5YVdaZUxtRENLNGRtZExldzRjc0ZpV05lRTZ1WXp6UHc3S0hNQW51Kzh0 +YnMxTnJEZ1Rhenl3RWIrdU5UdytzZ2N6TjYraUlzN0dQaVh2M25pSVNyMmJKNDhubitZYmwyLzhG +bWUvdnozZmlXaUpCSzgvT1JIdjVOKzFqTnlZcU1mNGZYYnc4UnhBS2lmQ3g1NTZWTTQ1S00yT0JS +TGI2dVQrUExCSTR4czFVSjk1YWUyNHNITWwzZ09CQUl0NTdqUnQvM0RhZnFjUHEzTFpmcWNQc1Yz +bno2blR6YlQ1L1E1ZlU2Zm4xV2ZMelloQUJFRXlxbkIyRS9Rd0V2TVQreldFQ3daVGdIeTJiejlC +bHZBa2VNblhJVm84NXFUa0hVSklxdU42U2QveWZFakljUVFFYnZNSFI2RStzdzJCYjZCT0RnbFpR +L2kyL0NFNmJNR3RxK0VacndMRkg0L25jdmRWeFNJLzJreTQzbnJZQThjUHZlUWtCczdNZUVYWHlH +dEtRNTc4M0Ntd1E5L0M0UXZGMnppS2pBOGJQaGtyeWJXQ0VJaHhmYXNPZVNpb0Q3em53UHVjbURY +Z2F2VTV2RGd3VjY4cWFmOTF2Q21FZFFIRm5PRXpwYzE5WlU3SHpEeVdheHkxUU9hMlY0KzVhMVI4 +YVl4MVp3ZlB1Q1h2L3h3eU43UW9MamtTMFBhYXcrcytxVjgyWU5UQjQwNDNzVFl4eS84OXVITW9Z +RWJmU0JuUHZ3L2t2eWpiUGE0VW1POWhoZStpVkVlZUkrUTcxbXM5bzg4Y0t2V3VPQ2Zid2VFbU9M +THUydkZ3VGQrN1pXZjJIaVRpMWk0YUd4MUVVc1BsR1A3Y1RaOVRwK3dtSnMrcDgvcGMvcGtiMHlm +MCtmME9YMStWbjNtQjFEQU5TOGpRUXpFQUFTRWpZZ1FRTE1ycEFTdGF4REp0UEh0UnpSN1JSQlFz +WUFSUzlFUkxEaC9CckRlTHNCUW45WVVpdUN0VytQUHVyY0Q4SGt6Z0J3WFcvdjRRYXlDc1lXMURR +UmpjV3A4RFM0L0pDSDB4OS8vVHI3K0FPOHozcksvWXZCWmZpN1B5QzVQaXVBekRqVzV4dU9iblp6 +RnR3KzI4bTIvL0dDSEZ6OXRDcm5BV2dGNUpoeFkrQ29XRFlock1SMFNIWGkzWDdPSWcwOTFhczdp +cXlVTzVZdGovc3lMWVErYzZzUVhqSEpNak50alRvNGFUM1BDcDJtdHd3dTNSalBQRDQ3Z2tSOTcv +ZzFyT05BVDl2TXRsamNxOXZBblAzdmhONitoMVJsTy9wODNSRy9KemYvczE3dzY4cWttUk8xckNq +MmNITVJpd3RHYW1lOGFucTNEaWxjSGs4OXdzTzFCQVl0MThYRXBGOWpzMTAvc3hkRDdEZ3orNWEz +TzZzWXZyUGFrVDQ5VE9WZWdjcHMrcDgvcGMvcWNQcWRQZU9RM2ZVNmYwK2YwK1RYbzgwWHpTRjVo +T0ZZa1B4Rno1RE94K1ltV0l5QVUyclBnUGdzS3JQMkc0RDZiNXhzSURZQUlBY1ZTVkdzYVVUSHQw +VFNTOXhuUjlta0lkZ0JMbUpEczkvWkFNdmF5c1I2ODEwd0s5elQ4STN6Sjh5RnhkemFHSERVWW4y +MlFiMy80NjNsNzhIR1VBNWVjUE1QSmozekN4NUd0U0xBNHVIQlRFZUJOY2UyeEJpdE1Qc3YvbHdm +SzgxZnN1T1ZUd2VCUGM5d2xoZ1ltT0hIaHdDbWJpbFVqR1BodTQ0dUZNN1o4dzRSLzYvZ3o0Tlow +ZkxqZzRBdEdlOFhHS2JIQVlwOWVnQ2Y0Ync0T09VZWNEcmhyTUg3NTg3VUIvT01RRHI3dGNVamlG +QjY4dzBUWWFnbVBQVDZIbDhNQUMxSHdrOFBtK3RWK3RYVEFtZnZtQjk4TlI3WEREWEhvbi9hQnI2 +SjRWZ3Q1c2NHcHVuVWZQSEwwR1ZhWXhWSlAvc1hTaC9JejlLTzVpbzBtWU1ZSGZQekJ6NWY2dXZp +MFgrOVZUL29LYi9iUkFzejJUWi9USng4dU9LYlA2WFA2bkQ1eE9uMU9uOVBuOU5rKytFejZmRUVx +NEJxSVladWJNVEFLcXBFRlJHZ0ZhSTJRa0twUUFYcEY0YWZCMnB5Q3Vkc3J1VFQ2T3hBK3hTd0dQ +MUh6cFJrUWFKOUVFQ1VlRXJ3aDBtemVDbWh5elNGZUM0TUV3NW9tZ3dPQmlpQzJnVFJGa3BlODVl +UXRncy9QZVA2aHNUMXdFYktZWXNEQjNoVnhYQzdGcTBEd2E0VGF3OE1IcmpTY2ZmS1FOL3MybUR3 +ME1KdHdkSDdzTmMrWHVJcHNqVCsyUFhqTWk5OWhIV2RxVVg4K3MxTXZ1QjBxaHZyQXdZK1kzbnJn +VGp6NWF6VE5La2VZNFdXTEUzeGJnMU91OGlBeWRjM0JmTHlyRnpHMTJlSEoyNW1MZ1RzaVoyTmRU +Zmx6dU1Bc1huZzVXMWpVVEE0dzRwTi9lMkFTdzI4SjA5Tjhzc0V6Z1hybVh5Lzg5ei81VitrTFdH +RlJkempFSTdyMm1Iek42VCtZOE5lNmk4WG4wNGVINFBLVlA5N1oyUTh2THVSY1czbW5ONDVUOC9p +R1JYejYwTWQwSVIrNVRaL1Q1L1E1ZlU2ZjArZjBPWDFPbjgrd1BuMStmbjNtL3dQS0lVT0pJYUxD +RU1CR1lCVFZ4aFlZR09EdEZ3Q0oxcEhHbDJlSlZxQUZKUUZyOXBrWHk3TTRDTlUwS2VENWtrQWJn +aS80WEE0TS9od0lrbUF2aDY3Wm8yaklNQTlUaEhTNEVHRFk1ODJGZ2pVMlVqVmRHOWVBRFE4bHNV +VlNiRTFxblcreDVLS3grTUtOTlhjeCtjQWpEbkJuUDMvd3lrTWgrWU5MTTFqRE5SSHliNjE1S0xR +N0R0cndpcXRKT21DQVdYeDg4Q1UrL3NXWGh3WVVWNlBLR3o5ODJnc0hQdTJUQjkvcUlKWUxmcUtH +dnpVd3gwNWQrZGJZY0RrRTJmR3JIOXFvMXV5UkY5NThOdVJQK0hMa2kzL2N3dUp0RWk3Wnl1ZXA1 +MXZpZWZZUHNJdE5uZlNxM3VGUFB2TDNoa2c4K09Tcjd2Z1doMy83Q2NvejN1SEJJWDVjOElyMVJh +QlhQLzBxSG03aCs5a2Q5SGlEQzBieHhKS0h1MzA0azZNMWQzelVwdnp3T1gxT245UG45RGw5VHAv +VDUvUTVmVTZmWDRzK1h5VGRSZ1BlSnNIY09SR3NRU1VFb0RjSGJDVmd2NS9hT1VhK2VVUUl5RTRE +bFdSQStUSXZTVzhRRkoxdmE1cW1wQUhwc0lETFhNU2orWThFOGV3bEpBMnZDZm13SDdtUzB5aE5V +dk5wRHJHUmJjaFBBL1BuRHJkR1V0QmZDdlExK2ZFdmJ6N0VreHRoRTArYjN4NlkrWksvenpESm9j +VzFacjg4Tkkxbk1UV1lmQ3BROWhXVEF0dHJ6UjY4RllNNW5NdEpQQmdld2YwaStQQ0VEL1l3aW9j +RGVjRFl3MU5zc1pMZjFkeGUvdmkzWC9QeXl4WTIzQkdBUE9CWEx4Zk1la2MrQmx6aWlhdCsxdTFW +VTc3VVJpT3ppWUN2RnVKWTUxdGM4ZlFHLzJJUkdsemk0NE1QOGVEbG93THRBY2UvZllTSFJ6WGhR +NjR3d01pUEdNM0xtcDZCQTg4OUJKdW5tT3JBTjI1aHhxVTV2T29YT2ZNSlYrMHJkUDZzMmNOL0Rz +ZkRhMDVQNlhjNXdTb0grVTZmMDJmeW16NnpObjFPbjU2bnorbHorcHcrWVJRUEIvS1lQaitIUGw5 +c3RNZ3A1d1RUcG5LM3VRMnFBZGdRV0VpNE9aZUUyUklGWHdxTUhJbno0eWRvUUFGUUJJbHBDTEhz +TWU4WkZ1Qjh0czhsUHRBSUZvZU54a3RqSFRHU0VFdGphVzcyQm5IYWI1MC9SR2dFRFc4Z1J6RmdM +UjU1SWJhRGYrUXFWSnNhRnZIa0tWNE9yZnVzeWVYQnAwWlVCTFlLNjdNOXNJa2hubHp5ZkxadGZy +SDQ0Sk9kbVB4cUFtdjROSS9yY2k4L2wzMlBPSXkzTDAwaGhvT0xqYmh5VUFNNHhlbGhoaHVZN0xQ +ZUpxcW9IVmJzOENjblRXL05KVStZSEc1UGN6MURURGtRanZyaGdGK1gydXNKTWVGeXlkUEFLLyt3 +d0tkV2FnQTdYN0RMclhYSG4xcmI0K3NIRlpZN3YyelZXVDdzODRib3ZVL1krUDY2ZVhtcWh6dXVl +eERDUUpqVmdUdC9iT1J1NEI0K2ZjU25BOFphKzF2ZDJndUV5cC9QN09IR1Urdk16cjdXY1BxY1Bx +ZlA2UlBPNlhQNmRFMmYwNmY5MXZtYlBxZFArWHcyZmI2VWFBUURveUU0cUtDSXdXYUNKRkJGRUl5 +dHh1UVUrVWh0SWkyVXU0Slc5SklEU0NJK1c1TVlId2lMbjdQakN3NitrT0RlSW5yVDRLc0Y5a3FD +cmN0YkF3VlRESHNWRE5HSWg5TWF6RzBnNjBpdVlOd1Z5cHdEeFpBUG5QYmlDQWYxcDRuNVV3UTJi +T0hBbFNLenNjNGZHMWprcmJua3dCODd2alUvYnVXT2U3bVo1d3MvL0Zkdy9NR3VpT1kwQXdHS1pX +K0gydm5WNG5LRFZWT0ppV2QzRno5cTB6emJsRER5RFFPTS9NT0hsNHBCcmZqbUQyNjg4Sm1HWlh1 +RHNNVFdZK3pVSHYvcXhrYUQrbjY2SHVOTFRJT3cvZlcvSE9TUEQzbkNBaHVmZXN0ZGIvQ2xYNG1p +WHo5UVQzdnd4WmVEUUc3dytYOXB3UzhmdlBrT3ZSelVxUDRkQlBDNFlNYTNlckRIRlR0NWljRVBq +T3FqUjNBdEgvaDd5TE4xRjZkKzFRc25ZdWtOUFF5aldQeDVuajZueitseit1UmZYdFBuOURsOVRw +L1Q1L1Q1TmVqelJVREdFa2toYmlDMnpTSlJaQWpxTFlaa0FXRXJFUUFSekhHRnlGNFJFQ29RQWhX +d3pXR2YvVWhWQUQ3czBTQVIvZG1LeGFjbVVVaEpTSVk0MlVWRWR3WHJ1MGpFWStNTmlUMXB1UE9s +NE9LSkhUdzNrR0FOaVFxcVlQNkI3aU9ZWi9BTmp5YVFJeTc0Y1NtbVdEMGc0T1diVC96SVJ6dzV5 +OU9jSXZRZ0tCNzhHODBYcnZCK3VVU2d4SE1ZY01TSEN4NDFZKzh6ekhLRlYrNkdkYitxV1dQWXIz +NXNjc2pjUG55SnBXWmlhd1oydUlTTFg3RmdrSk5HeTNqbkhHZHF3ajhPMmJBdHZ3NGRuODNob0pm +KzBCczl6QjBpT09FTGo0WjZFUTNNTUxKVnp3b1VkdGpVd0x4WURnMGkvT1lIMzgxZTY5YjBHa0VT +ak56ZzlLdW8wMFB2c1J3U3NLaW5HUEpoN3hrditJRUR6K3JHajFyeTRiUGhEd1Vha2d0OGN1VWJ2 +KzZ3eVozWTVhays4TWxCNzRrRm16cmoxeVgvNlhQNkZIdjZuRDVobmo2bnorbHorclNISFYvVDUv +VDVXZlg1b2xpTUZRNUlCUVlHTUk3TUNhWnArbWFBTTQyRkxIYm1CRU04a01pcGFDclFFcXl3WWdF +dGNRWG5GMUFFSUZTU1NlUVNhaUwyd1lLVWtIdjdrQUpYbXV0ZG9CckNFRC9OOWQ3RWt0V0FNQm9P +QnZQV0ZZT3RpN0E2Mk5vSGgvaXdwYm5mQllOUThUVVFXemh4dzU5TE1mRmhQMDQwb2VZM0o5ZjRo +UGY0YVRQamhXOEhrV2NjNFVCTllPTkhBL0VOQzkrYVJRMDB3eVBRdC9nM1IwQzRnRk1kY2FSMmZP +UEF3TDljK0pNRGZ6MjQ1SnErT0l3R29SWUwzMktVWS92c3p6aDd0dTBUTlhQSXNWTTMrY3JWWVlW +ai92Z3gzUEVuTi80MEszenc4NFdQOW9zK01XQ0M1My84eGIvNTBrZnlLcyt3cVpQNitIOHA2VUhj +eXRQL3lCY2ZzSXFobit3Uk03bmZBUUNMSHVNSFh2SFZFaGFEcng0Zy9NaUhIeHp6Z3lkNFlGQ0w5 +TlhwaXo5LzZNR0xENWQ2T2RSYjQrbHorcHcrcDgvcGMvcGtOMzFPbjlQbjlQazE2UE9GR0N3Q2Fp +TkNXakFnQVBRVE5DSTBqZ1RhU0lBQWpSaUVzN0Z1emw3RkRZRW5KdXVlRlVwaWdnTmZ3REMwNlRR +aHdoVkdFa2pRc09KWlV6eURUL2pzZzRkdnhKZFFjOWJ0VnhqUE9SUnVsRGh6L05tWEErVHdmeG5Y +WkhpQlF6NzFKMzk3RlQ5RnVCemk3N0RiancrMjFpdFVCZXRuc2VUTGovMFp4eEdNbnVXdWFJcVhn +K29LUm54OEVMZDgrSUJCSWZuUlVIQlVvUFo2VTRKclhQRDUwMisvbHdaaUw3NjlST1J3d2F2NDZx +L09FZlQ1eExsbStpaFFlTmpMa3ovMmFtdS8zc0UvbjJKYXh3bDdBbVVyVjFqMWlhOFVXR3RmeVZO +K0VlalZQajF3K05OYmgwY2ZpSVVIKzluREpCZHYrTDc1ZzE5TEgvQlpnZUxCNGFoR25yMnRzUmNH +dnVHU1k0WEpQM3UxeG9lZWtRdDg3U1B4MmFobi9yQTRETzBoNjJ6bGdRZDMrZkVoSHI5aXlZOGY0 +dFZmK2djT0hNblg4L1E1ZlU2ZjArZjBPWDFPbjlQbjlEbDlmazM2ZkxIQW1RM0lVSFJHQWlHWFV3 +a3FTcHV1d2cyUUs3UkNBY2toMEFMd1VSSVFwZUQyS0hpYkRIZ2lRNEo1R0JETnY4L0VJYjY5U0Rm +dlFyNWhueGhJWXQ5RTJ3RGl3VzI5NUxiUitJUWZTWWkxVndQTHIwTkIrV0RMUjBVdk56eUpiVjBz +dWJjd0RqMTVzSWVIbmFKck91dndFNXZQL0JoaTRLQTV5NWR2L09CS3djeTV4RkVibUhwNHlBRWZI +Zkw0MFgvNlovRUptMzNxYUI0UDRucFRvL25nVnpjMmFhTGp6S1VIMk1wWHJTTCs5ME9MOEhzQXdN +YzJ1WVRmMXdqSFhvTGkxNEVoZjlqWmlLMW12a2FoZWZHdmwrQlJreDdJT0JTUFB6SGd4S1g4K2Vl +SE9QREFCL0U1M1BBckJ4eUtRYnpwbDl1cnpuSnFMbzBqUHJIS0ExWTFVVWY1NEVWZCtaVnZ4ZWh6 +RHYzck9aallxTGZZcWNmRmtJODFjK3JKVjNyellzSUROMDIwdDZ5emcwdHRwOC9wYy9xY1B1MFJj +L3FjUHFmUDZUTWNITzdwYy9yOHJQcDhVVXlHUk9hdW1JQnFJZzBraUg5VWJFT2J2STNKb2VaQUpx +SXExZ3JkTTJJbEtiQ0x2V0pKakFBS1Z1T0lUOHlTOU15LzVHQXc3OUtVZ0FmM3JmRm56Z1VIZkFa +eUhSVGlzR2RYOFNrb3N2ZzNwMUhrQ3pzL0hab1NnZDBQSDFzY2hlRHpZWTZkdU9iWXdxL3dmY2FE +QWpRditic1VEWmNHdkdMWUkwOTdyTUhLQi84YVBZMTNlWWV2dTR2anM5cmdzZ1BPUC9uZVA0NlA1 +a2RROXZzTXQrYUJIYjlFa3ZuemFZOTRyYmM4SDRINm54ZS9CcU9tOGxmOWJkZzJHRHVZMmJXWE5M +TGNYZVlpNG10byt3akMzaDdhK2tnOGIycmcxa3RxWXMxK0FvUUpWM3JNNFVJZ2NuZFl3QU9iZk9U +Q2w2OHRlQzdIZnBXMVhQVTB6SGpWSytJUUhveDZubjFFZHJrNElOVU16aDRRYk1XSVFPL3dnczlj +YXZQZXk5YmdoSnRmK2FpWmZNeXp4d0dPWUxGbUw5M2d6dkE4ZlU2Zm1UK2YwK2YwQ2QvME9YMU9u +OU5uOWNXbk1YMmVEZy8zOVBuL3Z6NWZpQVNwTFJwUzJpaHRNb1ZGdXAvT1N6S1FDaWdJTXV1RHJj +UXJSb0dReUE0QkxUeUFoQ3NHVytRaXdUT2hJS3h4SkF5SDVPeGxiNVJnbHpsdkFmZ3hKQzVlTDM0 +VXptalQyeWRmTWEzQnBnZ2R5RzBqaUF0REx6N2RGUUdITUxLMUgrSFd4TUFKZmhTSUQvSEtnL2tL +VklQWmp4ZDQ0SWNQZitZVWkzMmJBaithQjBmc3pPUEhVQXN4K3QxditNV1RJenNYbjA5ai9TSlk0 +WXZ2NjRIV0ZqYk5hNzhHN0JEZkc1VC85WmYvTm52Z0ZlK3hJK0xuLy9zRUczOUV3Mzk3Qi85aTIy +TWVSL3p4YXg5L2NsWFRqd0tWTTI3MWh6MjRGcE0vUFNZL2I0TzhJZkpaZjlublYxZkgvMkZSSjMx +Q1lCVWdmejdqeUY3eDlMcll1SEdnNkUwMVVLUFdydHc4QjlJdklqTDl4OGJCb0Mrc3dVM3djdWZU +WFR6ODlsREJlZnZKWG5lSHdmUTVmY0k2ZlU2ZjhwYWJmWGliUHFmUDZYUDZkRTJmMCtkbjFPZUxB +SnJBdlpzQmRnZEtna0FqRktpU0lxaWZ1dnNUTXpDS1pjNWVOcDZSUkVnQ3NoTkQ0ejRpZTB2RFZo +aklBcElkSDJLemEzTUJudXNLYWNERVB4OXM0ZFlFM2o0b0JBTDRjdGR3V2N0NHl6emY0dkFuUjBX +VFY0ZG1FWTlkU1MvR3pwVlUvalNOdk9GVlRQNDhpOS9EUXpGY2l1c091OEV2ekJVSUh2blFlSElr +UXZ6ekFhTzRSTjJtd3grZW4vRWFINW8xQXIwYzJkZ3ZKcHoyUCtQdGkxRHd4NGNZYk1XVWkvc2p2 +RjkrOTExdC9ZWXY5WWNISDRTVnR6L0cxUUN2OG9kTmd4SUpMSGlWRjR6NUxXSFgyQVR4WUhwOUJI +cjU2eE9OM0h6eFdBN3NnY0U4WDNyQm5IL1A0UjkrVzlPLzlzck5IeHp5Z0FjV3RXa3UxdUIzK2N3 +bm50UkViZHRuMXEzMWdGTERwMThlYm5EbFVsTng0REl2Qi9uSml6OXJmT0FkVG43eDRtNWVUZUZT +ZjdiVDUvUTVmVTZmN0tiUDZYUDZuRDZueituemE5Qm5mZ3V1alpvdmpYWWtJQUE0WWpJdkdRQVJE +UUJpT1ZaUUlBV3kxMmRBK0pTSTRnSFhwcTdnekh2dVQ4L0lFS05GcmVnQkpBS1hkWDRrejRjQm0y +ZngrVW5oVG9TSVJ3S3M5c0pESUJyekdVOERXN00zRFhHRjc1dWRYNDducjlrUnhyOENLN3E3dUdu +K2l5TVBXUG5ISDUvdS9CV0hSclNPUXpqbHpBWW5CdjdLaXpYWTVDbWV1M2o4eVUveDdKTS9iUEN3 +VWFNT3Z2UC9EQ0wwZDVIQzRhQVRQL3lmVUdKN1hKakRNYjh3dXpjUDhYQWhUODJxUnZ3NEFHQ0ds +Vzk0Y1ArOE1Ybjcwb1RXWUhmQnFYSFppSUVYbktpUHVJYWUwVXRzeFU0TnpoZU80V1NQQjRjT0g0 +bDV1UkFpY2ZvT1BCSHFGL3Q2T0RwODVPb3JDNTZ0ODUzRC8remdKSEJ4Y1NHMlA3ejRwZ1h6TG50 +d0NnZnUvZUhFVG8vSWwrL202Y0N5dDczVlB3VEZheHhha3J1OWZPaEhIT0NXN2ZRNWZackRNYjh3 +dXplUDZYUDZGR1A2bkQ2TjZYUDZuRDZuejgrZ3p4ZUFGVUZBQ1RDMFNXQWIzYTIzSVpIaXVhTFNB +QnhyRHFSNkZvUzlBQlVWWHdMYmoyano0aUZZZ1g5MlAzRWpHa2tTdEFjMmVNUnpHRWlxNGpVa1dn +SHk0eTJQdHdxR1dIeVpkeEZnMXU0UVFEaWZMbGdSeGIvaXdLVzVudkg4MUk4RC9oVEV1bnlSelo5 +bnpjYVhHTERqaFUrRlZ4Q1krOWY1OG1lRGd4YkVJRVR6WWxYSXhLZndlR2NISTE5dFdwaUl3aDV4 +MGhBMzRNR0xmTlNFUHh6d0lSKytQVCtDZnMwOEc5akVyRURGOEZrY3ZHazIzSlZQZ2dpUDZ2VGVk +STg0WDlPMHJaMmVZT3ZmTUhpMlpvaUZGMXpJalE5RExyZ1F4MmQyRlpDNGVQUTJTSS9BWENFUXV6 +NlNDMUZxY25zSmxrQTg0NFY0N1pPLzNNUzN4cTgxK1loampXK0hFaHl3NDQ0ZHZLMHpidFNQSDM3 +bExSZTFjOW5iQXlqNVhNMzRkamxvWUlhTEx6bkRvc1ppVFovVHAvbnBjL3FFeC83cGMvbzBwcy9w +MDd4citwdytQNk0rOHdNb1E0UjRTOEdBK0RSZ2d5SENoUlJONlRPSG5OdW4wU1Npa2VvUG1SSnE4 +eUlOTU1EdFY2UytrYktuamNjblAyeGhNZGRtOXh3L1Z5ekRtdVpxc3NqMWpGQk5DNE1DTmI4SVZK +TWRKdkZjNGdURFhmWWp0SU5kbXZiaS9jTWMzTk1jbDRlREJxbDRJd2lGOHRuYkNuWStpOCtIM0Qz +RHhoWk9Cd0lmQ2c5L3VaYXY0ck9EM1Q3enpVbXh4VlVqK3p3YlBlZzBFVDdkMVFoKzJLelp6ODRs +RGk3RUtBNnhZTGZQT21FYXVJV1BMem41ckZmWTRJVS8rYWd0VE9ZclVEWEVtVFhjc2ljZWVBaElI +eGp5SUJCK2NTOC9kNWdkd2hwYVh2YkRDck9oZnI1aVlNMWJJTmdKUjI3ODRVMHZpY2tIN3N6SnRY +WEJGUjVnRm8rSVllWEhYaGVjY21mditSbHZ5UU12OG9hTkp0b2ZzSW5oUUJkWGp2eXp4d3VmOXJr +cTBJOTZFbS82bkQ2bnorblQvdWx6K21RL2ZVNmZ6Vy82bkQ0L216NWYycVNNT1VLRXBJRkVwaVRk +QWVWSWdUV2FlVW0zeWV6bkN6RFBmTGhyZUhlTnhRWkloQ3U0WWxyems3QzlDZ0tvcEFFVng3ckVF +ZUt6Ty9BSVVmenVZWXQwaE1CcVRRNXc4Syt3L0NEYlhqbVpsNjk0L0NvdUFUL2o3VXNUT1l6NGtq +dS80c05pVHJPeDFiZ3d0TkdJMDFjRU5JRGN4QlBmMWVieWhzRzZvV0R3NEZVOHhjZVBmT3gzYVJw +M1BLcVpCcEFyRHVDcFFIRXVqdm9vc24zbGptK2Y1VzB2LzdDdzkvVVMrWW1ocnZ6NkxNZEhlSytw +Rzc3YXpQS0NSYjN3SWpZYmZNQloyeDVjN0RVN083MEFrNXpWRG0vMjRvU2dZYWx2ZU9VTG96NXM3 +Zmg3QlBxV0dQNUJ0bDdMMjZqelZ5NzBnVHMraUVVOGVNVXloeFByeGRrK0l4YTV1NHZKVGs1aXRG +ZDdlTWxYcnZqV0MzakRzZjM2UXA3eUVkTWxmejc1c3VhU2oxajJ0ZTdUNS9USmZ2cWNQcWZQNlhQ +Nm5ENm56K21UdjgrdXo1Y1d3cUpDSVVWUVJBQUpMRWRJMWNCK291V0VqYUJOQkNDQktneUI3V3RD +Z0xlWjJHZ01RQ29zT015emIwRmFvQWp3TGtueEE0dG1zYVk0N051UUpWTStmTU1CTTZ4OEU1M0xI +czBLV3h2Rlg1ZHJnZzdGRmRNK1BDZ3luMzdLdDBlZTRzWHU4UEtwZWZqa1QwenpDbWMvZXpIaHhJ +TTUvZzE4NEErMytHVDNDTzR0ZFpFRFFjdUhQOXl3WThPWHE5ZzFERDdFNTBkY1B1QlB6TU9FTzM0 +Y1NIeUpTM0Rselg1NzhDMWZPUklkUE96bHdEOGVOQ0piUGlQa3YvOTViTmxaaDUwZjJPR0Jqejl4 +TktFNXZpTFFxdzB1OEFpZkhCM29mS20xNXNjRGZOYllQSWZrODl2Ty92TDNmelY3SGNqeTVrc3R4 +SmQvMzhqNHJFLzRsamY4Y3BlWEE5UWNudVFPcnpqOHdNaHY4OEdiWEhIUEY3R0xvYTV3aWE5R0Rn +NjV3aGorYjExTVBzUTFyMi80bDVjZVY2dnBjL3JrYS9xY1BxZlA2Uk9lNlhQNm5ENm5UM2wvZG4y +K2FBcVRERXo2akRnL1JTTUNXQ1J6NkswSEliSGhERmxBSXc1UXo1S1dBS0QySk9qNXM2YUF3S1da +RHJoMVJMTjFJQURiZU4wbk9Ra0RDemdDQ1YyajhJTUVmdXhoa3dJZldjRnd6M3p5TFIvMmh0aDhw +MkV2Tm9JUXJrbjQ3VkNZRVBtQitBb0l1WGpnaTUzUDFucXhsd3MrWEhrK3J1SENJUjl5Znc2YkIx +TUw2R0tQcnhUNG1xVDdZUmJib2VHQ3o4RWcxc09MOFJvTTZpVi9jVHhyT2puQ0lMNjZzWVc5UWxF +ZnorSVJCYXpCY1p4V29QaHdxUEtIZHpYU3dPejRNOXBMNHVxanhQN0o4NnU2Q1ZoTytFeVBYVjV3 +cVlNNDl2ckRRQS9BQ1ljY3hZSFRIam5BYXI1NTZ3WC9jMksxOXU4RDFCVDNmRHQ4V3hkdmorQ0h3 +UjVmWitBUFQyeHhSc1RXWEE0eGRjQTlIM0tHUTMvaDR1SGwrWi81aXNtWEMyNzc1T01mdE50Zm55 +N1k0VkZIUHZWa0RycXJLV3llcDgvcEUvYnBjL3FjUHFkUGZxZlA2VE1ZcHMvcDg1UHJNNzhGMXdj +RUcwaDFFUlNnQktGd3lKRVVjb0IwSVpnZFVuMzJqMlFWQ1hqemdQRnJ2K1FWUVBPSnFVajJFYmhD +aUNWaGU4VmlidzM1WXZvTXVKLzhROW9OU1NHb01TU0tKSVdEcjgzcHJrSFlJbEpqK2d5SDJFaEtr +MStqZkJ6d3NlT1BINlN6bDZONStUZ1ErSVRibFNhNFBIQ2x5SHp3alE4aXFoMmNhZFlydnFFZzR2 +UE5oM2ptNU9ZdWx2ejRWaDgrWWJBR244YjZJdER6aVE4Q1pRc3pmdzRxZ3VNRGw5N1MyRitoc09N +RFg3Q29sWGw4eVVQTnZBR3l6cGRhYURwMWlMZ1B1OE1LSDVxMk5SQlA0L3FIeW5yTmdCbVdpaTMx +dVhyWUJ4ZnM5cGNEOW5EWWo5djJrcjNzRGJZLy92NTN3anVCNmhtOUpMYi9xVERlNGJVSEpnSlNQ +L1k5VEh6R3M3N1h2M3BOVG5peDF4cnVvNVBESXVhVDgzUEl3bWJkSmJaODhPSVFzRmFmRGc1NTRi +NDFjb2RKcnU3OFQ1L1Q1L1E1ZmVxUDZYUDZuRDZuVC9tMU4vaHhuejZuVDd4L05uMitXRkJvZ1Jz +RXFaSkRBcUFTVWhoTnBxa1VEMWprS3BRMVBqU0p3QnpiaHpqKzJRTGlybEVBRVFkWVByNEk5M3pr +TUxpazJkbVRSajR5RlZOaTdNWHpwZ0lKa21jakRvSjlMbmx3dXlzTWJHdy9OZzg3b2hISG1ydy9E +cjVnNHBzdm5NaGRqdXl0S1k0ODRVZXFkWDU4UHg3bWNvQ1A1cytQT1kyajZXR1NzNzF5eElkaWlx +R0k0cGx6OFYyTytDQTBqYTBPT08zQVdkOUtLRGlzYlZwckdnNy83dUpxVEw3NXd4ZHNlZ0kvY2lQ +UWlQUjR4NTA2d01nWG5ucWdWQ3psR0ZaKytTRm9PTHB1elQ1NWVhc2pIc0h3VTFzeDB4dVhvNFpY +VHptMG1ZUHRQVzk5OFZkLzlKdDVHeVJYOGRWSGZYL3lvOThKWm5rVno5TVB2MGh2cUs4TEYvcUty +UU1CUDN5b2c3MjRkTUVhVEllMThmR3BEbkREYVMvODZ1U05GOXllV3pONzJ4ZnlrSk85K0pTYk90 +aHJidnFjUHZtZVBxZlA2WFA2bkQ2bnorbHordnpzK256eEg4NU1hQlRCSldZalFpU0RRR0Fsb0ND +SWxZU2Y2djNVcnlnaDd3S1l0d2NwYlZ6ekVoWUhNSDZzU1VnY29wZUV2ZTdJU0hNY1dlekZrd1Ev +Q0VoVC85M2Y1czBFdkRrVWJvMmR2ZllwZkMvRlVBUnJCZ0xNOFkxMHVNUmwrM0Vvb0JndU5uTEhq +VnpGeFpuaWFRekZnQjgrMkwvNXdYZlRKTW54RGpaY3NWY2t6ejdEKzR6WEZBNW5NSWJuNHlTTmVR +WGp0ODFUN2l0YTYzemhQQWVYTjA3SGoyZTE0Y2NlK1BqbkE1ZHd3VzdlTS84T0ZEbmpSNjc4ZWxQ +REwwRzVERncxSjN4VW9PN2lFMW5mVXJFVjAvVkxnVDc1dHBmd1JlenkwbWM0MEJmeWg2ODh5QmxP +YStMQzU1S0xuQW5Gd1Nodmg1Tjg1TWcvZ2Rvam5obzVFUGlGeFRPZTlJTyswRXV3aWtkRWNzRUxm +K0taczllYzY2TkE5VGt1eXJFODFVTC8wUm9PQ1JTbUhrUjZRai9samVkaEVKZC85dFBuOURsOVRw +L1Q1L1E1ZlU2ZjArZjArVFhwODRVemhCU3NSWVlXT2JlT1pFNkJsMGlGeGpIUUVtaGc5dVlCUkRh +UTlwc1R3eHNRUkNIVW1ua0pLQVRRZkxBUnc1bzRDaUtPWjAzaExZVW1xRURaYXdBRjU1TS9PT3hC +R056RUlvWWhSL2pna28raUs2cW0remcwREYrYVE5RmdzYytleEQ2czhuRFl0R2tWWHRHOEVXQUhI +d3o0dE45ZE15bzBIemxzN3BJUHpHSzVpNmZoK1lJRFovSzNacCthc2JYV3Qwb1J4dFd3YjNFY1Bt +enc0bTRPZm5lTmExUU1zQk13ak0wVGRqNXdLc2NlQU9yVUpzZWR1c0tGWDM1ZDl2Q0RJeGo1bHJj +NVBtQTJoOS8yQnl4ODRNYWFPckhUeU5aOGprQVBLMzdNdWZTWklhWi9ERTBNdm8vUEJ5eGlFaTdl +WU5kRGhBR2J2SHkxQmc3NXFnODhzSXF2Qm14Z1VFY0NKMGkyYW9QWGp3TEZsMTdFaHp6VndtZngz +SEZZQWVJRlZybkQxZ01YcnRiQUhqbE9uOVBuOURsOTRuajZuRDVobmo2bnorbHoraFR6cytyelJj +SW0zRXVzeG1Kc25sTU9BWktZWnZmWDJjQnk3RmxURUtpZ21zRWNvQS93dDVEQ3AzWCtrYXVaMnJ6 +c0VTQmhoUGlzaVJUQ0dySVFudWRMM2w1WUZWS0IrR1NIWlBQRUxZYkdnOVVlYnd6WUdacUFyUndW +UXl3RVBybjhjdUJFam5DeEpYaDJDcTlRYlRZSEdTenNGWnNkd1NOYnp2WkVvUGVNVDUvWjgvdU0x +elFCbjdEd2p5ODg0RU1jYzdCVVlIelZoemRXL0twQkJIcUNoMFVjNnhxQmIwM0FqM3RxYzNaOGxM +dGd2RmpXY1ppNlhUM3hwYkY2QVBDSlY4MkZQem5LR2Q2bktaKzNjR3FBTnhmL2JKb3p6c3pCRFo5 +YVdSTkhUV0ZxTDdTaG02Yys3S0VETCtFYWhPcE5FTDU3cU1QTzd0cy8vSTF3eHg2UHZzWUFsMzR4 +TDEveGNRQVBHL0h3QkNzc01Ja3JEbTc0dFk4ZExtSEVpOXF4VTFORFRQUGkxWmVjOEt5MzFRUlg2 +b3BQdW9DQmorbHoraXgzMCtmME9YMU9uOGIwT1gxT245UG5aOWZuaThJUm9vRElsUkJ4SVk5Qms3 +VFJQSUxNdVFPcytRMUJOWVVnNWdWT0U1eFBpZGpMbitDU0FoNGhrbUVQNE1lbWdnc0dBdlltSW9m +QnhlSy9UZGdDSXN3OGU1L2QrZU1YK1lTdFlCVW9jdmhGR0V6dThJbjVjVFJuL1BBcEJuOXA1TXNK +WnhWbkc4VkJ3dFkvQ2lZWXphYWg3T0ZmRWZtQkQvNk81b0FiM0NvZW4rYll3V2cvRHVCU3dBajAr +T1RUbmtkQVI4MDFuV2RZWVZjL2V5dFFtR0NGRDM4VkV0K2UrV1ByamtOKzJjdlZIdldSazZhREJV +NTh1anVRMllqSm41cnliOTBldU5WUGI4QUNJeC82QlM2eDVNVTIrUjErZHhkczh1cFhDdkRQWCt0 +SzNBVHFjRlEzOGVIZ1Z3K0pKeWMxOThiSTNTanZlQTVubDUvYytKWkxlMVV1ZUNISXhtNTh1UFVF +M0RDekUxdi80OVliU2pWZ0s0NTZ5MGs4OGZuRFV3Ui9lY0lNbnozVDUvUTVmVTZmL0UyZjArZjBP +WDN5TjMxT24zQjhabjIrU0lSanhncGdJWTF3aFhFblJvbFhaQndpU2JLSWFKSUFtVmNreEJDRlJn +R0VUM01SM3pXVXk1b2k4Y3NuMzBSb244VDQxK2h3K0o2MkpLMGp5YkFmT2NUbTRwKzlISnEwdTMz +STFpRDJHQWlRbC96YXVQYmI4M0VnVWp3WTVhWXA0R0ludGx3VnNFVTM3MWNnczFVb2g0aGlkcDk1 +UENzQ3JIam93QVVPM0dIbWkwK0ZVM0I3eE9RSFhuN2d3cTM4SDE3dTRNcDRTOVBiRHlNN2VXb0Er +K1VQRjlGcFBISHhZeDMzNHVHdWdsQkR0dnBBdzRtbkZ2S0NBd1oreXdlZTJkc0xoenpaNGx3OFEw +UHJoeHpPbDV1Nnk1VVkzQjEwY0dobys4V0JXUjdxNlk0UHNlMHgzQW5VVzdyMlVySHpKNmZ3ZjNt +cWsvMkdlUWU0T0RDMlh4dGZQbkxqRS9mV203YzllREtzd2RaNmlZMHY4ZlREUjM3ZDFSay9xZVg1 +ZDVjN3ZIb0tQOVBuOURsOVRwL1Q1L1E1ZlU2ZjA2Y3hmWDR0K253cGFNMElETkNjYXhBRUM2aHBy +QU52RGNrMis2bGJFSTBvdUQwdHFnWTBnTFJYWUhzVkY1R1NSamhiYndkY2ZFWEVsNENDZXhPQUZI +TjhJMUdoSlEyVHovekNEQXM3ZnZrcHlXTHlyWEJwbm11TVl1UVRNZkx4ek5mSEFUZlMyZFJXVE0x +aER6L2lpU1YvL1BocmU3YStJMiswV05aYlZMSDQ4QmwzTGdjTFFlR21tTVJPUFc0dkgycEZTUGF4 +SVR3K2NjeWYvSjd4R250eENMY0hGQ0hpZ3MvaUY4OWUyTTJiMHdld2FKTFcxQVdqR0dKV0pCb01E +dnZaNDlkZ0w3WStVQStmMWRKZUEyL3FBNWMxMk9UYlE1dC9qU3Ftbm9uUUR6Ti8ra0p1NGVBdWZX +U0krZTBQZnoxZlB4Q0xEM1BxNm8wZFRuQW5OL1hCcStHUUVGL2VQc01JbTdqTkdUZHNXa2R4ZmNZ +UFhBYU1EZ1ZyUGhPaDJzcWZRT0dRSDN1KzdWZFRlSEJnVC9pK2ZIQXB4K2x6K3B3K3A4L3BjL3Fj +UHFmUDZkT1lQcjhXZmVhMzROcG9rNFprMUdJQmlReGtzbEZjNndWbmJ3UjZnNkFFNDZPRk1ZQkV1 +SDIxMXhRU0YwTkNCUStjR0d6YVRENDNDZUpReEdlOEpnSFBZaElHTzgzS0wxS1J6U2RoLzgyZi9Y +YnlNQ1JmWDN3b0lnejIvM0s4cGhEaXR4RjhsaWVzbWtlc05xdzNCWnJDcGVuWUdYakNCUnk0c01l +YTJJcjBqT2V2OVRXUUp1RlBEaW4rQ1p3UTVJSS91TTNaejRaL3NkMzVNSWhFWHZiRDdQQ1NtN3J3 +SVgvMWR2VmdVR3U0TkVmcm04UGw3akREN2cwUkR0VlRYL0RuZ2p1TmVmT05qeDh4WVllMXdvSUh0 +MnlzaTR0UGVjbGJESGYrMUJVR2VQbUFseC9DRTFmK21iLzhEUmk5K2RFNytDSkMrL24zajdmaHMw +Y3NkZUxYOEt6WDJQYWdzQzQrUEM1cjdNUzFyeHhWOUd6d2JaODVzWEFnVC95cWdUbDgyMitOTC9I +VXhHZHg4YU1HY3VYSEd2elQ1L1E1ZlU2ZjdLZlA2WFA2bkQ2bnorblRmdjQvcXo1ZkpHd1JBQTBq +aURua0FlU3VLSUxVR1NkQUphbDNjaFNDdmVCcytER0FRcmdZTGdRUnFMdTFOTWVScVRGaDRBY3d5 +ZlBQcDhZUjIyZDdES0xpRHc2NEVTT09ab0tQUHpncW1KOSsrNzJRMkwxdG5vcllHeVQrTkkrUm9o +eEpGVWJ6ZzROL2VTTFNnSUZ0M21CZFBwcWtPQW1saGJSbUh6LzhLakp4ZW91QUUzNzVNYS9aMlBN +Tkx3NnM4NGNuUHRsNWxqY09uZ1BvL04xbFhmT0xxNTdsMU9Gckgrd2FzRUxDdGR6NFVwdkVnZVh5 +Z01XYmpmSm5qb0RhVUo3RlNNNS85L3dqYlg3RmdvSFBpdEhuNEx5YzFRVVBzSWtsVC92MWxEVno2 +Z2tuM3R6WkVtamlYUjdFcDlZR3Jud1ZBWDUrMVlGUHVQeVBjbHR6OVZRcmRnWmZmTW94ZUk0blhJ +Z1BLNUhoUUgvZ0JSWVhMRGlBUS8waTRxdVplUElrUnNNZS9NSVN1OHZQdmZXd3pwZDlNS21OWFBR +SmJPSk9uOVBuOURsOWlqOTlUcC9UNS9RNWZVNmZmTUwxV2ZXWnIrQXlrTHhrQlJPSU1VQ0FWcUNL +NHFkdXdleVJLRnNFQWFSWWZLVDVGT3VHd0lKYUV3TTQvaEFGaEJqc3pVdmFzMWg4bSsrQjRWSTAv +Z3pKMWtlYldjSUtiQjliR0wzNThWbXhETEhGVXBCaTQ0ZEE1Zkp4eUFzZVJTQVFHUGlVaTN6bFF3 +dzQwNURXK2RRa1loaHM4ZGFpODlWbXRhOEMxUnhzckl1cHVKNWJOSVgwTEw3R2dkbWFpemhoU2VN +YjV3K08xdFplK0h2WXdhNEo1SWM3SE1Gb2pUOTFnTnVhbUd6NTlteFlrNnVZUHFzNXpQZ3JCZzBv +UGpzMVZBUFBiWHA1cTVNNTYvYmkwRnN1R0hDSW56WXJ6SjcxUlgrakd6dHJiQXpQL2tlOTdQQllz +Y29yaDlYbDRsbXZPTEM5dFRUd294L3dnUWQrNE02QjRjQytub0lONXppRVJjN0Y3Yk43YW4zQ2g0 +bHZIQmppd2VBWmh6andtUTE3c2ZybWxhL3FSTzFhUTNQVzJJZ0ZJODZueitseitwdytwOC9wYy9x +Y1BzV2VQcWZQejZMUC9BQXFXUUJzbEFoandmMjFLZ2VjdW1zdy96QWJRWWdTbUNNL3hmUEJlWVhU +NUFVRFJqQ0Z0Njc1K0cvQ3ZsNVFjVXBFTE9SYVE3VEU3VGRmZ1dwSysrMURpRHpFOXhiRDNTSENG +NHhJaC8wWmJ5Rll3L0JoM2JPQ0tVd0hQL0kyeDQ4M1RmVUh1elVGWlFlM1ozaGNPSHFLZTAxenBQ +dXIrZkppdjd6d1V4dURMN201Y0d0TjRlU25zZkFyRGk3Z3hvczhQV3RRbU5xb21xb0hEOTRxQlA3 +RXh4Y2Y2dWJDcTVnNUVBNnZuTmszcHdmbjI1ZUdnNUZQZkpSM2U5ay9iNUhlY3VEd0NWZHJRQWpo +K0JyZTBHdDRGVVB2MmVOdERPejVLLzNESWlmUDhNcVZ2WDArNnoxOTI3eUp4UGZqSSs2TG84LzBu +N3o5d2NLUDUrQzVuRDJMSjc1bmZ1REVEK3o4eWNjbFBpN2xDYXMxUFBqTWoyZCs0VlliYStMalF1 +L0pIWC84aU9NZ3NsZThIdFl1ZXl0UVBxYlA2Uk92MCtmME9YMU9uOVBuOURsOVRwOWZpejVmQkJR +WU9SeDZXNEQ0aVBXS0lEbkVTMXdnRHRsb0ZvNDFOOEkxbkFDQStwejVHeHBBZ1RVbFg1TGhUMXh4 +N0duakVnMWlBZFBraWlGbUUzU1hnQ0ZKeFpjTVh3Z1d3OXNIUlpPVFBTNll2UUhvMFBUczA1eTN4 +cWZZUFZRTVB0akJWM0hDQmFkNDF0Z2dIUmV3OE9renNTUFl3S2tEeUhxYlZYT2wwTWRWMzViSXgz +d0VlWEh3aDJkN2NHS2ZuRnBBYzJ6U1NGY3IrT3g5eG11YWpIMmJpb2o0YUROVVRBNEcrV2xxWXVP +RGY3ekJMbzQ4NFF6V3YvOTU1dHIwL010RERXTjNEYzhHdC9KSU14OFhEa3crMVYrK01CYWZuUGg4 +R3ZyNW4wVTcxT1RBWi9ycWZLaVBmTm5ES1NlMno2RndBcjFZZi8ybnY1VmVUVytkVHo1Z2s1L1ll +dHJYRlhwQWkybWYvaEFEQi9iZ3hCMVBhcXlIWVJYVFBFdyt3OGZHbGZwZUQvS25Oc1hWSGhTTG54 +eEV4eVU3ZU5TbDllUzNBdTNCSis3ME9YMU9uOVBuOURsOVRwL1RwNXpzS1RmVDUvVDVHZlg1SWln +akd6V0pZZ0xKUUdKRTRhZFp2MlZKdzdDVnJIMUk1OHcrQlFFY1NJVXhUN2o4Q0ZxL0VjSE5BNFZF +VFduT0hvME1LTjkrMWJCMWpWQ2h0bkVOaENpV1FsbURsUzhrbENoZlM3QlhvbXc2N1BHVkJBVlRi +QmRzd2Z3KzVBTmovZkpwWFI0d3dLekE3SHkyWC9OcmVMOVdHeGVhVFRFVVNDNTh3TU9XSDd3OXpm +b2NDUHlvQTN3dGxwemhJL3p3ZFB2RTVkKzh4b0lCUmx3YlJGSmVjSlMxRTVHOTdPSFFkSVFFZ3pr +MWRjRGd4SnE5T05aRWNwSUxlM2UrWVNtM252RUVWOGJWVno3aXFxYzgxTVBGWG8zRUVJc05QL3hG +b0xjWEpyM0FyOC9zNUNaZndwS1RPYjd3OE9CNzNsd1JBbDlxSVIrMi9JdlQvcFN2R3VCTmI4UEhw +ajB2cmo1WGJ6WXc0UUorTWZDb0huTERLNHh5VDc3dk5WTS90VGZFZ2l0OFhneDc1TXJlbWp0Y3ph +YzJjcHcrcDgvcGMvcXNYOXhObjlQbjlEbDlUcC9UNTJmWDUvTUQ2Q1VES0ljbUpTaUF4ckt1aWZ6 +RVRwRG1XM2h6eUtrUElJRUdVSUg4aEl4VS9zeHIrQlR4Q2gweWowREV0RUNJa3l6ZmZzb0h2azF0 +cjBUZ014Qm5YbElWc21ieWt6ZHk3WU9YTC9QMmRpQ0tlR0d3bHc4NVBLSjZCdjhWRmV6aUtoaE1M +UUxPNExXM3drV3NZdlR0QUord0s2ZzllSUlWSGppZThmb0k2TmJ4aVR2N2lGV3h4R3pjY3BraTNz +VWZESExRV1BVSEt4eHkwRmpCZGp6Z3BvZURXR29PRDU3VVc3NmFCMmIyWXNBcDErWlUzdmhKUXgx +MkhEengzM0pBOEcrZEg5Z2Q3c1NsSCtBVDIxcjVsUnZSdXV6MWh0Qzh6L3kzY2VGWDF5OEN1VG5Z +REp6Q3BULzFrcnp4NDdtMWhCRXVRbXorZmNZM3JIeXlaOXUzWWpEQUt5YXUyZ040eGxmM2lBR2Jl +bFNnOXVsVlBZVlQ5UlViRnpqbnIxejI0bGY4YW12Nm5ENm56K2x6K3B3K3A4L3AwNzdwYy9yODdQ +cDhzVmtoSmNrQkVvRWxKRTRaQWNDUlp3R0FhRkNFQ29BMCs1R0VSSUN0MmErNUpXUVAwQzVrSUs2 +SGdNL2VyUER2TTk4SWtJUm5ObXdscmduNFFLYkdZNk1RRmEvaTFLOThFditJOVpPK0lUZGt3cW9S +eEVFUXJCMndJeHBaOW5ZOUI4SGx4aWNiellzdi92REFwNnNESjdEQnlzWmY2N2NnVDd6WDJQa3NE +dzJoR2ZpUW0wS0Y3MnMyYzNMQ0pmN2M0Vkh3Tm15SGZVUmhYVnkrK2ZYY3cwWWVPTUhwUjR6VzVK +MEQ0dXBCSkVUbk12RGx1K0JwMUl2SnZnS05XSzZoMnd0OHkxVTl4WERBUE9NdGU4UnJmK2d6ZGJX +MzN6V0h3U1hIaWtCOVlmQU1wem9ZUG52VHBlWnlFUmUrNXV5enUvNkVwWWNVdS9ZOG52bTFoeitI +RWp4eUZKTU4rL0I1M1BUd2dsR085c21iTGYrRytva0pKenM4eVJXVy9uc1QrYnZVWEZ6eDJFMmYw +K2YwT1gyS1BYMU9uOVBuOURsOVRwOWZpejVmYlBhbXh3WmdPSklnSW0xK0NIMU5RdXpjMmRnbktl +QVV3WnE3QkJHaDBmaHJNTGJXRUcxWWw0Uy9pblluenAvOCtiOU9Fd0hNdndLNnhBTWVMdXNHUHlX +RGJ5SVdUeDQrS3lMQ0ZOU2xZSnJDUUp5RzdtRkR6UFlvUkVhRS9Qci9pRVRSMkxlUVByTlhZQmps +NWdCcWJFMXFhR3ByaWlHbTczQ3p3ZEhUV0hmWUhHYUZreU5zUHR2REwvNzU5WXluSGhyd3lBa2VU +ZTBPNnhQMDUvR0ZVejd3eEk5OFlNTmhtNG90L3ppeVIzM3RFU041WHB3STlPcmxZcTl4K0xFUE56 +aVFqN3NhNEkvdkNsaHNmdUgzT1g1dWlJRUxmMEJZVTFNTno0ZStnQmZQWXJqMGwxejBBejlFUWl6 +eU45akRoQXY3MVJaVzlZV2xPYnJyVno3eHdBZWY4TUFyYi9iMndnS1RPYkhOV1JNSFh0ejBqamUr +K29jTC8vcW8vdmx4d1dBTnQzTFhHL2lDWDN3NVcyTTNmVTZmL0UrZjA2ZTE2WFA2bkQ2bnorbHor +dndhOVBraVNVYUlGMFJ3Q1dvSTg0cUpVTThhQXRHQUlndTVpc0VHS0NDQVFZdzVEU3hwbDRUc2t6 +QlNnTkJRQ2cyRC9VZ0ZyUGFhRVJaZlpVQ011UXBVVVdBUkMzbnM0RGNrQ2J2Q2xEU3hrV1h3RDZz +YzJqVHNuOFo1YkloSGZOaVFWOUtRS0E0OENpTWZNWkJwajV5OHBhaEEyWHBUb3dqaStiY0djUEda +QStINDQwY2N1QWxhYm5LVm0zaDRyeGo1a0M5L2NvS3JlRndkZUxKZm51b2tGemh4ekFkODZtRHdr +Nlo3UHhUc1k2K1pOSko0OG53T3NLZmh2TUdCeHhvKzJhZFhEcjloRHFjOVZHQVFoMjl4SFVyeUZa +Tmc3TVdEM09YamY2d0xFejhWcVp6WnE1ZjUxTyt3UExpZU4wN2lpS0dmeE9VVDMvakVJZDlzck9l +QXVuM1c3R1hMbnpwN1ZoUDFnVlY4ZjBDb3MvM3B4ZmVlNkRxZnNJbWpidWJzNTFNdWNoUmZIdkM3 +MjhPbjJvbnBzdVlaUDlQbjlNblA5RGw5MmpkOVRwL1Q1L1E1ZlU2ZjRuNTJmYjVJZ3NObnZLYUFp +QlRJVDlFQ0lNQUdZQVJLRWdldVRhOElQdk1qS1FRcG5JUUYxN1FTUXE0Q0ZQVEh5NXBtc3MrVjV4 +TWdmQWlXTFBEZVFoajh3TkxHWkE4VHNwQ2c2QzV4RlRKa1p6d05KaFlDNEVPVTcrVGIyNkY0NGx1 +RG42MDRjbXd6d1VLVXVITHhXMUdJWTlqLzAyLytmYkRLUldNckNwLzhzT09EZno2SXdoby9udkdx +SmhWWjZuRDdjQVM3eithS1U4TVp1TUNYZldMejQ2RHpGUW4yQklwRGdwS24vTjN4S0VleDhhaFp4 +SUtUU04zaGNBanhMNzVZY3YveWh1Z0dMdkd2TjNBUGcxckk4emtZbnUveE55OFgvRER4OHo5Ly9M +dkJnWTllMXVIalEwMWg3QjU1bTVObmMzZUF3RUZZM25hMjhmMmhnRHU4dzlLRDNqNTczR0Vsc3Ra +SG5uS0F6VnJ6NVJOUDl1alQ1aW9mNi9iWFhnNjRoRnZ1L0l2dERnOGJlOXV2OHVSbitwdytwOC9w +RTdmVDUvUTVmVTZmMCtmMCtUWG84OFVIamlXcDZUaERqQVI5Zjl5Y1lnS29hUkhFeGo0T0RYZUJK +Q2U0Qm14U2ZDSEZ2dGlmTHdNUWx6bXhmdmJUUHc1eENOVnMzaTVvOU5wSmlBL2lOaW9nTVpETGh6 +bUhDTUw0Ulo0R1VhQTJyd2JpVnlFclRuZTVQbzN6RERqa0UvSGNPdElRQnBjOTRpSzhCd1VPTmFU +Q3dQV010MkRqeDkxZS96aWNML2s4Qlh3THgzaVRRL0VRazN4aEpRYWM4bXV2MlBiTHRRZUZldGhi +Z2FaV1Z5ZTV1L01QSTEvMnlmWEo5eTI0TmJCYStXd1A3dVRIVml4MXEvakVrZ3UvN0RTbkp1TXZO +bWNMRTEvbFdEM1l3UFc4aVhzT1dYVzM3bEkzUHZqenE2U3QrY01CanRiMm8ramR4VmNqZWVBR0pu +WnEwdnJKRzdmOHN1RkRQL0FMcXoyNDdnSEFwLzVnUzJBd1dCT1B2L2FQWHVUVG1qcUlvZjdlQ0tw +Myt3bFhjbmNZeWxFdDFCQW43RDNyTFRhd3VZdkY3L1E1ZmNJOWZVNmZzWjgrNDJ2Nm5ENXhNbjFP +bis3VDUrZlU1NHRGUDZVRElqakhKVUV4TmFva0VFUm83Q1VPWkl0aURtQ2tTVXF4a2E4NGZBc0Vn +RFhGc1FjSi9HazZlMzAvSHBFdzhLZEJlMUJvemk4NEkvRFg0TEVYeVhBb0pyTDR0V1pPZkhsNEs0 +T1FqQ08xNG15VCt5eEdHeWN4emc0WEZUc2JlSDAyNTFJNC9NRHBrcis0dU9sSUk5MmVDa1Zzeldq +dkk2Z2JGd3VuTG43NWdFZSs5aW13UXZhdzY5c2hGeUdVYTJ1NE1Yclk4bU12YmpTZmc4aGRiQTFq +aU1PM2VYdmdUODNPcDMxeUpMd2MxamR3cWduVml4ODJCQ0duY0hqOHFZUFllTWV4MmxlRThCcXdl +d3VwTi9qTWdYZXgxTnFiTFB2MWd6ckJZbS83Z24vYzJpc1BGeC8ybXRlRCtvTWREbjFXQnpZKzR4 +ZzJtUGxPWGE5djVBR3JHT2IxS2t6NENJK1hpejlJckt1RGVYbmdCemFjc09NZmRrTnZlc1lMRHZt +SVFBOEx2c1cwUjcvaHZqb1MvMHZmVDUvVDUvUTVmVTZmMCtmME9YM2VYSGkvUEtiUDZmT3o2dk5G +VXlEaWFmem5meGpiUnZPUGZKR0dkRTZKaFZQQU9PSVFNWWdYd0RyQUJCNGdpdk11R3Z1czhlVkNq +T2NXNkpzZmZEZC9sUzlaRGFrQnJNUG0yV2RZbi9FYTBzU0JTMnpOcFVINWdnY1doVWp4ajN3WTVJ +aklrSC96OWlKQnJraVhhLzNqUW41OGllMk9OR1NtYWEvSkZFQWU5c0lyam5VTllWanpyR25ZS0FZ +eHNGTjRQQm93bWJNdWpyaUVYSEh5QWE4Y1laWW5HMzd4ckdibXdtOUU5NWFtYnJ6YXVtc2l0Y01C +cmcyK095OXVjc1ROemVOTkhqQVdyN3JDZ3VNS1VKMnNzOE16am1DM0h6NzMyTjcxVWFCeTVLZDQx +QlVHWDh1QVE2L0lCZmY4RUo0OS9LZ2RqUEkxMk1oWi83WVh4U0lRd3JHT0o3SGtDaVAvN1BXbit0 +dmZBNGQvZUhCcXJyNzdCNWpZRWZiaEVnY0hlaEZlWEJPOElSNHNhbTR0TmJzMVB1RDZjbmpjQlJl +LzdOVnMrcHcrK1o0K3A4LzB6L1FadStseitwdytwOC9wODNQck16K0FjcG9rLys1dnMrQ1pVeits +Q3d5Z2VZNDBvZ0JOMmtBKzRKb0xBSGRGVWpEQkVPNlNwT1lIbEwwQzl1Mk5XRzFBQlNoQjdtS0pi +MDhPa3J0Z2dwMk5Za2hPYkRFNng0ODlIOGtpS2xpUUl4YXNNQ0s5alpNWWg1TWZOb3Frc0h6anha +eExIaG9TcWNXdlNONnNHUXJuMExGWHJuQm9NSmpzNVY4c2pZSER4ckR1MEdnZWVOY2M2cElHUHp5 +NGpmMTd3OHZCOVFqMCtUWFYrQ0ZRbVBFRGcrK2VWL1FSMCtXcGNjMnhTZk5lUEg3TEo0SG1rRTc5 +M3RJNE9Hd3pxNDNjNE1DdjNQQ3RiakREeTY2aUtzOEVDWjk0OHRSLy9QdU1KL2Y2a3FzNHVGVlht +UEhVWHJOUC92aUdUZTR3d1VQczZtSU9yK2IxTHA5RzgrSGZYWCt6OVJ5c2h3c1g4clZmVG56QUVK +dkxUeTQ0WlNNZlBPTE00Tk8rMWswKzZxMWZYR3paeU5kblB2VVhYSEJNbjlQbjlEbDlQdldkUG1H +YVBxZlA2WFA2bkQ0L3R6NWZFR2F6UkNRcWdFUUJiS01wa09BMkkwZGg4MGJoZ0NEUVp3MHZDY0Fq +cEJ1QThHT3ZmZUpVb01pUjZQLys1dCtsU1A3cVhGenppbU9QWjZCOUR1ZzBpUEY4bmFKRklDNkpp +cGRENFhKU0VFMG5IK3ZtN2N0YmdIZFNGQmNKY0xITElYVURzZXo1VHR4cnJKSUtpelZ6aW9JYjVH +b0NQdnUyeUlCWDNzaDJKMlFIa3NLS3dWOGE4SVJzVHI0K3kxbGNYUENObnhaUWJFMkRhK3YyZVpa +RCtFMnpQckVKVHU2NFlLZE8vZFhWdUhzRThkaVdmOXpCaGFjS0F5WTROYTZCRjdYREkzczhxbmtF +Zno0MUlueEVJQlo4OWExUCtJT1Q3N3c1MDZqSGYzcndCanZ6N1VzNXd5SUhvcGVUM1BoaUM1dmFz +Y3RYTU02WG5ILzY3ZmVDbVIvY3c0QlhlTzFsRDdOMWRZVkhEY1hDdDJjSG5scUpMUjk1NjVrS1ZD +NTh5Tm04T2Z0Z3FFRFZEOTV5aUJ2N1lPQVBSOG5uTHZ2NkIwQTVLdy9UNS9TSnUrbHorcFREOURs +OVRwL1Q1L1E1Zlg1V2ZiNEFWR2NhcmdrUW42WVNHRWtTQmtwaE9kZG9BQmtLRDVSTGN5S2NQNFVY +QkdnRXBTbU95TXdmc1pyQUljRGVyMitXZ09iZ0EySHMrUlFYZUlsMUVJWjU1RXBRZy9BcFljUkwz +b1Zvc2MySmJaOTUvc1d5bjI5dkZrcW93WjlHS0ZuMm03T1hEd0oxUnpqOGlnY2ZYd3BpV0hkSTRJ +Q0FmL0tqMzhtem5PU2VmSzVSOFFzN0xEaVFqOWp3eWRHQmhXOSs3Uk83QndkTTdOUWk5WWhBbjdk +Yjl0YVhaeGpGdDBkOTFkdlFrR3BkZS81Yy9MdkVoQ3NDdklFN3ZQS0hEL1dFcmZWaGE0NHZ0Y2ZS +UjNHSXEvSE5xWFh6RUlmQThhQWU2bzlEM0JNNTN2UXJuUHphNDk2NjhXK2ZmbWFITjFqRU1nZHpo +SEUxSXVUbndIbmVTRmtUQnkvOHFLa2NEUHZMR1V3K3UvakJVL3djYmp5YWx5T09ldURBWWw1ZDBq +dlhUL2FZRTV0dCsxWC80a085K0prK3A4L3BjL3FjUHFmUDZYUDZuRDZuejY5Sm55K0s1d01oQXU5 +ek4vcHBuRlBPRkVNUmtJTllvbXhEdEpFUkpYSEFEUWtpeGg2TklmZy9uUGRic3lTdFVIekJnQncr +WVVBVzRBZ1VvMFBCNElGYlFmbmlFL1lLQlJrSzZIQkJucUU1MlBHdDRJaUFMZkUvRExraFZrdytl +bVh1Q0xTWEx6SDQ4MTMvNHFoQUZVaFI1V3Y5MngvK2VyQW9xdjF5a3djLzRuZzJyTU12WGc5S0dQ +a1Z6enBiZWNwWFkvRW5kc2J4S0tiY0hFN2VsQlNYTjNMbTVLKzJtZytHenFtRmZOUktuU3NPdFgw +YTdqVTl3bDR1YlNqK0krQnJWUEhUSTRkWkRzMVhqbkMxRnJCN0UyUi9Eejc3NWE0ZTdyaVJwM3NQ +SWI1Z01KKyt1eHdNYXovKy9uZkNseDdDSGRFM2Zucm9ZckZURC9QOUE0Tkk0S29lMmtkeWtUdStj +U05mL0xRZjJOQ0gzTzNCaTNYK2tzK05jSG0xd0IwdVlkRG45ck1Wczd6bllMaWNySC9FTTMxT245 +UG45UGtQNTZmUDZYUDZuRDZueituek0rcnp4V1NMQUpoRmdTUWppRUVFaW11ZUk4UWhYME1hQUhD +SUtBMnNPUDNKSExHQThQZVJFQVVoZW05TytQTWRjNlBGTW9jVWU5bzQyWCtFdVdDQ0hUR1M1eE0r +ZXpRTFRQWWdqQzk0N05Gay9OdURYT1Q0L0dCN2ZzT1dnUlA0RkZadWlYZmt5VVBSemZPbE9IakRG +Yjl5TFMvcytERmdrYXMzWXJDbVdTOVh4VmNJVGNPM092RGJPdUJkM01hR28velo1K0lISG5tMldX +SEdneVlWMDE2WGd4VlduT0lrNDBUaHVUN2w0RE11Y1FOUGhXeUl4MWRyTFVkODE4NkZFL3pWajJl +ZllZTGZrTE0rRXNObmZLa3QvZ2dVanVSNzhjU1FuNWpxem8vWTlqVVBuSGxESks2dlloQy8vb0RW +WGhqVmkzOXZqK0JTYy83emg4emQxUW1YYlBRSDMvS0MzMXA3U216NHpQT3BqdFdIdmVMcGZVTmNh +MnprZytNZXluS1hpNXphczN6RHdtNzZuRDZueittVHYrbHorcHcrcDgvcGMvcjhXdlQ1SWdoRFEx +R0I4aE4yaFpQNU01UTBNQWhDdExjREhCc2Nzd1hVdW5sZ2lORmVnQVVGU3ZIU29BZUFENWQ0Q0RM +NFFTZ2ZDdWFuYkdzU2JMUHppemgrcmJjQjdJRmZNMnZFTnJaNU9TSUgyV0tZZzBsaktqSThYOFkx +ck1MS2hWK3hGSTBQZk1FVnpxNlo1R1EvZjBTT2NId1kxdXlYczZMNkI5TEVZaDVYN2hyRUhaWWVD +UEwwTEdkaWN1RkFMbXpGZ1lPdDNHRTN4MTZPalkxSFdQREV0M3k4OVhOcGhFZHdiOWtESHk0YUcw +ZGl5QzArajVQNmxqdGZmTWlQalZoNGlVQ3Y5akNWZXpqWjZSc1hyUGkxcHY3bXhFeWozMzdONmlz +WjV0U0xQZC9zSFZqNGtCZDhiR0F6eE5DM2FxRCszZ0xaRDBlLzRnRTczTFVURDRmNHdWL3JDRk9G +cC81OFIrZzN6eDh1K0JJL2Z6aTlDOVIrZkxqc1YxOVlQWXVGYzN6b1d4ejViRThPK09QY0JaYzY5 +TEJNcjkyWVB0L0g5Qm1PcHMvcGMvcWNQczJKT1gxT245UG45UG1aOVBraW1FUWxJbGpCUzA1aEFU +SHZUWU5BN2hKUVVNNlFvMWdBc1BPVkE1OE5RaFFZQUExV2dSR1JLeUs2WXRnVG9tOW9rZ0JGeEsx +SkFFYnhBTmRRaEFHdk9HS1lWMEQyU091QmdqQ2t5ZytKQ0dGcm56ekZZS05oeGVqZ1cxNzh5UTBI +N1BCaEgxL3c1aEE2Tzd5d3pmejVmM0FlelBlaXdhekFmLzJudjVVOEZNNEZwejM4ZXc3K3c0Z3Zl +Y0dIUHpITmlXTyt3dVN6dWZHbExwckF3TFdEVHp5NHZZbmhTNjdsT3dmRWNVa3NhaUhYY3VLdURt +TFVya0tRSDE3ZDRZR1BiMDNYK3FnSHZPb0pyNnUrN2NOeFJIdzV3eUtmSGdEMndDa24yTVJ3aDBk +ZHJjdUp6eHlVNzdqa25PL0k2NWZyVXpuYko1N1A1c3VyTjFEMkczQVFMeTd4TEdjNGlZazQ1UU9M +UE8yUkY4N1oyeXNYRjN6eVVZZmFHRDZMV2E3bGl0UDA0K1VPVjNYUytQanJIemJUNS9RNWZVNmYw +K2YweWJkOU9KYXJuR0daUHFmUDhIRzIwK2YwK1RuMCtYOSs4WDhCN1VFajViU2JqcGdBQUFBQVNV +Vk9SSzVDWUlJPSNLR2xEYjJSbExsRlVMbFJoWWxScGNHOXpDa1pzWldOb1lRcHdNUW9vWkhBeUNs +TW5iM0JoWTJsa1lXUW5DbkF6Q2tZeENuTlRKMlp2fGNtMWhKd3B3TkFwVEp6TW5Dbk5USjJOdmJH +OXlhVzUwWlhKcGIzSXlKd3B3TlFwSkxURUtjMU1uWVc1amFHOURZWE5wYkd4aEp3cHd8TmdwSk16 +WUtjMU1uZEdsd2J5Y0tjRGNLU1RFS2MxTW5ZVzVqYUc4bkNuQTRDa2sxQ25OVEoyTnZiRzl5Sndw +d09RcE1OREk0TWpFMnxPRGt4T1V3S2MxTW5ZVEZvT0NjS2NERXdDbE1uWkRKa05pY0tjREV4Q25O +VEozWjFaV3h2Sndwd01USUtTVFFLYzFNbmNHOXphV05wfGIyNG5DbkF4TXdvb2FVTnZaR1V1VVZR +dVZHRmlWR2x3YjNNS1VHOXphV05wYjI0S2NERTBDaWhrY0RFMUNsTW5ZV3gwYnljS2NERTJ8Q2tr +eE5ncHpVeWR2Y21SbGJpY0tjREUzQ2trNENuTm5PQXBKTVRJNENuTlRKMkZ1WjNWc2J5Y0tjREU0 +Q2trd0NuTlRKM2tuQ2treHxOZ3B6VXlkNEp3cEpNQXB6WW5OVEoyUmxjM1JwYm04bkNuQXhPUXBU +SjIwbkNuTlRKM0psWkc5dVpHVnZjeWNLY0RJd0Nra3dNQXB6fFV5ZGhiSFJ2WTJGaVpYcGhKd3B3 +TWpFS1NUSXlDbk5USjJ4cFZtRnljeWNLY0RJeUNpaHNjREl6Q2loVEozQnZjMmxqYVc5dUp3cFR8 +SjI4bkNuUndNalFLWVNoVEoyRXhhRGduQ2xNbll5Y0tkSEF5TlFwaEtGTW5aM0p2YzI5eUp3cFRK +MjRuQ25Sd01qWUtZU2hUSjJGc3xkRzlqWVdKbGVtRW5DbE1uYmljS2RIQXlOd3BoS0ZNbmRHbHdi +eWNLVXlkdUp3cDBjREk0Q21Fb1V5ZGtaWE4wYVc1dkp3cFRKM1FufENuUndNamtLWVNoVEoyRnVZ +Mmh2UTJGemFXeHNZU2NLVXlkdUp3cDBjRE13Q21Fb1V5ZGpiMnh2Y2ljS1V5ZHVKd3AwY0RNeENt +RW98VXlkamIyeHZjbWx1ZEdWeWFXOXlKd3BUSjI0bkNuUndNeklLWVNoVEoyTnZiRzl5YVc1MFpY +SnBiM0l5SndwVEoyNG5DblJ3TXpNS3xZU2hUSjI5d1lXTnBaR0ZrSndwVEoyNG5DblJ3TXpRS1lT +aFRKM0psWkc5dVpHVnZjeWNLVXlkc0p3cDBjRE0xQ21Fb1V5ZG1iM0p0fFlTY0tVeWQwSndwMGNE +TTJDbUVvVXlkaGJtTm9ieWNLVXlkdUp3cDBjRE0zQ21Fb1V5ZDJkV1ZzYnljS1V5ZHVKd3AwY0RN +NENtRW98VXlka1pYTmpkV1ZzWjNWbEp3cFRKMjRuQ25Sd016a0tZU2hUSjNCdVp5Y0tVeWRqSndw +MGNEUXdDbUZ6VXlkamIyeHZjbWx1ZEdWeXxhVzl5Sndwd05ERUtURFF5T0RJMU5qWTBPVGRNQ25O +VEoyUmxjMk4xWld4bmRXVW5DbkEwTWdwSk5ncHpVeWR6YVUxdmRtbGliR1VufENuQTBNd3BKTURF +S2MxTW5aM0p2YzI5eUp3cHdORFFLU1RFS2MxTW5jRzVuSndwd05EVUtVeWRjZURnNVVFNUhYSEpj +Ymx4NE1XRmN8Ymx4NE1EQmNlREF3WEhnd01GeHlTVWhFVWx4NE1EQmNlREF3WEhnd01DQmNlREF3 +WEhnd01GeDRNREFnWEhnd09GeDRNRFpjZURBd3xYSGd3TUZ4NE1EQnplbnBjZUdZMFhIZ3dNRng0 +TURCY2VEQXdYSGd3TkhOQ1NWUmNlREE0WEhnd09GeDRNRGhjZURBNGZGeDRNRGhrfFhIZzRPRng0 +TURCY2VEQXdYSGd3TUZ4MGNFaFpjMXg0TURCY2VEQXdYSGd3WlZ4NFl6UmNlREF3WEhnd01GeDRN +R1ZjZUdNMFhIZ3d8TVZ4NE9UVXJYSGd3WlZ4NE1XSmNlREF3WEhnd01GeDRNREpjZURoalNVUkJW +RmhjZURnMVhIaGxaRng0T1ROZFMxeDRPVFJCWEhneHxORng0WXpkY2VHWm1aMXg0T1dWY2VERTNM +Vng0WTJSY2VHUmlYSGc0WVZ4NE9EZ3VYSGhoTWx4NFlUQmNlR0V3SUZ4NE1EaGNlR0pofGNGeDRZ +V1JjZUdKaWNGeDRZV1JjZUdWaWRseDRZVEUrUUY5alhIaGlaa3RjZURrNFhIaGxZbHg0T1RKaFhI +aGtPVng0WldWTlJWeDR8WVRaY2VHRmlSaTljZURFMFhIZzVZVVpjZUdWalJpRlVJRmhHYmx4NFpq +bGNlR05qYzF4NFkyVmNlR0ptWEhnd1lseDRPRE5jZUdSbHxiRzFjZURnelhpNWNlR1kyWEhnd056 +TTNaMXg0WlRaY2VHTmpYSGhsWmx4NE9XTmNlRGs1WEhnd01WeDRaR0ZjZUdJMGFWeDRaRE5jfGVH +VTJYSGd4WmlOY2VHSm1YSGhpWVZ4NFlqQmNlR0ZtY25aY0oxeDRaVEJjZUdObVhIZ3hNMXg0T1Ro +Y2VEazRYSGhqWTF4NE1HVmZ8WEhobVpseDRaV0pjZURBeVhIZ3dNRng0WkRCYlhIaGpPVDkyWEhn +NE1seDRaR1JtWEhoa1kxeDRaVEpjZURnMFhIaGxNMFpjZURCbHxYRzV3ZFhKY2VHRXdYSGhpTkZ4 +NFpqUlhYSGd3TkZ4NE1EQWdVMXg0WTJWWFhIaGpNMXg0WVdWY2VHVTRLRVJpVFZ4NFpESlZlbk1r +fFhIZ3hObHg0TURWY2VERmpYSGd4Tmx4NFpURmNlR1UxWEhnNE9WeDRaV05jZUdZd1hIZzRNejhx +WEhnd01GeDRNREI5WEhnNU5WeDR8WkdOZFhIaGtOMXg0WkRWeE9GeDRPRGhjZURneVhIZ3hPRng0 +TURCY2VHRTRYRzVkWEhoa00xeDRaRFJjZURFeVhWeDRNRFZjZERwY3xlRGswWFZ4NE9HRmNlR1V5 +WEhnNFpWeDRPRFpjZURoa1hIZzVOVng0WTJWY2VEazBYSGhpTkZ4NFlUbEFieVZjZUdJM0tDWmNl +R1l6fFhIZ3hNRng0WTJWY2JseDRaamxjZUdNMFhIZzVZMXg0WkRSY2VEbGpYSGhoTUQ1Y2VHUXhY +MXhjWEhoa1kwUmNlR1V5VVZ4NFpEQmN8ZUdRMWNWeDRaREJGUVZ4NFpqaE5YSGhqTUZ4NE1EaGNl +R1l6WEc1Y2VHWTFYSGhsT1Z4NE9HRWxYSGhrTmlOY2VEQmxkMXg0WVRCeXxNVng0TURoNFpUeGJm +Rng0Wm1WY2VEZ3pYSGhqTUZ4NFpqRmNlREZpWjF4NFpUbGNlR0V5WEhnd01GUmNlRGd6WEhoaE9U +VmNlRGt3fFhIaGtNbHg0WkRNc1hIZ3dORng0WkRCY2RGeDRZekZyWEhnNE9EeGNlREV6WWx4NFky +VmNlRGhqWEhobU0xeDRNREJjZUdWaFhIZzR8TVZ4NFlqbGNlR1JoWEhobFpIdGNlR1ppWEhobFlp +aGNlREUwTEZOY2VHTmxMVng0TURSY2VHUmlPbHg0WmpaY2VEQTFZVng0TVRCY3xlR1pqVEZ4NFpE +UmNlR0pqWEhoak1WeDRNVEpjZUdabVVWeDRZbVJjZUdFMVFudGNlREF6WEhobE4wWmNlRGhqWEho +aVl6UmNlRGsxfExVNWNlREF3WEhnNE1GeDRPV05jZUdJNGVWeDRPR1ZjZUdNeFhIZzVObWhjZUdN +elhIaGpaRlJjZURBelhIZzRaRng0WVRCY2VERTV8WEhnNU9GeDRaR0ZIS2x4NFpEZEljekIyWEhn +d00xeDRaakpjZURrMlhIaGpNaVZBWEhobU5rUmJYSGhsTTF4NFl6aGNlR00xWEhoaHxNV3RjZUdR +MlhIaGlNVng0WmpWOFhIZzROQ1ZjZUdSbExGeDRaREYzTkZ4NFpXSmNlREEwWkQ1c1hIaGlaVVZj +ZURBd1hIZ3hNRng0fE1ESmNlREF4UVZ4NE9UQmNlRGd3VUVCK1hIZ3daVng0TURCY2VERXdYSGho +TWx4NE9EVmNlRGszUkVCa2ZYQjlYRzVKWEhoak1seDR8TVRKY2VEQTFYSGhrTldobVhISmNlR0Uw +WEhobE5tbGNlRGhqU1Z4NFl6WWllQ1VpWEhoalpraGNlR05sWEhnNU1XNWNlR014T1Z4NHxZV0o1 +WVgxbVhIaGhNRlJjZURnM1hIZzRNRng0T1RseVhIaG1aVWxjZUdRNFhIZ3hNMXg0T0RjdVhIZ3dZ +MzVhWEhoaVpIbGNlRGcxfEpWeDRabUZjZUdNeGZGeDRZV0ZjZURBeVdTWTRRbHg0WkdGY2VHRTFY +SGhsWVZ4NFlUbGNlR1F5SkZ4NE1EQmNlRGcwVEZ4NFpESmN8ZURFM2ZpMWNlRGxrWEhneE0zSmNl +REUyWEhoak1GTnpVa01vWEhobU5WeDRZVGxjZURrelhIaGpOVng0T1RkY2VHTmthbHg0WXpsVXxY +SGhtTWx4NFlqTlJkMXg0WW1OWFhIaGlaVng0WW1KY2VEZG1YSGd4WVVGY2VHRm1YSGhrTURWZFhI +aGhNVng0WVRZOVhIZ3hNRGN0fFhIaGpNRng0WVRCcU1EWjllbHg0WmpCY2VHTTFSajF1WEhnNU9V +dzVYSGczWmo5Y2VHVmpYSGc0WlZ4NE1HVmNlR0k1S0Z4NE9HTmN8ZURBd1hIZzRNRng0WVRsQlhI +ZzVNMXg0WkRSY2VHUXpXMXg0T0RObVhIZ3hORng0WXpGY2VEazRYSGc1TVZ4NFl6TmNlR1ExTzF4 +NHxNRGRjZUdGbFhIaGhNVkJjZUdJd1pseDRZamxhWEhneE5seDRaVGhjZUdGaVhIaGxOR1pjZUdS +alhIaGtObHg0WmpoY2VEZzRYSGd3fE1GeDRZakVtWEhobVlWeDRPV1ZlUTF4NE1EQmNlR05tWEhn +d01WeDRNVGt5WEhoa00xeDRaREZjZUdFNVhIaGtNMXg0WVRWY2VEZzN8WEhoaFpGeDRaVFJyU1NC +VFhIaGpaUzFjZURnNFlGeDRNVGRjZURnNVRseDRNVEZjZUdSak1GeDRaR0ZjZURrd09seGNYSGc1 +WkZ4NHxaVGt2TFZ4NFlqZGNlRGt5WEhobE4yczJYSGhtT1Z4NE1EVmZPRFpjZURsaFhIaGtabHg0 +WldWY2VEbGpYRnhRV2x4NFlqVTZVRng0fFltTmNlR1kxWEhoaVlseDRNRGRjZUdJMmFWeDRaRE5j +ZUdFMlhIaGpaRng0TjJaY2VHTTNYQ2RjZURsa0puUmNlRGhrWEhneE5WeDR8WW1KY2VEbGhLVng0 +TURCY2VEQXdYSGd3TUZ4NE1EQkpSVTVFWEhoaFpVSmdYSGc0TWljS2NEUTJDbk5pTGc9PXwjS0ds +RGIyUmxMbEZVTGxSaFlsUnBjRzl6Q2tac1pXTm9ZUXB3TVFvb1pIQXlDbE1uYjNCaFkybGtZV1Fu +Q25BekNrWXhDbk5USjJadnxjbTFoSndwd05BcFRKek1uQ25OVEoyTnZiRzl5YVc1MFpYSnBiM0l5 +Sndwd05RcEpMVEVLYzFNbllXNWphRzlEWVhOcGJHeGhKd3B3fE5ncEpNellLYzFNbmRHbHdieWNL +Y0RjS1NURUtjMU1uWVc1amFHOG5DbkE0Q2trMUNuTlRKMk52Ykc5eUp3cHdPUXBNTkRJNE5Ua3p8 +TXpNeE1rd0tjMU1uWVRGb09DY0tjREV3Q2xNblpESmtOaWNLY0RFeENuTlRKM1oxWld4dkp3cHdN +VElLU1RRS2MxTW5jRzl6YVdOcHxiMjRuQ25BeE13b29hVU52WkdVdVVWUXVWR0ZpVkdsd2IzTUtV +Rzl6YVdOcGIyNEtjREUwQ2loa2NERTFDbE1uWVd4MGJ5Y0tjREUyfENra3hOZ3B6VXlkdmNtUmxi +aWNLY0RFM0NrazRDbk5uT0FwSk1USTRDbk5USjJGdVozVnNieWNLY0RFNENra3dDbk5USjNrbkNr +a3h8TmdwelV5ZDRKd3BKTUFwelluTlRKMlJsYzNScGJtOG5DbkF4T1FwVEoyMG5Dbk5USjNKbFpH +OXVaR1Z2Y3ljS2NESXdDa2t3TUFwenxVeWRoYkhSdlkyRmlaWHBoSndwd01qRUtTVEl5Q25OVEoy +eHBWbUZ5Y3ljS2NESXlDaWhzY0RJekNpaFRKM0J2YzJsamFXOXVKd3BUfEoyOG5DblJ3TWpRS1lT +aFRKMkV4YURnbkNsTW5ZeWNLZEhBeU5RcGhLRk1uWjNKdmMyOXlKd3BUSjI0bkNuUndNallLWVNo +VEoyRnN8ZEc5allXSmxlbUVuQ2xNbmJpY0tkSEF5TndwaEtGTW5kR2x3YnljS1V5ZHVKd3AwY0RJ +NENtRW9VeWRrWlhOMGFXNXZKd3BUSjNRbnxDblJ3TWprS1lTaFRKMkZ1WTJodlEyRnphV3hzWVNj +S1V5ZHVKd3AwY0RNd0NtRW9VeWRqYjJ4dmNpY0tVeWR1SndwMGNETXhDbUVvfFV5ZGpiMnh2Y21s +dWRHVnlhVzl5SndwVEoyNG5DblJ3TXpJS1lTaFRKMk52Ykc5eWFXNTBaWEpwYjNJeUp3cFRKMjRu +Q25Sd016TUt8WVNoVEoyOXdZV05wWkdGa0p3cFRKMjRuQ25Sd016UUtZU2hUSjNKbFpHOXVaR1Z2 +Y3ljS1V5ZHNKd3AwY0RNMUNtRW9VeWRtYjNKdHxZU2NLVXlkMEp3cDBjRE0yQ21Fb1V5ZGhibU5v +YnljS1V5ZHVKd3AwY0RNM0NtRW9VeWQyZFdWc2J5Y0tVeWR1SndwMGNETTRDbUVvfFV5ZGtaWE5q +ZFdWc1ozVmxKd3BUSjI0bkNuUndNemtLWVNoVEozQnVaeWNLVXlkakp3cDBjRFF3Q21GelV5ZGpi +Mnh2Y21sdWRHVnl8YVc5eUp3cHdOREVLVERReU9USXlNekk1TmpCTUNuTlRKMlJsYzJOMVpXeG5k +V1VuQ25BME1ncEpOZ3B6VXlkemFVMXZkbWxpYkdVbnxDbkEwTXdwSk1ERUtjMU1uWjNKdmMyOXlK +d3B3TkRRS1NURUtjMU1uY0c1bkp3cHdORFVLVXlKY2VEZzVVRTVIWEhKY2JseDRNV0ZjfGJseDRN +REJjZURBd1hIZ3dNRnh5U1VoRVVseDRNREJjZURBd1hIZ3dNQ0JjZURBd1hIZ3dNRng0TURBZ1hI +Z3dPRng0TURaY2VEQXd8WEhnd01GeDRNREJ6ZW5wY2VHWTBYSGd3TUZ4NE1EQmNlREF3WEhnd05I +TkNTVlJjZURBNFhIZ3dPRng0TURoY2VEQTRmRng0TURoa3xYSGc0T0Z4NE1EQmNlREF3WEhnd01G +eDBjRWhaYzF4NE1EQmNlREF3WEhnd1pWeDRZelJjZURBd1hIZ3dNRng0TUdWY2VHTTBYSGd3fE1W +eDRPVFVyWEhnd1pWeDRNV0pjZURBd1hIZ3dNRng0TURKY2VEaGxTVVJCVkZoY2VEZzFYSGhsWkZ4 +NE9UUmRTMXg0T1RSQlhIZ3h8TkZ4NFl6ZGNlRGRtTTF4NFptSmNlR1V5Ymx4NE9EVmNlRGcwWEho +aE9TRmNlREUyWEhnd05WeDRNVEZjZUdJNFVsRWdKRng0WmpSQ3xkVng0WkRsY2VHVTNYSGhsT0N0 +MFVWeDRZVEpjZUdVeFZWeDRPVGhjZUdKa2ZGeDRPREp1WEhoalpGeDRNR05jZURoa1hIaGtNbHg0 +fFltRmNlREJpWEhnNFlWeDRZakpjZURFM1hIaGhNaWcwWEhoa04xeDRaRFZkZDNVclhIZ3dOVng0 +WXpOV1hIaGtaRng0WlRkWmZWeDR8WlRaY2VEazVaMXg0WW1GY2VHUTRYSGhqTlY1a1hIZ3hOMWQ2 +WEhoaVlseDRaRGhjZURGbVhIZ3dZMXg0WXpOY2VEQmpYSGhsTjF4NHxPV05jZUdZNVhIZzVabk5t +WEhnd05pcFVYSGhoT0ZCY2VHVXhYSGd4Wmx4NFpUTmNlR1JpWEhoaE9GeDRaVEZjZUdNMUpseDRN +V0ZjfGVEaGxYSGhsWkZ4NFpUQjhhMXh5WEhobVpWeDRaVGRjZURCaVRGeDRabVF1WEhnd01WeDRZ +VEpjZURGalhIaGxNMXg0WWpZbVBseDR8T0RSY2VHWmpYSGhsWTF4NFkyVXFYSGhqTWxWY2VEZ3lK +MXg0WVdKY2VEZzJYSGd4WlZ4NFlUbGNlREU1WEhoaVkxeDRNV05uWEhobXxaVng0WVdaY2VEQTRY +SGd3TUZ4NFpUaGNlRGc0TUZ4NFpHTmFRMXg0T0dKY2VEa3dYSGd3TkZOY2VERTJWbHg0WkdGR1Nr +RmNlR1JofE5WeDRaalJwWEhoaU9GeDRaR0k5WEhoalpWeDRaR0kvS2x4NFlUQWdYSGhsTWx4NFpq +VmNlR1l4T2x4NE1HVmNlR1ZrWEc1Y2VERXp8WEhnd05GeDRaamhjZUdJeVhHNHpObTVjZUdReVhI +aGpNbHg0WWpJMVhIaG1PRng0TURSY2VEQm1YSGc1TlZ4NFpUWmNlR1kyWEhoa3xORng0TURSRGQw +QmNlRGszWEhneE5GeDRaREJjZUdRMlJGcGNibHg0WXpaY2VEazBZVVI3WEhnNFkwdEJYRnhjZUdJ +NUpGeDRZV1VufFNGeDRPVGR5YkZ4NE9HWmNlR1l3WEhobVpWUmNlREZrWEhoalpGeHlZVng0Wm1N +L1hIaGxaVng0WlRjMFhIaGhORzFJV2l4Y2VHTmx8WEhoaFpWQmNlREZrWEhobU1seDRaakZjZUdO +aE1WeDRaR015WEhneFpWeDRaamRjZUdKaVlseDRZVFJjZUdRMlhIUmNlR0k0WEhoa3xPVng0T0RK +cFhIZ3dPRng0WXpGY2VHSXlYSGd3WWx4NE1UbGNlRGcxWEhnNVpIRlFXVng0TVRkY2VHSm1YSGhs +TTF4NE1URmNia2hjfGVHVTJYSGhoTkdGY2VHTmhYSGc0TlZGY2VERTNYSGhqTmx4NFpqQklYSGd4 +T0Z4NE1XWmNlR1l4TFZGY2VERXlYSGc1WkZ4NFpUQjF8UkZ4NE9EaGNlRGxsWEhoaE9XZGNlRGRt +ZlZ4NFlUaGNlR1k0WlZ4NFptVmNlRGswWEhnNE0xeDRPVFJjZUdNMVhIaGpZVng0T0dOY3xlRGhr +YTF4NE1HTmZQVng0TVRoUVhIZzVZVng0Wm1WY2VHVmxTVng0T1dWY2VEQXlYSGc0T0Z4NE9XVmNl +R0V6WEhnNU9GeDRZek5jfGVHUmlYSGhrTnp0NlhIaGpNRng0WWpKY2VEZ3lKVGMvWjF4NE1XTldY +SGd4Tmx4NE1UVmNlR0ZpU3k0eVhIaGhOMXg0WkRsY2VHVTJ8WEhnNE0xeDRPR05jZURrd1hIaGpZ +M3RjZURnMlBTZHJYSFJjZUdWalhIaGtaRng0T0dFc1ZURmNlRGd3WEhnNFkxeDRPREpjZUdJMHxY +SGc0WkZ4NE9UZGNlR0kwV0Z4NFkyRW9Ra0l3Vmx4NFpEUmNlR001WEhneE9EQmNlREF3WEhnNE5Y +bHRYSGc1T0h4Y2VHVTFYSGc0fE5GeDRZekJjZURnNFhIaGxabHg0WldKY2NseDRZakZjZURFMlhI +aGhabHg0TVRCY2VEQXpYSGd3Wmx4NFpURmNlR0kzTlNSY2VHSXp8WEhoaU1GeDRZVFF4WEhneE9W +eDRNRGQ3VVZ4NFlURmNlRGsyWEhneE5VRmxYSGd3T0Z4NE1EWmNlREExWEhoaU0xSmNlR1UwVzF4 +NHxZVEExVVNOY2VEZzRTMXg0TVdaY2VEZzVLekZjZURFeVhIZzRNR2xjZURobU1INWNlR0kyWEhn +d01WeDROMlpkVlZ4NFpqRmNlR1ZqfFhIZ3dZaTFjZUdNNFhIaGpaRmhvWEhnd00xeDRNR0pHTUZ4 +NFpUQnFYSGhtWVZ4NFltSW1lRng0TURaY2VHVXdYSGc1Wmx4NFkyRXl8WEhnNVpGeDRZakpjZURF +NWRUVWpYSGd4WlV4Y2VERTRYSGc0WmpoY2VEa3lSSGRjZURoalhIaGpabHg0WVRWY2VHSXlhVzlt +WEhobHxOSFF0WEhobVlseDRaV0ZjZUdGaGZseDRaV1ZjZUdKbVhIaGxOVng0WXpKY2VHTTNYSGd4 +TlVndVhIaGlNemhjZUdJM1NuVmNlRGsxfFhIaGxORng0T0RWalhIaGxPSEZjZURBMVExeDRaRGRj +ZUdFeVRGeDRabVpjZURGaFozTmNlR05tWEhoaU1GeDRPVGszSjJvNVhIaGt8T0Z4NE1UZ21YSGd3 +TURCY2VEbG1YSGczWmx4NE9EWXFaV0ZjZUdVM05GeDRZelpjZURBM1ExeDRPR1ZjZUdFeEwxeDRN +VFJjZUdVenxRV2RjZUdabE9seDRNVFZjZUdFMWJGeDRNREZjZURrM1hIZzVZWGxjZUdRNVdseDRZ +ek5jZURFeFhIZ3dNMXg0WXpGcFhIZzVZbXhjfGVHUmhYSGhqTmk4aFhIaGxOVUZjZUdGbVhIaGhN +MXg0WWpsM2RWeDRPVEozWEhobE5WeDRZelFyWEhobFppdGNlRGhsWEhneE1GeHl8U2x4NE1XRnRS +U2hjZUdVd1hIaGxNMXg0T1RGY2VHVXpYSGhrTWx4NFlXSmNlREF5WEhnd1kxeDRaR1ZjZURFNFkx +eDRZVEZjZUdKanxORFloWEhobE1GeDRZekpjZURBeGRseDRNV0ZjZUdNMU9WeDRNRE5jZUdNelhU +RmNlREZsYjF4NFpqWmNlR013WEc1Y2VERTFLbFJjfGVHWTRYSGhsWmx4NFpqaGNlREEyWEhobE55 +azJYSGhoTTI1Y2VHWTBYSGd4T0Z4NFpqQmNlREF3WEhnd01GeDRNREJjZURBd1NVVk98UkZ4NFlX +VkNZRng0T0RJaUNuQTBOZ3B6WWk0PXwjaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUVBQUFBQkFD +QUlBQUFBbEMrYUpBQUFBQTNOQ1NWUUlDQWpiNFUvZ0FBQUFDWEJJV1hNQUFBN0VBQUFPeEFHVkt3 +NGJBQUFNWlVsRVFWUm9nZFZhN1c4YzVSR2ZlWjUxSE1kbngrVmlPdzZVeHVlN3N4TUthb3NFZ2xM +MVE2Q0pFdjZOZkFrUy9DOGtFbjlHWHhRNUFkRUtRcU1XQ1ZVcWlQcnRqQU5OU1B3YUhML2J0OC8w +dyt6T3pqTjdUa1BWcXUwcWl1OTJaMmQvOC9aN1p1Y2V2UHZOblNQZFIrSC84OWpmMjAyT2RCL3RQ +dHJ6MzBieXJ4OEovN2wzLzk3ZXpnWUJBQkFBSWdJRkFvUzFoVHNBQkFBSVNFQUFRRVNWb2FIdVNo +OEFBQkFpQWdFUjdXOXZieXcrUUVRRUFCWW1JS0xxMkZnbUNRakFaMkZ6YVdsL2F3dXdVQXNFWGIy +OS9jUEQyVmZCQUxDMnNBQUlRSUJZWUJnODNSeXROd29EZHJjZmpReFFDQUVSaWNoN2YzQnc0TDF2 +M2ZvdEFnQ0FjeTVOVTBSMHpwMDRkMjc0MUFRcjh0NkhFTkkwZmJoODk4NUgxNU1rWVExRWhJZ2ho +T2QrK2pZUnNRWUFDQ0VRMGNNL2ZycmFhbm52V1lhdkRveU9ubXhlWWd4c1FidmRkb2l6SC8zYWV5 +OFluSFA4Q0RiQXNRR1VSNFRWRVpISW9Ub1lTbllMa1h6bFJ6cm4rQXgvRlNpQ215L0pTZmtxQm1z +TXJGQ01NUmc0bnBrQnV6dGJxTlJwb1B4QmF5Y2lwNVRLdzR5d3lJdHRSbGcvUlR5bExUUVl4QjJa +NDNJbk9BRDQ5dTlmUWVHVTZHRFZJUVIyWVdhOWMyVXhIUmFSMTg0Mmtpd3NhbzJQdEl5RUtNS0FL +Z0sxNXZNUVcxQmNkazYrbW9TUnorSTVZNG01MVBFRHFGVHNxUFpRRERxRlJKSE9PWDJQdUNHTHU4 +cG1YU1NRNTNva2ZBZ2dRYVB6VnJROW9Wb1FGaktoanU3WHBSTW5xellQQVJEUk9jZjVveDFtTE9F +L0xDbnVQNndHeFAwR1F4R3JwY1VIM1VkN1B2dkRqYlZidjJNYUNTRWtTVUpFZ1BqeTVjdk9lOWJZ +YnJmNS9OeUhIeTdOekxBd3h3MEFxclZhOC94NWlXU2FwdDU3SXZyVGUrL3BGR2ZjNCtmUER6WWF6 +TXR5YWJYVm12bmdBNms2bnlSQTVMeC82Zkpsc1Uwd0xHNGt0ZHJFM3U1T1VpUmlDR3hYU05QQWJp +QkM1OWdBSW5MZW8zTkFCTEd3ay9BaENwT3lNQUtFTk5VSlEva25kTTduQ1NsaENXbWFMUmRwNnAw +REFBb0JuY05jYzRFQlRRMVFFVjlaY1V5K21tckxySEpPeUZHbnNnaWJHbUJoWFJKYVVqUklMb0Jh +RGJSbVNmbWNSZ0VRTVUxVGN3T1c2QVVVTWJNa01zRmpjVWdjTkVRTldtdlRsZ0NBclBjRmhvN2Ns +YSs5RGdDR1R6MmJmZUdvRVlHS2VObVhFRmRWZGxWOTVpSVd4SUttdk9wQnpFVUdneFl3d1FkTm85 +NG5nSVdjdnRrZ3pyd1NrM2ZaU1NLc2c4NFJ5KzZLOVpzRU03YkZ1TVVkMmFXY1JpRmlQVkRaWDdv +VFVIR1pjODdGQzdOSkQ4a3hEUlJVT3VsYnpBZlJMT2RkdVFsZ0dwMzYvTE5qVzNmbHJITXViYmNC +OGVIQ2dsaWY5YW9BeDZyVm8vMzlrTE1OaFpDRzBON1plWFQvZnBJa0txdUFRcWpXYXNCMWlRaGNM +VVJieTh2N1cxdUl5SmNZWGRMVDAzZnlwRENNeTJ0eTdjNGRaekFRWWUzRm43enl5NEpHdTNwN0I1 +OXVTbzU2Nzl2dHRuTnUrdVpObDN0T3l1djRzOCtlYURRWUpKTjl1OTErdUxDd05EUGp2WmYxZ1ZX +Tlg3aWdzNXh6YWFYVldpbTEweitvMVdyTnBxYXBORTBSWU9yR0RkMU9NNGErb1dibWF4T1J3c1E4 +eXBqem1pQ1QyRXZaY0dRbHZoMExVV2NkeE5YQ3p4VnFsMHJRR0NEUDRSeERKcHdBd09LMzMwZzd6 +YmFhTWpLOGptcDVsMFlmY29FeUI0aUZZZ2JGenNva1l3d0NvQ083RkZVQkFFblhFZUVGaWFDNFZ1 +N1hMeVhhdk16eHdsRjZ1U2wxeU5xanhrSFNwV3NNa0dlcDhhTlFnQU9BNnVCSkFKczJFVy9HcmpY +UjV3OHVYaFllb3kwaW9LaERockphN1JTSVZwSlNPNjF2RmdRYWlsUWh4RWtpTjJzOVVHb2I5VlZT +MFBVVEtWNndSWW5HWUpiNWpJVU90cmRXbHU1NjU5SVFnRE1Id0R0M29sNm52S0U0YUxjVDd3bmdZ +SHQ3cGRVcWFob1JFZmMzTndjYkRYU09RbUFDNGJaeHRkVWlsZjJjYnozOS9VUE5abFFEaU4yVlNx +WTJCTW94T09jR20wM0lueUlZdW5wN0l3TTJGNWUrL25qU3R0TUFyMXk1SXUyMGRNaXo3NysvT0Qw +dDhlR1QxWHI5ektWTG1ET1lDTisrZWxYbk5EOWkvTUtGb2ZGeHpUWUFzREk3T3pVNUthVEVHSnoz +cjF5NUlyZUwyZ2VQY3NiTHdnZlJHNUFPYnBFNWNUcVpma3N5R0ZSYUM3MmF4Q0FsWEdSTDNqWWJK +ZkxRaUlWMERleHNiL0VRS21JVmhRQlVRdXZYY0oyalJnd1VhZWkxU2U2SzdJazFnMnBieWhoSVA0 +OE5XRjM2MWp6YmxFN2twN2hBQzI4cDl0RCtNN2Z3NFpTa1VhNXZORnhzRkJZR1BITzZ3ZDFvMlN2 +RzJhQTR4T2pWa2pyVFRBSmsxVng2ajlGek5PTUYvYi9PODhLQWppQkVTR3ZVTDJoNjhzTXFzWFFJ +UlAybUQ3R2s4WVhKdWpLRy9HU3BuZFlsaTRwdVRTNEtWbkZ0Sm5uSXd6VEtnc0pWbnBndXZhTy95 +aGprOXF5ZG5wK2ZHaGtvWXVxYzQrSHU3V3ZYSUs4cVdRWHJyNzgrZk9ZTXhNUGQ3eFlXdnJ4K1BV +bUtLUUU3L2hmdnZLTzVnZU0yZS9QbTh0eWM3a1lSY2FCV2UrNVNNZHhsREFodys5bzFZVE5aT283 +Ly9NMlhmL1htM3U1T05Od2xOYjVqWjVqV0tNTVJMKytpUVNleGRwZ1dOcW1sRDVsdTZQV2g0K3Ux +Y3k2YWpSYmhVSFFob0xIY1RwZnF6S1N5VktSUmJza2dod2c1ajJrbEdvTkVRTFgwYWgyWW4vbEN2 +R2JxU1Z3aTgxcnRLbDBuUWdCNkVpdzViY1lGaHdrTEJtMkdBS0JPSGJFRGdOSDZXV251ekdWZXQ3 +V3pBWXJwdEJaMk1ic1pHdEg1QS9FNkpWZWRFdGIyOE9zQmxsYk13Z0NYdjhSb0V0REJsVVRLYm92 +UkNHMkpYeVhyZEJ3TUowWWVVVVZsMWNhWHFMU3U1VHhWaXAxK2trN2ZNbU5DblAyZzByZWp6aklW +YWlWV3Z0VExvUG1oaEduMDh6L2ZPcGo5VkNiR3pudDJZWFZzRERFYlVCOGNIQ1JKQWtRYkR4N3Ny +Szlud2pubzdrcWxiMlFFRVhtYW1hWnA0ajBSTGMvT1Frd0dSTlEzTXRKei9IakloN3NNYTI5emMv +M2VQWVBCTy9kVXZjNHVSdWNZQXhGdDl6NDk4ZU1YaTZuRTN0Ym1XdDdpcDJuYTFkWEY1eHR2dk9I +eVFZTzBzcXZ6OC9JK3dIMHZBRlRIeHFwalkvb1ZsRDlQVFU3cVV1RjFvOXBvbktqWGJUczlON2ZT +YW1IZU5qTUdkSzV4L3J6dzZhSHR0TTQ1UFJmUldRaHhMY3BoaHJ2OFdRZ2I4ejRIODBhZlB4bk5v +QXF2SXdiNTJvR0YxbFlXZVNVenk1WkpaWG1TRkt0T2VtRnh5UmFSTk8yMEtVUzVoY1U2OXQ3bWpH +SWpua3JrNjcrcEoraFVhdVo4NFJXVjVickl0TENPbFZ3dEl0RHBRV1hhTWJjN0FPZ2ZxQUpHUCt1 +S3JXYmFxcjJvSXdENXhCZmpKZzlVeEVrUGFVbzZtU28wQm9qejFtUnZ1UnNGeVZGUXE3ZE9heGYv +OUcyS0JGV3N0V0dRRXgrcTEwV3RGa29CRjl2SzNhaCtkR1RBa1VwbGFIeGM5RGtldVFFc3o4MDVO +Vy9pNFUvWHNXT0R6U1lCWUQ3Y0RTRjA5L1V0VGs4emRZcDZJaG9hSDJjRFhQNWJOd0hzcnE4dlRV +OERZcGJPaUFDd3U3RWhHRFE5OE85eFBQUmxESUZvci9lWnlJQmpKd1pQMVMrRTBsNkpUNjVlZGJr +elpMQmFPM2Z1OUd1dlFkeE9QMXhZK052MURuc2xYbnU3dzE2Sm1SczN5c1BkZ2RIUnM1Y3U2U0xt +dlJLZnZQdHVlYTlFMzZzWHN5aEJmSWdOV2JqL1Ezc2xTaW1oaTFXbmtFNjVLSFYxTjdxOXRXRmFD +YzJZQmdvOXdWNEpLS1cxTTNzbFNqK0RrK3JTSDQ4aGM1eCtIOWhZWHp0c3I0UXNLTkdtaG4rMlY0 +TGlOK2FPa2hDL1AzV01BT2lCOW1QMlNneWYraEd6c0hFYnhNUmlhU2QybU1GS2NWL1pVYTIyMlp3 +MDdHY3dnRW1od3g2cFF5Wm5TQTEzVWJHcUVhWlNraGhBVUtKMnVaZkR6aWVsNCtxb0ZvU0ZBTElm +MzNtd0N2ay81ejN4Z3hFQmtkOGNBSkZDQUI0UUVRV3BPZSt6WlJneCs2V2VKVGxiY3Naa1hENUpR +Z2c4RE00UTgyRFlPYjRsNVBiNEpNazh5QWJ4endVU3pHeXZ4Tzl2ckg3OEcybGw1VVhzMWJmZXdw +d0taSjhDRDNlbFBOaWoxYkd4aVlzWHhVazhuUTRoM0w1NlZZZUZhWGZpNHNXaFpqUEVIY2ZLM056 +VTVLVEI0Sk5FRDNjRnc0Tkhyalk2b2FjU1dUU0ZDczI2VytTUE9qQmVqelZ2bUhDYnpOUW50VTZo +TE1rbGlHbE4xWW1wQVFWTVZoeVRyOFdUNHBiQkpMcVFxVUdHcWxYUkpWRkk2amZzNzdWWDR0N1hM +YjZ6dzE2SmppU2ppSmtQczFjQ095MTVHclFoSE8zRGpoaktkMFY3SlVaK09LcDlMeTdVUnB2bkdX +ZURFalcyYVRUZmQ2OUUrWC9sVFpWQ3p2bC95MTRKSFM2eFVJNG4yU3NSZWFRVEJ1VU8yMDVIU3hX +VWtsczdFbFVoOHJTcy9EQUQ3dkY3SmR3aFMvdGh3OTFJa21uMHpueHJlV0dXOXdMTE5RSjZhblFV +a1dNYXhCR2JTNHQ3bTV1OEYxZ2t1M3NybGFFaFJDUWdDb1F1aS9McS9EeWZWSnFwTWp6YzNWc2h5 +TllkNXh3QTdtOXViaXd1R2d3QThGUnRGREwvQm9Tc0VldysybGVwOUNXWUcxQ0FEdTNXekZUanpQ +UEcwS20vZmpyeHdrdFJLUU04WExsL3BLZS9OLy9CTU5PUTdzL05URGZQdm1BMGZEWHpSVzNjcXQz +K2JuSGZWUWI2SXcyaHZmZlYvSHg5L0t3Um52M3lMODNuZnNhZjF6YzIraXVWNWVWbC9GL2Nmaysw +dDcvWC9WaFU3WFo3YTNzSEtQd0RBU2s4RVpNaXFhSUFBQUFBU1VWT1JLNUNZSUk9IzAjwrdDYnVy +bmV0dCNBcmlhbCNTIzY0IzUwIzIjMTAwIzUw diff --git a/LCEngine/LCEngine.pyd b/LCEngine/LCEngine.pyd deleted file mode 100644 index 07b9d4e..0000000 Binary files a/LCEngine/LCEngine.pyd and /dev/null differ diff --git a/LCEngine/LCEngine.so b/LCEngine/LCEngine.so deleted file mode 100644 index c3bc5cf..0000000 Binary files a/LCEngine/LCEngine.so and /dev/null differ diff --git a/LCEngine/LCEngine.c b/LCEngine/LCEngineV1.c similarity index 91% rename from LCEngine/LCEngine.c rename to LCEngine/LCEngineV1.c index a8abf9c..bc65aef 100644 --- a/LCEngine/LCEngine.c +++ b/LCEngine/LCEngineV1.c @@ -10,7 +10,7 @@ "irina" ] }, - "module_name": "LCEngine" + "module_name": "LCEngineV1" } END: Cython Metadata */ @@ -441,8 +441,8 @@ static CYTHON_INLINE float __PYX_NAN() { #endif #endif -#define __PYX_HAVE__LCEngine -#define __PYX_HAVE_API__LCEngine +#define __PYX_HAVE__LCEngineV1 +#define __PYX_HAVE_API__LCEngineV1 #include "irina.h" #ifdef _OPENMP #include @@ -639,7 +639,7 @@ static const char *__pyx_filename; static const char *__pyx_f[] = { - "LCEngine.pyx", + "LCEngineV1.pyx", }; /*--- Type declarations ---*/ @@ -1132,11 +1132,11 @@ static int __Pyx_InitStrings(__Pyx_StringTabEntry *t); /* Module declarations from 'cython' */ -/* Module declarations from 'LCEngine' */ -#define __Pyx_MODULE_NAME "LCEngine" -int __pyx_module_is_main_LCEngine = 0; +/* Module declarations from 'LCEngineV1' */ +#define __Pyx_MODULE_NAME "LCEngineV1" +int __pyx_module_is_main_LCEngineV1 = 0; -/* Implementation of 'LCEngine' */ +/* Implementation of 'LCEngineV1' */ static PyObject *__pyx_builtin_range; static PyObject *__pyx_builtin_object; static PyObject *__pyx_builtin_StopIteration; @@ -1288,7 +1288,6 @@ static const char __pyx_k_numMove[] = "numMove"; static const char __pyx_k_prepare[] = "__prepare__"; static const char __pyx_k_xpv2pgn[] = "xpv2pgn"; static const char __pyx_k_InfoMove[] = "InfoMove"; -static const char __pyx_k_LCEngine[] = "LCEngine"; static const char __pyx_k_castle_K[] = "_castle_K"; static const char __pyx_k_castle_Q[] = "_castle_Q"; static const char __pyx_k_getMoves[] = "getMoves"; @@ -1311,6 +1310,7 @@ static const char __pyx_k_metaclass[] = "__metaclass__"; static const char __pyx_k_promotion[] = "_promotion"; static const char __pyx_k_siBlancas[] = "siBlancas"; static const char __pyx_k_traceback[] = "traceback"; +static const char __pyx_k_LCEngineV1[] = "LCEngineV1"; static const char __pyx_k_coronacion[] = "coronacion"; static const char __pyx_k_getExMoves[] = "getExMoves"; static const char __pyx_k_movimiento[] = "movimiento"; @@ -1339,7 +1339,7 @@ static const char __pyx_k_InfoMove_isCastleQ[] = "InfoMove.isCastleQ"; static const char __pyx_k_InfoMove_coronacion[] = "InfoMove.coronacion"; static const char __pyx_k_InfoMove_movimiento[] = "InfoMove.movimiento"; static const char __pyx_k_InfoMove_isEnPassant[] = "InfoMove.isEnPassant"; -static const char __pyx_k_c_correr_lucaschess_pyLC12_LCEng[] = "c:\\correr\\lucaschess\\pyLC12\\LCEngine\\LCEngine.pyx"; +static const char __pyx_k_c_correr_lucaschess_pyLC12_LCEng[] = "c:\\correr\\lucaschess\\pyLC12\\LCEngine\\LCEngineV1.pyx"; static const char __pyx_k_rnbqkbnr_pppppppp_8_8_8_8_PPPPPP[] = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; static PyObject *__pyx_kp_s_; static PyObject *__pyx_n_s_FCpos; @@ -1356,7 +1356,7 @@ static PyObject *__pyx_n_s_InfoMove_jaque; static PyObject *__pyx_n_s_InfoMove_mate; static PyObject *__pyx_n_s_InfoMove_movimiento; static PyObject *__pyx_n_s_InfoMove_pieza; -static PyObject *__pyx_n_s_LCEngine; +static PyObject *__pyx_n_s_LCEngineV1; static PyObject *__pyx_n_s_PGNreader; static PyObject *__pyx_n_s_PGNreader___enter; static PyObject *__pyx_n_s_PGNreader___exit; @@ -1541,57 +1541,57 @@ static PyObject *__pyx_n_s_xpv2lipv; static PyObject *__pyx_n_s_xpv2pgn; static PyObject *__pyx_n_s_xpv2pv; static PyObject *__pyx_n_s_y; -static PyObject *__pyx_pf_8LCEngine_9PGNreader___init__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self, PyObject *__pyx_v_fich, PyObject *__pyx_v_depth); /* proto */ -static PyObject *__pyx_pf_8LCEngine_9PGNreader_2__enter__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_9PGNreader_4__exit__(CYTHON_UNUSED PyObject *__pyx_self, CYTHON_UNUSED PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *__pyx_v_type, CYTHON_UNUSED PyObject *__pyx_v_value, CYTHON_UNUSED PyObject *__pyx_v_traceback); /* proto */ -static PyObject *__pyx_pf_8LCEngine_9PGNreader_6__iter__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__pyx_self, CYTHON_UNUSED PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pgn1); /* proto */ -static PyObject *__pyx_pf_8LCEngine_2posFC(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pos); /* proto */ -static PyObject *__pyx_pf_8LCEngine_4FCpos(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_f, PyObject *__pyx_v_c); /* proto */ -static PyObject *__pyx_pf_8LCEngine_6posA1(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pos); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a1); /* proto */ -static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a1h8q); /* proto */ -static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_num); /* proto */ -static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos); /* proto */ -static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos, PyObject *__pyx_v_fi, PyObject *__pyx_v_ci); /* proto */ -static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos); /* proto */ -static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos, PyObject *__pyx_v_siW); /* proto */ -static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_no, PyObject *__pyx_v_nv, PyObject *__pyx_v_mx); /* proto */ -static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_x, PyObject *__pyx_v_y, PyObject *__pyx_v_celdas_ocupadas); /* proto */ -static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv); /* proto */ -static PyObject *__pyx_pf_8LCEngine_28xpv2pv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv); /* proto */ -static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pv); /* proto */ -static PyObject *__pyx_pf_8LCEngine_32runFen(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen, PyObject *__pyx_v_depth, PyObject *__pyx_v_ms, PyObject *__pyx_v_level); /* proto */ -static PyObject *__pyx_pf_8LCEngine_34setFen(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen); /* proto */ -static PyObject *__pyx_pf_8LCEngine_36getFen(CYTHON_UNUSED PyObject *__pyx_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desdeA1H8, PyObject *__pyx_v_hastaA1H8, PyObject *__pyx_v_coronacion); /* proto */ -static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv); /* proto */ -static PyObject *__pyx_pf_8LCEngine_44isCheck(CYTHON_UNUSED PyObject *__pyx_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self, PyObject *__pyx_v_num); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_2desde(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_4hasta(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_6coronacion(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_8movimiento(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_10jaque(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_12mate(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_14captura(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_16pieza(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_18isCastleK(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_20isCastleQ(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_8InfoMove_22isEnPassant(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desde, PyObject *__pyx_v_hasta, PyObject *__pyx_v_coronacion); /* proto */ -static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desde, PyObject *__pyx_v_hasta, PyObject *__pyx_v_coronacion); /* proto */ -static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_move); /* proto */ -static PyObject *__pyx_pf_8LCEngine_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen); /* proto */ -static PyObject *__pyx_pf_8LCEngine_56setFenInicial(CYTHON_UNUSED PyObject *__pyx_self); /* proto */ -static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pv); /* proto */ -static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen); /* proto */ -static PyObject *__pyx_pf_8LCEngine_62getCaptures(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen, PyObject *__pyx_v_siMB); /* proto */ -static PyObject *__pyx_pf_8LCEngine_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen); /* proto */ -static PyObject *__pyx_pf_8LCEngine_66fenTerminado(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_9PGNreader___init__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self, PyObject *__pyx_v_fich, PyObject *__pyx_v_depth); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_9PGNreader_2__enter__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_9PGNreader_4__exit__(CYTHON_UNUSED PyObject *__pyx_self, CYTHON_UNUSED PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *__pyx_v_type, CYTHON_UNUSED PyObject *__pyx_v_value, CYTHON_UNUSED PyObject *__pyx_v_traceback); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_9PGNreader_6__iter__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_9PGNreader_8next(CYTHON_UNUSED PyObject *__pyx_self, CYTHON_UNUSED PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pgn1); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_2posFC(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pos); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_4FCpos(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_f, PyObject *__pyx_v_c); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_6posA1(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pos); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a1); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_10move2num(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a1h8q); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_12num2move(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_num); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_14liK(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_16liBR(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos, PyObject *__pyx_v_fi, PyObject *__pyx_v_ci); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_18liN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_20liP(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos, PyObject *__pyx_v_siW); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_22knightmoves(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_no, PyObject *__pyx_v_nv, PyObject *__pyx_v_mx); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_x, PyObject *__pyx_v_y, PyObject *__pyx_v_celdas_ocupadas); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_28xpv2pv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pv); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_32runFen(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen, PyObject *__pyx_v_depth, PyObject *__pyx_v_ms, PyObject *__pyx_v_level); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_34setFen(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_36getFen(CYTHON_UNUSED PyObject *__pyx_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_38getMoves(CYTHON_UNUSED PyObject *__pyx_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desdeA1H8, PyObject *__pyx_v_hastaA1H8, PyObject *__pyx_v_coronacion); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_44isCheck(CYTHON_UNUSED PyObject *__pyx_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove___init__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self, PyObject *__pyx_v_num); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_2desde(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_4hasta(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_6coronacion(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_8movimiento(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_10jaque(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_12mate(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_14captura(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_16pieza(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_18isCastleK(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_20isCastleQ(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_22isEnPassant(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_46getExMoves(CYTHON_UNUSED PyObject *__pyx_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_48moveExPV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desde, PyObject *__pyx_v_hasta, PyObject *__pyx_v_coronacion); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_50movePV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desde, PyObject *__pyx_v_hasta, PyObject *__pyx_v_coronacion); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_52makeMove(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_move); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_56setFenInicial(CYTHON_UNUSED PyObject *__pyx_self); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_58makePV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pv); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_60getCapturesFEN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_62getCaptures(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen, PyObject *__pyx_v_siMB); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen); /* proto */ +static PyObject *__pyx_pf_10LCEngineV1_66fenTerminado(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen); /* proto */ static PyObject *__pyx_int_0; static PyObject *__pyx_int_1; static PyObject *__pyx_int_2; @@ -1778,7 +1778,7 @@ static PyObject *__pyx_codeobj__170; static PyObject *__pyx_codeobj__172; static PyObject *__pyx_codeobj__174; -/* "LCEngine.pyx":42 +/* "LCEngineV1.pyx":42 * * class PGNreader: * def __init__(self, fich, depth): # <<<<<<<<<<<<<< @@ -1787,9 +1787,9 @@ static PyObject *__pyx_codeobj__174; */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_9PGNreader_1__init__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_9PGNreader_1__init__ = {"__init__", (PyCFunction)__pyx_pw_8LCEngine_9PGNreader_1__init__, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_9PGNreader_1__init__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_9PGNreader_1__init__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_9PGNreader_1__init__ = {"__init__", (PyCFunction)__pyx_pw_10LCEngineV1_9PGNreader_1__init__, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_9PGNreader_1__init__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_self = 0; PyObject *__pyx_v_fich = 0; PyObject *__pyx_v_depth = 0; @@ -1843,23 +1843,23 @@ static PyObject *__pyx_pw_8LCEngine_9PGNreader_1__init__(PyObject *__pyx_self, P __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("__init__", 1, 3, 3, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 42, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.PGNreader.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.PGNreader.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_9PGNreader___init__(__pyx_self, __pyx_v_self, __pyx_v_fich, __pyx_v_depth); + __pyx_r = __pyx_pf_10LCEngineV1_9PGNreader___init__(__pyx_self, __pyx_v_self, __pyx_v_fich, __pyx_v_depth); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_9PGNreader___init__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self, PyObject *__pyx_v_fich, PyObject *__pyx_v_depth) { +static PyObject *__pyx_pf_10LCEngineV1_9PGNreader___init__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self, PyObject *__pyx_v_fich, PyObject *__pyx_v_depth) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("__init__", 0); - /* "LCEngine.pyx":43 + /* "LCEngineV1.pyx":43 * class PGNreader: * def __init__(self, fich, depth): * self.fich = fich # <<<<<<<<<<<<<< @@ -1868,7 +1868,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader___init__(CYTHON_UNUSED PyObject * */ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_fich, __pyx_v_fich) < 0) __PYX_ERR(0, 43, __pyx_L1_error) - /* "LCEngine.pyx":44 + /* "LCEngineV1.pyx":44 * def __init__(self, fich, depth): * self.fich = fich * self.depth = depth # <<<<<<<<<<<<<< @@ -1877,7 +1877,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader___init__(CYTHON_UNUSED PyObject * */ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_depth, __pyx_v_depth) < 0) __PYX_ERR(0, 44, __pyx_L1_error) - /* "LCEngine.pyx":42 + /* "LCEngineV1.pyx":42 * * class PGNreader: * def __init__(self, fich, depth): # <<<<<<<<<<<<<< @@ -1889,7 +1889,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader___init__(CYTHON_UNUSED PyObject * __pyx_r = Py_None; __Pyx_INCREF(Py_None); goto __pyx_L0; __pyx_L1_error:; - __Pyx_AddTraceback("LCEngine.PGNreader.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.PGNreader.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -1897,7 +1897,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader___init__(CYTHON_UNUSED PyObject * return __pyx_r; } -/* "LCEngine.pyx":46 +/* "LCEngineV1.pyx":46 * self.depth = depth * * def __enter__(self): # <<<<<<<<<<<<<< @@ -1906,20 +1906,20 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader___init__(CYTHON_UNUSED PyObject * */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_9PGNreader_3__enter__(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_9PGNreader_3__enter__ = {"__enter__", (PyCFunction)__pyx_pw_8LCEngine_9PGNreader_3__enter__, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_9PGNreader_3__enter__(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_9PGNreader_3__enter__(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_9PGNreader_3__enter__ = {"__enter__", (PyCFunction)__pyx_pw_10LCEngineV1_9PGNreader_3__enter__, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_9PGNreader_3__enter__(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("__enter__ (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_9PGNreader_2__enter__(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_9PGNreader_2__enter__(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_9PGNreader_2__enter__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_9PGNreader_2__enter__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; @@ -1928,7 +1928,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_2__enter__(CYTHON_UNUSED PyObject int __pyx_t_4; __Pyx_RefNannySetupContext("__enter__", 0); - /* "LCEngine.pyx":47 + /* "LCEngineV1.pyx":47 * * def __enter__(self): * pgn_start(self.fich, self.depth) # <<<<<<<<<<<<<< @@ -1945,7 +1945,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_2__enter__(CYTHON_UNUSED PyObject pgn_start(__pyx_t_2, __pyx_t_4); __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":48 + /* "LCEngineV1.pyx":48 * def __enter__(self): * pgn_start(self.fich, self.depth) * return self # <<<<<<<<<<<<<< @@ -1957,7 +1957,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_2__enter__(CYTHON_UNUSED PyObject __pyx_r = __pyx_v_self; goto __pyx_L0; - /* "LCEngine.pyx":46 + /* "LCEngineV1.pyx":46 * self.depth = depth * * def __enter__(self): # <<<<<<<<<<<<<< @@ -1969,7 +1969,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_2__enter__(CYTHON_UNUSED PyObject __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); __Pyx_XDECREF(__pyx_t_3); - __Pyx_AddTraceback("LCEngine.PGNreader.__enter__", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.PGNreader.__enter__", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -1977,7 +1977,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_2__enter__(CYTHON_UNUSED PyObject return __pyx_r; } -/* "LCEngine.pyx":50 +/* "LCEngineV1.pyx":50 * return self * * def __exit__(self, type, value, traceback): # <<<<<<<<<<<<<< @@ -1986,9 +1986,9 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_2__enter__(CYTHON_UNUSED PyObject */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_9PGNreader_5__exit__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_9PGNreader_5__exit__ = {"__exit__", (PyCFunction)__pyx_pw_8LCEngine_9PGNreader_5__exit__, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_9PGNreader_5__exit__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_9PGNreader_5__exit__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_9PGNreader_5__exit__ = {"__exit__", (PyCFunction)__pyx_pw_10LCEngineV1_9PGNreader_5__exit__, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_9PGNreader_5__exit__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { CYTHON_UNUSED PyObject *__pyx_v_self = 0; CYTHON_UNUSED PyObject *__pyx_v_type = 0; CYTHON_UNUSED PyObject *__pyx_v_value = 0; @@ -2051,23 +2051,23 @@ static PyObject *__pyx_pw_8LCEngine_9PGNreader_5__exit__(PyObject *__pyx_self, P __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("__exit__", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 50, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.PGNreader.__exit__", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.PGNreader.__exit__", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_9PGNreader_4__exit__(__pyx_self, __pyx_v_self, __pyx_v_type, __pyx_v_value, __pyx_v_traceback); + __pyx_r = __pyx_pf_10LCEngineV1_9PGNreader_4__exit__(__pyx_self, __pyx_v_self, __pyx_v_type, __pyx_v_value, __pyx_v_traceback); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_9PGNreader_4__exit__(CYTHON_UNUSED PyObject *__pyx_self, CYTHON_UNUSED PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *__pyx_v_type, CYTHON_UNUSED PyObject *__pyx_v_value, CYTHON_UNUSED PyObject *__pyx_v_traceback) { +static PyObject *__pyx_pf_10LCEngineV1_9PGNreader_4__exit__(CYTHON_UNUSED PyObject *__pyx_self, CYTHON_UNUSED PyObject *__pyx_v_self, CYTHON_UNUSED PyObject *__pyx_v_type, CYTHON_UNUSED PyObject *__pyx_v_value, CYTHON_UNUSED PyObject *__pyx_v_traceback) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("__exit__", 0); - /* "LCEngine.pyx":51 + /* "LCEngineV1.pyx":51 * * def __exit__(self, type, value, traceback): * pgn_stop() # <<<<<<<<<<<<<< @@ -2076,7 +2076,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_4__exit__(CYTHON_UNUSED PyObject */ pgn_stop(); - /* "LCEngine.pyx":50 + /* "LCEngineV1.pyx":50 * return self * * def __exit__(self, type, value, traceback): # <<<<<<<<<<<<<< @@ -2091,7 +2091,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_4__exit__(CYTHON_UNUSED PyObject return __pyx_r; } -/* "LCEngine.pyx":53 +/* "LCEngineV1.pyx":53 * pgn_stop() * * def __iter__(self): # <<<<<<<<<<<<<< @@ -2100,25 +2100,25 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_4__exit__(CYTHON_UNUSED PyObject */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_9PGNreader_7__iter__(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_9PGNreader_7__iter__ = {"__iter__", (PyCFunction)__pyx_pw_8LCEngine_9PGNreader_7__iter__, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_9PGNreader_7__iter__(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_9PGNreader_7__iter__(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_9PGNreader_7__iter__ = {"__iter__", (PyCFunction)__pyx_pw_10LCEngineV1_9PGNreader_7__iter__, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_9PGNreader_7__iter__(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("__iter__ (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_9PGNreader_6__iter__(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_9PGNreader_6__iter__(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_9PGNreader_6__iter__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_9PGNreader_6__iter__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("__iter__", 0); - /* "LCEngine.pyx":54 + /* "LCEngineV1.pyx":54 * * def __iter__(self): * return self # <<<<<<<<<<<<<< @@ -2130,7 +2130,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_6__iter__(CYTHON_UNUSED PyObject __pyx_r = __pyx_v_self; goto __pyx_L0; - /* "LCEngine.pyx":53 + /* "LCEngineV1.pyx":53 * pgn_stop() * * def __iter__(self): # <<<<<<<<<<<<<< @@ -2145,7 +2145,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_6__iter__(CYTHON_UNUSED PyObject return __pyx_r; } -/* "LCEngine.pyx":56 +/* "LCEngineV1.pyx":56 * return self * * def next(self): # <<<<<<<<<<<<<< @@ -2154,20 +2154,20 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_6__iter__(CYTHON_UNUSED PyObject */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_9PGNreader_9next(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_9PGNreader_9next = {"next", (PyCFunction)__pyx_pw_8LCEngine_9PGNreader_9next, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_9PGNreader_9next(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_9PGNreader_9next(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_9PGNreader_9next = {"next", (PyCFunction)__pyx_pw_10LCEngineV1_9PGNreader_9next, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_9PGNreader_9next(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("next (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_9PGNreader_8next(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_9PGNreader_8next(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__pyx_self, CYTHON_UNUSED PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_9PGNreader_8next(CYTHON_UNUSED PyObject *__pyx_self, CYTHON_UNUSED PyObject *__pyx_v_self) { int __pyx_v_n; char *__pyx_v_pgn; char *__pyx_v_pv; @@ -2187,7 +2187,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p PyObject *__pyx_t_7 = NULL; __Pyx_RefNannySetupContext("next", 0); - /* "LCEngine.pyx":57 + /* "LCEngineV1.pyx":57 * * def next(self): * n = pgn_read() # <<<<<<<<<<<<<< @@ -2196,7 +2196,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p */ __pyx_v_n = pgn_read(); - /* "LCEngine.pyx":58 + /* "LCEngineV1.pyx":58 * def next(self): * n = pgn_read() * if n: # <<<<<<<<<<<<<< @@ -2206,7 +2206,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p __pyx_t_1 = (__pyx_v_n != 0); if (__pyx_t_1) { - /* "LCEngine.pyx":59 + /* "LCEngineV1.pyx":59 * n = pgn_read() * if n: * pgn = pgn_game() # <<<<<<<<<<<<<< @@ -2215,7 +2215,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p */ __pyx_v_pgn = pgn_game(); - /* "LCEngine.pyx":60 + /* "LCEngineV1.pyx":60 * if n: * pgn = pgn_game() * pv = pgn_pv() # <<<<<<<<<<<<<< @@ -2224,7 +2224,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p */ __pyx_v_pv = pgn_pv(); - /* "LCEngine.pyx":61 + /* "LCEngineV1.pyx":61 * pgn = pgn_game() * pv = pgn_pv() * d = {} # <<<<<<<<<<<<<< @@ -2236,7 +2236,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p __pyx_v_d = ((PyObject*)__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":62 + /* "LCEngineV1.pyx":62 * pv = pgn_pv() * d = {} * n = pgn_numlabels() # <<<<<<<<<<<<<< @@ -2245,7 +2245,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p */ __pyx_v_n = pgn_numlabels(); - /* "LCEngine.pyx":63 + /* "LCEngineV1.pyx":63 * d = {} * n = pgn_numlabels() * r = pgn_raw() # <<<<<<<<<<<<<< @@ -2254,7 +2254,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p */ __pyx_v_r = pgn_raw(); - /* "LCEngine.pyx":64 + /* "LCEngineV1.pyx":64 * n = pgn_numlabels() * r = pgn_raw() * fens = [ pgn_fen(num) for num in range(pgn_numfens()) ] # <<<<<<<<<<<<<< @@ -2274,7 +2274,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p __pyx_v_fens = ((PyObject*)__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":65 + /* "LCEngineV1.pyx":65 * r = pgn_raw() * fens = [ pgn_fen(num) for num in range(pgn_numfens()) ] * if n: # <<<<<<<<<<<<<< @@ -2284,7 +2284,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p __pyx_t_1 = (__pyx_v_n != 0); if (__pyx_t_1) { - /* "LCEngine.pyx":66 + /* "LCEngineV1.pyx":66 * fens = [ pgn_fen(num) for num in range(pgn_numfens()) ] * if n: * for x in range(n): # <<<<<<<<<<<<<< @@ -2295,7 +2295,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p for (__pyx_t_4 = 0; __pyx_t_4 < __pyx_t_3; __pyx_t_4+=1) { __pyx_v_x = __pyx_t_4; - /* "LCEngine.pyx":67 + /* "LCEngineV1.pyx":67 * if n: * for x in range(n): * d[pgn_label(x).upper()] = pgn_value(x) # <<<<<<<<<<<<<< @@ -2332,7 +2332,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; } - /* "LCEngine.pyx":65 + /* "LCEngineV1.pyx":65 * r = pgn_raw() * fens = [ pgn_fen(num) for num in range(pgn_numfens()) ] * if n: # <<<<<<<<<<<<<< @@ -2341,7 +2341,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p */ } - /* "LCEngine.pyx":68 + /* "LCEngineV1.pyx":68 * for x in range(n): * d[pgn_label(x).upper()] = pgn_value(x) * return pgn, pv, d, r, fens # <<<<<<<<<<<<<< @@ -2376,7 +2376,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p __pyx_t_6 = 0; goto __pyx_L0; - /* "LCEngine.pyx":58 + /* "LCEngineV1.pyx":58 * def next(self): * n = pgn_read() * if n: # <<<<<<<<<<<<<< @@ -2385,7 +2385,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p */ } - /* "LCEngine.pyx":70 + /* "LCEngineV1.pyx":70 * return pgn, pv, d, r, fens * else: * raise StopIteration # <<<<<<<<<<<<<< @@ -2397,7 +2397,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p __PYX_ERR(0, 70, __pyx_L1_error) } - /* "LCEngine.pyx":56 + /* "LCEngineV1.pyx":56 * return self * * def next(self): # <<<<<<<<<<<<<< @@ -2411,7 +2411,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p __Pyx_XDECREF(__pyx_t_5); __Pyx_XDECREF(__pyx_t_6); __Pyx_XDECREF(__pyx_t_7); - __Pyx_AddTraceback("LCEngine.PGNreader.next", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.PGNreader.next", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_d); @@ -2421,7 +2421,7 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p return __pyx_r; } -/* "LCEngine.pyx":73 +/* "LCEngineV1.pyx":73 * * * def lc_pgn2pv(pgn1): # <<<<<<<<<<<<<< @@ -2430,20 +2430,20 @@ static PyObject *__pyx_pf_8LCEngine_9PGNreader_8next(CYTHON_UNUSED PyObject *__p */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_1lc_pgn2pv(PyObject *__pyx_self, PyObject *__pyx_v_pgn1); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_1lc_pgn2pv = {"lc_pgn2pv", (PyCFunction)__pyx_pw_8LCEngine_1lc_pgn2pv, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_1lc_pgn2pv(PyObject *__pyx_self, PyObject *__pyx_v_pgn1) { +static PyObject *__pyx_pw_10LCEngineV1_1lc_pgn2pv(PyObject *__pyx_self, PyObject *__pyx_v_pgn1); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_1lc_pgn2pv = {"lc_pgn2pv", (PyCFunction)__pyx_pw_10LCEngineV1_1lc_pgn2pv, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_1lc_pgn2pv(PyObject *__pyx_self, PyObject *__pyx_v_pgn1) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("lc_pgn2pv (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_lc_pgn2pv(__pyx_self, ((PyObject *)__pyx_v_pgn1)); + __pyx_r = __pyx_pf_10LCEngineV1_lc_pgn2pv(__pyx_self, ((PyObject *)__pyx_v_pgn1)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pgn1) { +static PyObject *__pyx_pf_10LCEngineV1_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pgn1) { char __pyx_v_pv[10]; int __pyx_v_resp; PyObject *__pyx_r = NULL; @@ -2453,7 +2453,7 @@ static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self PyObject *__pyx_t_3 = NULL; __Pyx_RefNannySetupContext("lc_pgn2pv", 0); - /* "LCEngine.pyx":75 + /* "LCEngineV1.pyx":75 * def lc_pgn2pv(pgn1): * cdef char pv[10]; * resp = pgn2pv(pgn1, pv) # <<<<<<<<<<<<<< @@ -2463,7 +2463,7 @@ static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_pgn1); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 75, __pyx_L1_error) __pyx_v_resp = pgn2pv(__pyx_t_1, __pyx_v_pv); - /* "LCEngine.pyx":76 + /* "LCEngineV1.pyx":76 * cdef char pv[10]; * resp = pgn2pv(pgn1, pv) * if resp == 9999: # <<<<<<<<<<<<<< @@ -2473,7 +2473,7 @@ static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self __pyx_t_2 = ((__pyx_v_resp == 0x270F) != 0); if (__pyx_t_2) { - /* "LCEngine.pyx":77 + /* "LCEngineV1.pyx":77 * resp = pgn2pv(pgn1, pv) * if resp == 9999: * return "" # <<<<<<<<<<<<<< @@ -2485,7 +2485,7 @@ static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self __pyx_r = __pyx_kp_s_; goto __pyx_L0; - /* "LCEngine.pyx":76 + /* "LCEngineV1.pyx":76 * cdef char pv[10]; * resp = pgn2pv(pgn1, pv) * if resp == 9999: # <<<<<<<<<<<<<< @@ -2494,7 +2494,7 @@ static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self */ } - /* "LCEngine.pyx":79 + /* "LCEngineV1.pyx":79 * return "" * else: * return pv # <<<<<<<<<<<<<< @@ -2510,7 +2510,7 @@ static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self goto __pyx_L0; } - /* "LCEngine.pyx":73 + /* "LCEngineV1.pyx":73 * * * def lc_pgn2pv(pgn1): # <<<<<<<<<<<<<< @@ -2521,7 +2521,7 @@ static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_3); - __Pyx_AddTraceback("LCEngine.lc_pgn2pv", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.lc_pgn2pv", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -2529,7 +2529,7 @@ static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self return __pyx_r; } -/* "LCEngine.pyx":82 +/* "LCEngineV1.pyx":82 * * * def posFC(pos): # <<<<<<<<<<<<<< @@ -2538,20 +2538,20 @@ static PyObject *__pyx_pf_8LCEngine_lc_pgn2pv(CYTHON_UNUSED PyObject *__pyx_self */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_3posFC(PyObject *__pyx_self, PyObject *__pyx_v_pos); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_3posFC = {"posFC", (PyCFunction)__pyx_pw_8LCEngine_3posFC, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_3posFC(PyObject *__pyx_self, PyObject *__pyx_v_pos) { +static PyObject *__pyx_pw_10LCEngineV1_3posFC(PyObject *__pyx_self, PyObject *__pyx_v_pos); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_3posFC = {"posFC", (PyCFunction)__pyx_pw_10LCEngineV1_3posFC, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_3posFC(PyObject *__pyx_self, PyObject *__pyx_v_pos) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("posFC (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_2posFC(__pyx_self, ((PyObject *)__pyx_v_pos)); + __pyx_r = __pyx_pf_10LCEngineV1_2posFC(__pyx_self, ((PyObject *)__pyx_v_pos)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_2posFC(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pos) { +static PyObject *__pyx_pf_10LCEngineV1_2posFC(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pos) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; @@ -2559,7 +2559,7 @@ static PyObject *__pyx_pf_8LCEngine_2posFC(CYTHON_UNUSED PyObject *__pyx_self, P PyObject *__pyx_t_3 = NULL; __Pyx_RefNannySetupContext("posFC", 0); - /* "LCEngine.pyx":83 + /* "LCEngineV1.pyx":83 * * def posFC(pos): * return pos / 8, pos % 8 # <<<<<<<<<<<<<< @@ -2583,7 +2583,7 @@ static PyObject *__pyx_pf_8LCEngine_2posFC(CYTHON_UNUSED PyObject *__pyx_self, P __pyx_t_3 = 0; goto __pyx_L0; - /* "LCEngine.pyx":82 + /* "LCEngineV1.pyx":82 * * * def posFC(pos): # <<<<<<<<<<<<<< @@ -2596,7 +2596,7 @@ static PyObject *__pyx_pf_8LCEngine_2posFC(CYTHON_UNUSED PyObject *__pyx_self, P __Pyx_XDECREF(__pyx_t_1); __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); - __Pyx_AddTraceback("LCEngine.posFC", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.posFC", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -2604,7 +2604,7 @@ static PyObject *__pyx_pf_8LCEngine_2posFC(CYTHON_UNUSED PyObject *__pyx_self, P return __pyx_r; } -/* "LCEngine.pyx":85 +/* "LCEngineV1.pyx":85 * return pos / 8, pos % 8 * * def FCpos(f, c): # <<<<<<<<<<<<<< @@ -2613,9 +2613,9 @@ static PyObject *__pyx_pf_8LCEngine_2posFC(CYTHON_UNUSED PyObject *__pyx_self, P */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_5FCpos(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_5FCpos = {"FCpos", (PyCFunction)__pyx_pw_8LCEngine_5FCpos, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_5FCpos(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_5FCpos(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_5FCpos = {"FCpos", (PyCFunction)__pyx_pw_10LCEngineV1_5FCpos, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_5FCpos(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_f = 0; PyObject *__pyx_v_c = 0; PyObject *__pyx_r = 0; @@ -2660,25 +2660,25 @@ static PyObject *__pyx_pw_8LCEngine_5FCpos(PyObject *__pyx_self, PyObject *__pyx __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("FCpos", 1, 2, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 85, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.FCpos", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.FCpos", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_4FCpos(__pyx_self, __pyx_v_f, __pyx_v_c); + __pyx_r = __pyx_pf_10LCEngineV1_4FCpos(__pyx_self, __pyx_v_f, __pyx_v_c); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_4FCpos(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_f, PyObject *__pyx_v_c) { +static PyObject *__pyx_pf_10LCEngineV1_4FCpos(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_f, PyObject *__pyx_v_c) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; PyObject *__pyx_t_2 = NULL; __Pyx_RefNannySetupContext("FCpos", 0); - /* "LCEngine.pyx":86 + /* "LCEngineV1.pyx":86 * * def FCpos(f, c): * return f * 8 + c # <<<<<<<<<<<<<< @@ -2695,7 +2695,7 @@ static PyObject *__pyx_pf_8LCEngine_4FCpos(CYTHON_UNUSED PyObject *__pyx_self, P __pyx_t_2 = 0; goto __pyx_L0; - /* "LCEngine.pyx":85 + /* "LCEngineV1.pyx":85 * return pos / 8, pos % 8 * * def FCpos(f, c): # <<<<<<<<<<<<<< @@ -2707,7 +2707,7 @@ static PyObject *__pyx_pf_8LCEngine_4FCpos(CYTHON_UNUSED PyObject *__pyx_self, P __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); __Pyx_XDECREF(__pyx_t_2); - __Pyx_AddTraceback("LCEngine.FCpos", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.FCpos", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -2715,7 +2715,7 @@ static PyObject *__pyx_pf_8LCEngine_4FCpos(CYTHON_UNUSED PyObject *__pyx_self, P return __pyx_r; } -/* "LCEngine.pyx":88 +/* "LCEngineV1.pyx":88 * return f * 8 + c * * def posA1(pos): # <<<<<<<<<<<<<< @@ -2724,20 +2724,20 @@ static PyObject *__pyx_pf_8LCEngine_4FCpos(CYTHON_UNUSED PyObject *__pyx_self, P */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_7posA1(PyObject *__pyx_self, PyObject *__pyx_v_pos); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_7posA1 = {"posA1", (PyCFunction)__pyx_pw_8LCEngine_7posA1, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_7posA1(PyObject *__pyx_self, PyObject *__pyx_v_pos) { +static PyObject *__pyx_pw_10LCEngineV1_7posA1(PyObject *__pyx_self, PyObject *__pyx_v_pos); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_7posA1 = {"posA1", (PyCFunction)__pyx_pw_10LCEngineV1_7posA1, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_7posA1(PyObject *__pyx_self, PyObject *__pyx_v_pos) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("posA1 (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_6posA1(__pyx_self, ((PyObject *)__pyx_v_pos)); + __pyx_r = __pyx_pf_10LCEngineV1_6posA1(__pyx_self, ((PyObject *)__pyx_v_pos)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_6posA1(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pos) { +static PyObject *__pyx_pf_10LCEngineV1_6posA1(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pos) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; @@ -2745,7 +2745,7 @@ static PyObject *__pyx_pf_8LCEngine_6posA1(CYTHON_UNUSED PyObject *__pyx_self, P PyObject *__pyx_t_3 = NULL; __Pyx_RefNannySetupContext("posA1", 0); - /* "LCEngine.pyx":89 + /* "LCEngineV1.pyx":89 * * def posA1(pos): * return chr(pos % 8 + 97) + chr(pos / 8 + 49) # <<<<<<<<<<<<<< @@ -2787,7 +2787,7 @@ static PyObject *__pyx_pf_8LCEngine_6posA1(CYTHON_UNUSED PyObject *__pyx_self, P __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":88 + /* "LCEngineV1.pyx":88 * return f * 8 + c * * def posA1(pos): # <<<<<<<<<<<<<< @@ -2800,7 +2800,7 @@ static PyObject *__pyx_pf_8LCEngine_6posA1(CYTHON_UNUSED PyObject *__pyx_self, P __Pyx_XDECREF(__pyx_t_1); __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); - __Pyx_AddTraceback("LCEngine.posA1", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.posA1", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -2808,7 +2808,7 @@ static PyObject *__pyx_pf_8LCEngine_6posA1(CYTHON_UNUSED PyObject *__pyx_self, P return __pyx_r; } -/* "LCEngine.pyx":91 +/* "LCEngineV1.pyx":91 * return chr(pos % 8 + 97) + chr(pos / 8 + 49) * * def a1Pos(a1): # <<<<<<<<<<<<<< @@ -2817,20 +2817,20 @@ static PyObject *__pyx_pf_8LCEngine_6posA1(CYTHON_UNUSED PyObject *__pyx_self, P */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_9a1Pos(PyObject *__pyx_self, PyObject *__pyx_v_a1); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_9a1Pos = {"a1Pos", (PyCFunction)__pyx_pw_8LCEngine_9a1Pos, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_9a1Pos(PyObject *__pyx_self, PyObject *__pyx_v_a1) { +static PyObject *__pyx_pw_10LCEngineV1_9a1Pos(PyObject *__pyx_self, PyObject *__pyx_v_a1); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_9a1Pos = {"a1Pos", (PyCFunction)__pyx_pw_10LCEngineV1_9a1Pos, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_9a1Pos(PyObject *__pyx_self, PyObject *__pyx_v_a1) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("a1Pos (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8a1Pos(__pyx_self, ((PyObject *)__pyx_v_a1)); + __pyx_r = __pyx_pf_10LCEngineV1_8a1Pos(__pyx_self, ((PyObject *)__pyx_v_a1)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a1) { +static PyObject *__pyx_pf_10LCEngineV1_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a1) { int __pyx_v_f; int __pyx_v_c; PyObject *__pyx_r = NULL; @@ -2839,7 +2839,7 @@ static PyObject *__pyx_pf_8LCEngine_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, P long __pyx_t_2; __Pyx_RefNannySetupContext("a1Pos", 0); - /* "LCEngine.pyx":93 + /* "LCEngineV1.pyx":93 * def a1Pos(a1): * cdef int f, c * f = ord(a1[1]) - 49 # <<<<<<<<<<<<<< @@ -2852,7 +2852,7 @@ static PyObject *__pyx_pf_8LCEngine_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, P __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; __pyx_v_f = (__pyx_t_2 - 49); - /* "LCEngine.pyx":94 + /* "LCEngineV1.pyx":94 * cdef int f, c * f = ord(a1[1]) - 49 * c = ord(a1[0]) - 97 # <<<<<<<<<<<<<< @@ -2865,7 +2865,7 @@ static PyObject *__pyx_pf_8LCEngine_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, P __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; __pyx_v_c = (__pyx_t_2 - 97); - /* "LCEngine.pyx":95 + /* "LCEngineV1.pyx":95 * f = ord(a1[1]) - 49 * c = ord(a1[0]) - 97 * return f * 8 + c # <<<<<<<<<<<<<< @@ -2879,7 +2879,7 @@ static PyObject *__pyx_pf_8LCEngine_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, P __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":91 + /* "LCEngineV1.pyx":91 * return chr(pos % 8 + 97) + chr(pos / 8 + 49) * * def a1Pos(a1): # <<<<<<<<<<<<<< @@ -2890,7 +2890,7 @@ static PyObject *__pyx_pf_8LCEngine_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, P /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.a1Pos", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.a1Pos", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -2898,7 +2898,7 @@ static PyObject *__pyx_pf_8LCEngine_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, P return __pyx_r; } -/* "LCEngine.pyx":97 +/* "LCEngineV1.pyx":97 * return f * 8 + c * * def move2num(a1h8q): # <<<<<<<<<<<<<< @@ -2907,20 +2907,20 @@ static PyObject *__pyx_pf_8LCEngine_8a1Pos(CYTHON_UNUSED PyObject *__pyx_self, P */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_11move2num(PyObject *__pyx_self, PyObject *__pyx_v_a1h8q); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_11move2num = {"move2num", (PyCFunction)__pyx_pw_8LCEngine_11move2num, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_11move2num(PyObject *__pyx_self, PyObject *__pyx_v_a1h8q) { +static PyObject *__pyx_pw_10LCEngineV1_11move2num(PyObject *__pyx_self, PyObject *__pyx_v_a1h8q); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_11move2num = {"move2num", (PyCFunction)__pyx_pw_10LCEngineV1_11move2num, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_11move2num(PyObject *__pyx_self, PyObject *__pyx_v_a1h8q) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("move2num (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_10move2num(__pyx_self, ((PyObject *)__pyx_v_a1h8q)); + __pyx_r = __pyx_pf_10LCEngineV1_10move2num(__pyx_self, ((PyObject *)__pyx_v_a1h8q)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a1h8q) { +static PyObject *__pyx_pf_10LCEngineV1_10move2num(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a1h8q) { PyObject *__pyx_v_num = NULL; PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations @@ -2934,7 +2934,7 @@ static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_sel int __pyx_t_8; __Pyx_RefNannySetupContext("move2num", 0); - /* "LCEngine.pyx":98 + /* "LCEngineV1.pyx":98 * * def move2num(a1h8q): * num = a1Pos(a1h8q[:2]) + a1Pos(a1h8q[2:4])*64 # <<<<<<<<<<<<<< @@ -3051,7 +3051,7 @@ static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_sel __pyx_v_num = __pyx_t_2; __pyx_t_2 = 0; - /* "LCEngine.pyx":99 + /* "LCEngineV1.pyx":99 * def move2num(a1h8q): * num = a1Pos(a1h8q[:2]) + a1Pos(a1h8q[2:4])*64 * if len(a1h8q)>4: # <<<<<<<<<<<<<< @@ -3062,7 +3062,7 @@ static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_sel __pyx_t_8 = ((__pyx_t_7 > 4) != 0); if (__pyx_t_8) { - /* "LCEngine.pyx":100 + /* "LCEngineV1.pyx":100 * num = a1Pos(a1h8q[:2]) + a1Pos(a1h8q[2:4])*64 * if len(a1h8q)>4: * num += ({"q":1, "r":2, "b":3, "n":4}.get(a1h8q[4], 0))*64*64 # <<<<<<<<<<<<<< @@ -3093,7 +3093,7 @@ static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_DECREF_SET(__pyx_v_num, __pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":99 + /* "LCEngineV1.pyx":99 * def move2num(a1h8q): * num = a1Pos(a1h8q[:2]) + a1Pos(a1h8q[2:4])*64 * if len(a1h8q)>4: # <<<<<<<<<<<<<< @@ -3102,7 +3102,7 @@ static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_sel */ } - /* "LCEngine.pyx":101 + /* "LCEngineV1.pyx":101 * if len(a1h8q)>4: * num += ({"q":1, "r":2, "b":3, "n":4}.get(a1h8q[4], 0))*64*64 * return num # <<<<<<<<<<<<<< @@ -3114,7 +3114,7 @@ static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_sel __pyx_r = __pyx_v_num; goto __pyx_L0; - /* "LCEngine.pyx":97 + /* "LCEngineV1.pyx":97 * return f * 8 + c * * def move2num(a1h8q): # <<<<<<<<<<<<<< @@ -3130,7 +3130,7 @@ static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_XDECREF(__pyx_t_4); __Pyx_XDECREF(__pyx_t_5); __Pyx_XDECREF(__pyx_t_6); - __Pyx_AddTraceback("LCEngine.move2num", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.move2num", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_num); @@ -3139,7 +3139,7 @@ static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_sel return __pyx_r; } -/* "LCEngine.pyx":103 +/* "LCEngineV1.pyx":103 * return num * * def num2move(num): # <<<<<<<<<<<<<< @@ -3148,20 +3148,20 @@ static PyObject *__pyx_pf_8LCEngine_10move2num(CYTHON_UNUSED PyObject *__pyx_sel */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_13num2move(PyObject *__pyx_self, PyObject *__pyx_v_num); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_13num2move = {"num2move", (PyCFunction)__pyx_pw_8LCEngine_13num2move, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_13num2move(PyObject *__pyx_self, PyObject *__pyx_v_num) { +static PyObject *__pyx_pw_10LCEngineV1_13num2move(PyObject *__pyx_self, PyObject *__pyx_v_num); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_13num2move = {"num2move", (PyCFunction)__pyx_pw_10LCEngineV1_13num2move, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_13num2move(PyObject *__pyx_self, PyObject *__pyx_v_num) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("num2move (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_12num2move(__pyx_self, ((PyObject *)__pyx_v_num)); + __pyx_r = __pyx_pf_10LCEngineV1_12num2move(__pyx_self, ((PyObject *)__pyx_v_num)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_num) { +static PyObject *__pyx_pf_10LCEngineV1_12num2move(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_num) { PyObject *__pyx_v_a1 = NULL; PyObject *__pyx_v_h8 = NULL; PyObject *__pyx_v_q = NULL; @@ -3176,7 +3176,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_RefNannySetupContext("num2move", 0); __Pyx_INCREF(__pyx_v_num); - /* "LCEngine.pyx":104 + /* "LCEngineV1.pyx":104 * * def num2move(num): * a1 = posA1(num%64) # <<<<<<<<<<<<<< @@ -3236,7 +3236,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel __pyx_v_a1 = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":105 + /* "LCEngineV1.pyx":105 * def num2move(num): * a1 = posA1(num%64) * num /= 64 # <<<<<<<<<<<<<< @@ -3248,7 +3248,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_DECREF_SET(__pyx_v_num, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":106 + /* "LCEngineV1.pyx":106 * a1 = posA1(num%64) * num /= 64 * h8 = posA1(num%64) # <<<<<<<<<<<<<< @@ -3308,7 +3308,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel __pyx_v_h8 = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":107 + /* "LCEngineV1.pyx":107 * num /= 64 * h8 = posA1(num%64) * num /= 64 # <<<<<<<<<<<<<< @@ -3320,7 +3320,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_DECREF_SET(__pyx_v_num, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":108 + /* "LCEngineV1.pyx":108 * h8 = posA1(num%64) * num /= 64 * if num: # <<<<<<<<<<<<<< @@ -3330,7 +3330,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_v_num); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(0, 108, __pyx_L1_error) if (__pyx_t_6) { - /* "LCEngine.pyx":109 + /* "LCEngineV1.pyx":109 * num /= 64 * if num: * q = {1:"q", 2:"r", 3:"b", 4:"n"}.get(num) # <<<<<<<<<<<<<< @@ -3349,7 +3349,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel __pyx_v_q = __pyx_t_2; __pyx_t_2 = 0; - /* "LCEngine.pyx":108 + /* "LCEngineV1.pyx":108 * h8 = posA1(num%64) * num /= 64 * if num: # <<<<<<<<<<<<<< @@ -3359,7 +3359,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel goto __pyx_L3; } - /* "LCEngine.pyx":111 + /* "LCEngineV1.pyx":111 * q = {1:"q", 2:"r", 3:"b", 4:"n"}.get(num) * else: * q = "" # <<<<<<<<<<<<<< @@ -3372,7 +3372,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel } __pyx_L3:; - /* "LCEngine.pyx":112 + /* "LCEngineV1.pyx":112 * else: * q = "" * return a1 + h8 + q # <<<<<<<<<<<<<< @@ -3389,7 +3389,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":103 + /* "LCEngineV1.pyx":103 * return num * * def num2move(num): # <<<<<<<<<<<<<< @@ -3404,7 +3404,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_4); __Pyx_XDECREF(__pyx_t_5); - __Pyx_AddTraceback("LCEngine.num2move", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.num2move", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_a1); @@ -3416,7 +3416,7 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel return __pyx_r; } -/* "LCEngine.pyx":114 +/* "LCEngineV1.pyx":114 * return a1 + h8 + q * * def liK(npos): # <<<<<<<<<<<<<< @@ -3425,20 +3425,20 @@ static PyObject *__pyx_pf_8LCEngine_12num2move(CYTHON_UNUSED PyObject *__pyx_sel */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_15liK(PyObject *__pyx_self, PyObject *__pyx_v_npos); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_15liK = {"liK", (PyCFunction)__pyx_pw_8LCEngine_15liK, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_15liK(PyObject *__pyx_self, PyObject *__pyx_v_npos) { +static PyObject *__pyx_pw_10LCEngineV1_15liK(PyObject *__pyx_self, PyObject *__pyx_v_npos); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_15liK = {"liK", (PyCFunction)__pyx_pw_10LCEngineV1_15liK, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_15liK(PyObject *__pyx_self, PyObject *__pyx_v_npos) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("liK (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_14liK(__pyx_self, ((PyObject *)__pyx_v_npos)); + __pyx_r = __pyx_pf_10LCEngineV1_14liK(__pyx_self, ((PyObject *)__pyx_v_npos)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos) { +static PyObject *__pyx_pf_10LCEngineV1_14liK(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos) { int __pyx_v_fil; int __pyx_v_col; int __pyx_v_ft; @@ -3464,7 +3464,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py int __pyx_t_14; __Pyx_RefNannySetupContext("liK", 0); - /* "LCEngine.pyx":116 + /* "LCEngineV1.pyx":116 * def liK(npos): * cdef int fil, col, ft, ct * liM = [] # <<<<<<<<<<<<<< @@ -3476,7 +3476,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_v_liM = ((PyObject*)__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":117 + /* "LCEngineV1.pyx":117 * cdef int fil, col, ft, ct * liM = [] * fil, col = posFC(npos) # <<<<<<<<<<<<<< @@ -3585,7 +3585,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_v_fil = __pyx_t_6; __pyx_v_col = __pyx_t_7; - /* "LCEngine.pyx":118 + /* "LCEngineV1.pyx":118 * liM = [] * fil, col = posFC(npos) * for fi, ci in ( (+1, +1), (+1, -1), (-1, +1), (-1, -1), (+1, 0), (-1, 0), (0, +1), (0, -1) ): # <<<<<<<<<<<<<< @@ -3633,7 +3633,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_XDECREF_SET(__pyx_v_ci, __pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":119 + /* "LCEngineV1.pyx":119 * fil, col = posFC(npos) * for fi, ci in ( (+1, +1), (+1, -1), (-1, +1), (-1, -1), (+1, 0), (-1, 0), (0, +1), (0, -1) ): * ft = fil + fi # <<<<<<<<<<<<<< @@ -3649,7 +3649,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; __pyx_v_ft = __pyx_t_7; - /* "LCEngine.pyx":120 + /* "LCEngineV1.pyx":120 * for fi, ci in ( (+1, +1), (+1, -1), (-1, +1), (-1, -1), (+1, 0), (-1, 0), (0, +1), (0, -1) ): * ft = fil + fi * ct = col + ci # <<<<<<<<<<<<<< @@ -3665,7 +3665,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; __pyx_v_ct = __pyx_t_7; - /* "LCEngine.pyx":121 + /* "LCEngineV1.pyx":121 * ft = fil + fi * ct = col + ci * if ft < 0 or ft > 7 or ct < 0 or ct > 7: # <<<<<<<<<<<<<< @@ -3695,7 +3695,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_L8_bool_binop_done:; if (__pyx_t_9) { - /* "LCEngine.pyx":122 + /* "LCEngineV1.pyx":122 * ct = col + ci * if ft < 0 or ft > 7 or ct < 0 or ct > 7: * continue # <<<<<<<<<<<<<< @@ -3704,7 +3704,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py */ goto __pyx_L5_continue; - /* "LCEngine.pyx":121 + /* "LCEngineV1.pyx":121 * ft = fil + fi * ct = col + ci * if ft < 0 or ft > 7 or ct < 0 or ct > 7: # <<<<<<<<<<<<<< @@ -3713,7 +3713,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py */ } - /* "LCEngine.pyx":123 + /* "LCEngineV1.pyx":123 * if ft < 0 or ft > 7 or ct < 0 or ct > 7: * continue * liM.append(FCpos(ft, ct)) # <<<<<<<<<<<<<< @@ -3778,7 +3778,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_t_14 = __Pyx_PyList_Append(__pyx_v_liM, __pyx_t_4); if (unlikely(__pyx_t_14 == -1)) __PYX_ERR(0, 123, __pyx_L1_error) __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":118 + /* "LCEngineV1.pyx":118 * liM = [] * fil, col = posFC(npos) * for fi, ci in ( (+1, +1), (+1, -1), (-1, +1), (-1, -1), (+1, 0), (-1, 0), (0, +1), (0, -1) ): # <<<<<<<<<<<<<< @@ -3789,7 +3789,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py } __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":124 + /* "LCEngineV1.pyx":124 * continue * liM.append(FCpos(ft, ct)) * return tuple(liM) # <<<<<<<<<<<<<< @@ -3803,7 +3803,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":114 + /* "LCEngineV1.pyx":114 * return a1 + h8 + q * * def liK(npos): # <<<<<<<<<<<<<< @@ -3820,7 +3820,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_XDECREF(__pyx_t_11); __Pyx_XDECREF(__pyx_t_12); __Pyx_XDECREF(__pyx_t_13); - __Pyx_AddTraceback("LCEngine.liK", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.liK", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_liM); @@ -3831,7 +3831,7 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py return __pyx_r; } -/* "LCEngine.pyx":127 +/* "LCEngineV1.pyx":127 * * * def liBR(npos, fi, ci): # <<<<<<<<<<<<<< @@ -3840,9 +3840,9 @@ static PyObject *__pyx_pf_8LCEngine_14liK(CYTHON_UNUSED PyObject *__pyx_self, Py */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_17liBR(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_17liBR = {"liBR", (PyCFunction)__pyx_pw_8LCEngine_17liBR, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_17liBR(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_17liBR(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_17liBR = {"liBR", (PyCFunction)__pyx_pw_10LCEngineV1_17liBR, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_17liBR(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_npos = 0; PyObject *__pyx_v_fi = 0; PyObject *__pyx_v_ci = 0; @@ -3896,18 +3896,18 @@ static PyObject *__pyx_pw_8LCEngine_17liBR(PyObject *__pyx_self, PyObject *__pyx __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("liBR", 1, 3, 3, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 127, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.liBR", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.liBR", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_16liBR(__pyx_self, __pyx_v_npos, __pyx_v_fi, __pyx_v_ci); + __pyx_r = __pyx_pf_10LCEngineV1_16liBR(__pyx_self, __pyx_v_npos, __pyx_v_fi, __pyx_v_ci); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos, PyObject *__pyx_v_fi, PyObject *__pyx_v_ci) { +static PyObject *__pyx_pf_10LCEngineV1_16liBR(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos, PyObject *__pyx_v_fi, PyObject *__pyx_v_ci) { int __pyx_v_fil; int __pyx_v_col; int __pyx_v_ft; @@ -3930,7 +3930,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P int __pyx_t_12; __Pyx_RefNannySetupContext("liBR", 0); - /* "LCEngine.pyx":130 + /* "LCEngineV1.pyx":130 * cdef int fil, col, ft, ct * * fil, col = posFC(npos) # <<<<<<<<<<<<<< @@ -4039,7 +4039,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P __pyx_v_fil = __pyx_t_6; __pyx_v_col = __pyx_t_7; - /* "LCEngine.pyx":131 + /* "LCEngineV1.pyx":131 * * fil, col = posFC(npos) * liM = [] # <<<<<<<<<<<<<< @@ -4051,7 +4051,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P __pyx_v_liM = ((PyObject*)__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":132 + /* "LCEngineV1.pyx":132 * fil, col = posFC(npos) * liM = [] * ft = fil + fi # <<<<<<<<<<<<<< @@ -4067,7 +4067,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; __pyx_v_ft = __pyx_t_7; - /* "LCEngine.pyx":133 + /* "LCEngineV1.pyx":133 * liM = [] * ft = fil + fi * ct = col + ci # <<<<<<<<<<<<<< @@ -4083,7 +4083,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; __pyx_v_ct = __pyx_t_7; - /* "LCEngine.pyx":134 + /* "LCEngineV1.pyx":134 * ft = fil + fi * ct = col + ci * while True: # <<<<<<<<<<<<<< @@ -4092,7 +4092,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P */ while (1) { - /* "LCEngine.pyx":135 + /* "LCEngineV1.pyx":135 * ct = col + ci * while True: * if ft < 0 or ft > 7 or ct < 0 or ct > 7: # <<<<<<<<<<<<<< @@ -4122,7 +4122,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P __pyx_L8_bool_binop_done:; if (__pyx_t_8) { - /* "LCEngine.pyx":136 + /* "LCEngineV1.pyx":136 * while True: * if ft < 0 or ft > 7 or ct < 0 or ct > 7: * break # <<<<<<<<<<<<<< @@ -4131,7 +4131,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P */ goto __pyx_L6_break; - /* "LCEngine.pyx":135 + /* "LCEngineV1.pyx":135 * ct = col + ci * while True: * if ft < 0 or ft > 7 or ct < 0 or ct > 7: # <<<<<<<<<<<<<< @@ -4140,7 +4140,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P */ } - /* "LCEngine.pyx":138 + /* "LCEngineV1.pyx":138 * break * * t = FCpos(ft, ct) # <<<<<<<<<<<<<< @@ -4205,7 +4205,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P __Pyx_XDECREF_SET(__pyx_v_t, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":139 + /* "LCEngineV1.pyx":139 * * t = FCpos(ft, ct) * liM.append(t) # <<<<<<<<<<<<<< @@ -4214,7 +4214,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P */ __pyx_t_12 = __Pyx_PyList_Append(__pyx_v_liM, __pyx_v_t); if (unlikely(__pyx_t_12 == -1)) __PYX_ERR(0, 139, __pyx_L1_error) - /* "LCEngine.pyx":140 + /* "LCEngineV1.pyx":140 * t = FCpos(ft, ct) * liM.append(t) * ft += fi # <<<<<<<<<<<<<< @@ -4230,7 +4230,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; __pyx_v_ft = __pyx_t_7; - /* "LCEngine.pyx":141 + /* "LCEngineV1.pyx":141 * liM.append(t) * ft += fi * ct += ci # <<<<<<<<<<<<<< @@ -4248,7 +4248,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P } __pyx_L6_break:; - /* "LCEngine.pyx":142 + /* "LCEngineV1.pyx":142 * ft += fi * ct += ci * return tuple(liM) # <<<<<<<<<<<<<< @@ -4262,7 +4262,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":127 + /* "LCEngineV1.pyx":127 * * * def liBR(npos, fi, ci): # <<<<<<<<<<<<<< @@ -4278,7 +4278,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P __Pyx_XDECREF(__pyx_t_4); __Pyx_XDECREF(__pyx_t_10); __Pyx_XDECREF(__pyx_t_11); - __Pyx_AddTraceback("LCEngine.liBR", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.liBR", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_liM); @@ -4288,7 +4288,7 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P return __pyx_r; } -/* "LCEngine.pyx":145 +/* "LCEngineV1.pyx":145 * * * def liN(npos): # <<<<<<<<<<<<<< @@ -4297,20 +4297,20 @@ static PyObject *__pyx_pf_8LCEngine_16liBR(CYTHON_UNUSED PyObject *__pyx_self, P */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_19liN(PyObject *__pyx_self, PyObject *__pyx_v_npos); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_19liN = {"liN", (PyCFunction)__pyx_pw_8LCEngine_19liN, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_19liN(PyObject *__pyx_self, PyObject *__pyx_v_npos) { +static PyObject *__pyx_pw_10LCEngineV1_19liN(PyObject *__pyx_self, PyObject *__pyx_v_npos); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_19liN = {"liN", (PyCFunction)__pyx_pw_10LCEngineV1_19liN, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_19liN(PyObject *__pyx_self, PyObject *__pyx_v_npos) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("liN (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_18liN(__pyx_self, ((PyObject *)__pyx_v_npos)); + __pyx_r = __pyx_pf_10LCEngineV1_18liN(__pyx_self, ((PyObject *)__pyx_v_npos)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos) { +static PyObject *__pyx_pf_10LCEngineV1_18liN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos) { int __pyx_v_fil; int __pyx_v_col; int __pyx_v_ft; @@ -4337,7 +4337,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py int __pyx_t_14; __Pyx_RefNannySetupContext("liN", 0); - /* "LCEngine.pyx":148 + /* "LCEngineV1.pyx":148 * cdef int fil, col, ft, ct * * fil, col = posFC(npos) # <<<<<<<<<<<<<< @@ -4446,7 +4446,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_v_fil = __pyx_t_6; __pyx_v_col = __pyx_t_7; - /* "LCEngine.pyx":149 + /* "LCEngineV1.pyx":149 * * fil, col = posFC(npos) * liM = [] # <<<<<<<<<<<<<< @@ -4458,7 +4458,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_v_liM = ((PyObject*)__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":150 + /* "LCEngineV1.pyx":150 * fil, col = posFC(npos) * liM = [] * for fi, ci in ( (+1, +2), (+1, -2), (-1, +2), (-1, -2), (+2, +1), (+2, -1), (-2, +1), (-2, -1) ): # <<<<<<<<<<<<<< @@ -4506,7 +4506,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_XDECREF_SET(__pyx_v_ci, __pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":151 + /* "LCEngineV1.pyx":151 * liM = [] * for fi, ci in ( (+1, +2), (+1, -2), (-1, +2), (-1, -2), (+2, +1), (+2, -1), (-2, +1), (-2, -1) ): * ft = fil + fi # <<<<<<<<<<<<<< @@ -4522,7 +4522,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; __pyx_v_ft = __pyx_t_7; - /* "LCEngine.pyx":152 + /* "LCEngineV1.pyx":152 * for fi, ci in ( (+1, +2), (+1, -2), (-1, +2), (-1, -2), (+2, +1), (+2, -1), (-2, +1), (-2, -1) ): * ft = fil + fi * ct = col + ci # <<<<<<<<<<<<<< @@ -4538,7 +4538,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; __pyx_v_ct = __pyx_t_7; - /* "LCEngine.pyx":153 + /* "LCEngineV1.pyx":153 * ft = fil + fi * ct = col + ci * if ft < 0 or ft > 7 or ct < 0 or ct > 7: # <<<<<<<<<<<<<< @@ -4568,7 +4568,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_L8_bool_binop_done:; if (__pyx_t_9) { - /* "LCEngine.pyx":154 + /* "LCEngineV1.pyx":154 * ct = col + ci * if ft < 0 or ft > 7 or ct < 0 or ct > 7: * continue # <<<<<<<<<<<<<< @@ -4577,7 +4577,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py */ goto __pyx_L5_continue; - /* "LCEngine.pyx":153 + /* "LCEngineV1.pyx":153 * ft = fil + fi * ct = col + ci * if ft < 0 or ft > 7 or ct < 0 or ct > 7: # <<<<<<<<<<<<<< @@ -4586,7 +4586,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py */ } - /* "LCEngine.pyx":156 + /* "LCEngineV1.pyx":156 * continue * * t = FCpos(ft, ct) # <<<<<<<<<<<<<< @@ -4651,7 +4651,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_XDECREF_SET(__pyx_v_t, __pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":157 + /* "LCEngineV1.pyx":157 * * t = FCpos(ft, ct) * liM.append(t) # <<<<<<<<<<<<<< @@ -4660,7 +4660,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py */ __pyx_t_14 = __Pyx_PyList_Append(__pyx_v_liM, __pyx_v_t); if (unlikely(__pyx_t_14 == -1)) __PYX_ERR(0, 157, __pyx_L1_error) - /* "LCEngine.pyx":150 + /* "LCEngineV1.pyx":150 * fil, col = posFC(npos) * liM = [] * for fi, ci in ( (+1, +2), (+1, -2), (-1, +2), (-1, -2), (+2, +1), (+2, -1), (-2, +1), (-2, -1) ): # <<<<<<<<<<<<<< @@ -4671,7 +4671,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py } __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":158 + /* "LCEngineV1.pyx":158 * t = FCpos(ft, ct) * liM.append(t) * return tuple(liM) # <<<<<<<<<<<<<< @@ -4685,7 +4685,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":145 + /* "LCEngineV1.pyx":145 * * * def liN(npos): # <<<<<<<<<<<<<< @@ -4702,7 +4702,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_XDECREF(__pyx_t_11); __Pyx_XDECREF(__pyx_t_12); __Pyx_XDECREF(__pyx_t_13); - __Pyx_AddTraceback("LCEngine.liN", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.liN", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_liM); @@ -4714,7 +4714,7 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py return __pyx_r; } -/* "LCEngine.pyx":160 +/* "LCEngineV1.pyx":160 * return tuple(liM) * * def liP(npos, siW): # <<<<<<<<<<<<<< @@ -4723,9 +4723,9 @@ static PyObject *__pyx_pf_8LCEngine_18liN(CYTHON_UNUSED PyObject *__pyx_self, Py */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_21liP(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_21liP = {"liP", (PyCFunction)__pyx_pw_8LCEngine_21liP, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_21liP(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_21liP(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_21liP = {"liP", (PyCFunction)__pyx_pw_10LCEngineV1_21liP, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_21liP(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_npos = 0; PyObject *__pyx_v_siW = 0; PyObject *__pyx_r = 0; @@ -4770,18 +4770,18 @@ static PyObject *__pyx_pw_8LCEngine_21liP(PyObject *__pyx_self, PyObject *__pyx_ __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("liP", 1, 2, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 160, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.liP", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.liP", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_20liP(__pyx_self, __pyx_v_npos, __pyx_v_siW); + __pyx_r = __pyx_pf_10LCEngineV1_20liP(__pyx_self, __pyx_v_npos, __pyx_v_siW); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos, PyObject *__pyx_v_siW) { +static PyObject *__pyx_pf_10LCEngineV1_20liP(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_npos, PyObject *__pyx_v_siW) { int __pyx_v_fil; int __pyx_v_col; int __pyx_v_ft; @@ -4812,7 +4812,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py PyObject *__pyx_t_14 = NULL; __Pyx_RefNannySetupContext("liP", 0); - /* "LCEngine.pyx":163 + /* "LCEngineV1.pyx":163 * cdef int fil, col, ft, ct, inc * * fil, col = posFC(npos) # <<<<<<<<<<<<<< @@ -4921,7 +4921,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_v_fil = __pyx_t_6; __pyx_v_col = __pyx_t_7; - /* "LCEngine.pyx":164 + /* "LCEngineV1.pyx":164 * * fil, col = posFC(npos) * liM = [] # <<<<<<<<<<<<<< @@ -4933,7 +4933,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_v_liM = ((PyObject*)__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":165 + /* "LCEngineV1.pyx":165 * fil, col = posFC(npos) * liM = [] * liX = [] # <<<<<<<<<<<<<< @@ -4945,7 +4945,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_v_liX = ((PyObject*)__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":166 + /* "LCEngineV1.pyx":166 * liM = [] * liX = [] * if siW: # <<<<<<<<<<<<<< @@ -4955,7 +4955,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_t_8 = __Pyx_PyObject_IsTrue(__pyx_v_siW); if (unlikely(__pyx_t_8 < 0)) __PYX_ERR(0, 166, __pyx_L1_error) if (__pyx_t_8) { - /* "LCEngine.pyx":167 + /* "LCEngineV1.pyx":167 * liX = [] * if siW: * filaIni = 1 # <<<<<<<<<<<<<< @@ -4964,7 +4964,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py */ __pyx_v_filaIni = 1; - /* "LCEngine.pyx":168 + /* "LCEngineV1.pyx":168 * if siW: * filaIni = 1 * salto = +1 # <<<<<<<<<<<<<< @@ -4974,7 +4974,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_INCREF(__pyx_int_1); __pyx_v_salto = __pyx_int_1; - /* "LCEngine.pyx":166 + /* "LCEngineV1.pyx":166 * liM = [] * liX = [] * if siW: # <<<<<<<<<<<<<< @@ -4984,7 +4984,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py goto __pyx_L5; } - /* "LCEngine.pyx":170 + /* "LCEngineV1.pyx":170 * salto = +1 * else: * filaIni = 6 # <<<<<<<<<<<<<< @@ -4994,7 +4994,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py /*else*/ { __pyx_v_filaIni = 6; - /* "LCEngine.pyx":171 + /* "LCEngineV1.pyx":171 * else: * filaIni = 6 * salto = -1 # <<<<<<<<<<<<<< @@ -5006,7 +5006,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py } __pyx_L5:; - /* "LCEngine.pyx":172 + /* "LCEngineV1.pyx":172 * filaIni = 6 * salto = -1 * sig = FCpos(fil + salto, col) # <<<<<<<<<<<<<< @@ -5074,7 +5074,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_v_sig = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":173 + /* "LCEngineV1.pyx":173 * salto = -1 * sig = FCpos(fil + salto, col) * liM.append(sig) # <<<<<<<<<<<<<< @@ -5083,7 +5083,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py */ __pyx_t_11 = __Pyx_PyList_Append(__pyx_v_liM, __pyx_v_sig); if (unlikely(__pyx_t_11 == -1)) __PYX_ERR(0, 173, __pyx_L1_error) - /* "LCEngine.pyx":175 + /* "LCEngineV1.pyx":175 * liM.append(sig) * * if fil == filaIni: # <<<<<<<<<<<<<< @@ -5093,7 +5093,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_t_8 = ((__pyx_v_fil == __pyx_v_filaIni) != 0); if (__pyx_t_8) { - /* "LCEngine.pyx":176 + /* "LCEngineV1.pyx":176 * * if fil == filaIni: * sig2 = FCpos(fil + salto * 2, col) # <<<<<<<<<<<<<< @@ -5164,7 +5164,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_v_sig2 = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":177 + /* "LCEngineV1.pyx":177 * if fil == filaIni: * sig2 = FCpos(fil + salto * 2, col) * liM.append(sig2) # <<<<<<<<<<<<<< @@ -5173,7 +5173,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py */ __pyx_t_11 = __Pyx_PyList_Append(__pyx_v_liM, __pyx_v_sig2); if (unlikely(__pyx_t_11 == -1)) __PYX_ERR(0, 177, __pyx_L1_error) - /* "LCEngine.pyx":175 + /* "LCEngineV1.pyx":175 * liM.append(sig) * * if fil == filaIni: # <<<<<<<<<<<<<< @@ -5182,7 +5182,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py */ } - /* "LCEngine.pyx":179 + /* "LCEngineV1.pyx":179 * liM.append(sig2) * * for inc in ( +1, -1 ): # <<<<<<<<<<<<<< @@ -5202,7 +5202,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; __pyx_v_inc = __pyx_t_7; - /* "LCEngine.pyx":180 + /* "LCEngineV1.pyx":180 * * for inc in ( +1, -1 ): * ft = fil + salto # <<<<<<<<<<<<<< @@ -5218,7 +5218,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_DECREF(__pyx_t_9); __pyx_t_9 = 0; __pyx_v_ft = __pyx_t_7; - /* "LCEngine.pyx":181 + /* "LCEngineV1.pyx":181 * for inc in ( +1, -1 ): * ft = fil + salto * ct = col + inc # <<<<<<<<<<<<<< @@ -5227,7 +5227,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py */ __pyx_v_ct = (__pyx_v_col + __pyx_v_inc); - /* "LCEngine.pyx":182 + /* "LCEngineV1.pyx":182 * ft = fil + salto * ct = col + inc * if not (ft < 0 or ft > 7 or ct < 0 or ct > 7): # <<<<<<<<<<<<<< @@ -5258,7 +5258,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_t_13 = ((!__pyx_t_8) != 0); if (__pyx_t_13) { - /* "LCEngine.pyx":183 + /* "LCEngineV1.pyx":183 * ct = col + inc * if not (ft < 0 or ft > 7 or ct < 0 or ct > 7): * t = FCpos(ft, ct) # <<<<<<<<<<<<<< @@ -5323,7 +5323,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_XDECREF_SET(__pyx_v_t, __pyx_t_9); __pyx_t_9 = 0; - /* "LCEngine.pyx":184 + /* "LCEngineV1.pyx":184 * if not (ft < 0 or ft > 7 or ct < 0 or ct > 7): * t = FCpos(ft, ct) * liX.append(t) # <<<<<<<<<<<<<< @@ -5332,7 +5332,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py */ __pyx_t_11 = __Pyx_PyList_Append(__pyx_v_liX, __pyx_v_t); if (unlikely(__pyx_t_11 == -1)) __PYX_ERR(0, 184, __pyx_L1_error) - /* "LCEngine.pyx":182 + /* "LCEngineV1.pyx":182 * ft = fil + salto * ct = col + inc * if not (ft < 0 or ft > 7 or ct < 0 or ct > 7): # <<<<<<<<<<<<<< @@ -5341,7 +5341,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py */ } - /* "LCEngine.pyx":179 + /* "LCEngineV1.pyx":179 * liM.append(sig2) * * for inc in ( +1, -1 ): # <<<<<<<<<<<<<< @@ -5351,7 +5351,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py } __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":186 + /* "LCEngineV1.pyx":186 * liX.append(t) * * return tuple(liM), tuple(liX) # <<<<<<<<<<<<<< @@ -5375,7 +5375,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __pyx_t_4 = 0; goto __pyx_L0; - /* "LCEngine.pyx":160 + /* "LCEngineV1.pyx":160 * return tuple(liM) * * def liP(npos, siW): # <<<<<<<<<<<<<< @@ -5392,7 +5392,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py __Pyx_XDECREF(__pyx_t_9); __Pyx_XDECREF(__pyx_t_10); __Pyx_XDECREF(__pyx_t_14); - __Pyx_AddTraceback("LCEngine.liP", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.liP", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_liM); @@ -5406,7 +5406,7 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py return __pyx_r; } -/* "LCEngine.pyx":231 +/* "LCEngineV1.pyx":231 * dicPB[i] = liP(i, False) * * def knightmoves(a, b, no, nv, mx): # <<<<<<<<<<<<<< @@ -5415,9 +5415,9 @@ static PyObject *__pyx_pf_8LCEngine_20liP(CYTHON_UNUSED PyObject *__pyx_self, Py */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_23knightmoves(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_23knightmoves = {"knightmoves", (PyCFunction)__pyx_pw_8LCEngine_23knightmoves, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_23knightmoves(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_23knightmoves(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_23knightmoves = {"knightmoves", (PyCFunction)__pyx_pw_10LCEngineV1_23knightmoves, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_23knightmoves(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_a = 0; PyObject *__pyx_v_b = 0; PyObject *__pyx_v_no = 0; @@ -5489,18 +5489,18 @@ static PyObject *__pyx_pw_8LCEngine_23knightmoves(PyObject *__pyx_self, PyObject __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("knightmoves", 1, 5, 5, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 231, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.knightmoves", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.knightmoves", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_22knightmoves(__pyx_self, __pyx_v_a, __pyx_v_b, __pyx_v_no, __pyx_v_nv, __pyx_v_mx); + __pyx_r = __pyx_pf_10LCEngineV1_22knightmoves(__pyx_self, __pyx_v_a, __pyx_v_b, __pyx_v_no, __pyx_v_nv, __pyx_v_mx); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_no, PyObject *__pyx_v_nv, PyObject *__pyx_v_mx) { +static PyObject *__pyx_pf_10LCEngineV1_22knightmoves(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_a, PyObject *__pyx_v_b, PyObject *__pyx_v_no, PyObject *__pyx_v_nv, PyObject *__pyx_v_mx) { PyObject *__pyx_v_lia = NULL; PyObject *__pyx_v_lib = NULL; PyObject *__pyx_v_li = NULL; @@ -5530,7 +5530,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ PyObject *__pyx_t_16 = NULL; __Pyx_RefNannySetupContext("knightmoves", 0); - /* "LCEngine.pyx":232 + /* "LCEngineV1.pyx":232 * * def knightmoves(a, b, no, nv, mx): * if nv > mx: # <<<<<<<<<<<<<< @@ -5542,7 +5542,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; if (__pyx_t_2) { - /* "LCEngine.pyx":233 + /* "LCEngineV1.pyx":233 * def knightmoves(a, b, no, nv, mx): * if nv > mx: * return [] # <<<<<<<<<<<<<< @@ -5556,7 +5556,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":232 + /* "LCEngineV1.pyx":232 * * def knightmoves(a, b, no, nv, mx): * if nv > mx: # <<<<<<<<<<<<<< @@ -5565,7 +5565,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ } - /* "LCEngine.pyx":234 + /* "LCEngineV1.pyx":234 * if nv > mx: * return [] * lia = liN(a) # <<<<<<<<<<<<<< @@ -5620,7 +5620,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_v_lia = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":235 + /* "LCEngineV1.pyx":235 * return [] * lia = liN(a) * if b in lia: # <<<<<<<<<<<<<< @@ -5631,7 +5631,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_6 = (__pyx_t_2 != 0); if (__pyx_t_6) { - /* "LCEngine.pyx":236 + /* "LCEngineV1.pyx":236 * lia = liN(a) * if b in lia: * return [[a, b]] # <<<<<<<<<<<<<< @@ -5656,7 +5656,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_3 = 0; goto __pyx_L0; - /* "LCEngine.pyx":235 + /* "LCEngineV1.pyx":235 * return [] * lia = liN(a) * if b in lia: # <<<<<<<<<<<<<< @@ -5665,7 +5665,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ } - /* "LCEngine.pyx":237 + /* "LCEngineV1.pyx":237 * if b in lia: * return [[a, b]] * lib = liN(b) # <<<<<<<<<<<<<< @@ -5720,7 +5720,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_v_lib = __pyx_t_3; __pyx_t_3 = 0; - /* "LCEngine.pyx":238 + /* "LCEngineV1.pyx":238 * return [[a, b]] * lib = liN(b) * li = [] # <<<<<<<<<<<<<< @@ -5732,7 +5732,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_v_li = ((PyObject*)__pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":239 + /* "LCEngineV1.pyx":239 * lib = liN(b) * li = [] * for x in lia: # <<<<<<<<<<<<<< @@ -5781,7 +5781,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_XDECREF_SET(__pyx_v_x, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":240 + /* "LCEngineV1.pyx":240 * li = [] * for x in lia: * if x not in no and x in lib: # <<<<<<<<<<<<<< @@ -5801,7 +5801,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_L8_bool_binop_done:; if (__pyx_t_6) { - /* "LCEngine.pyx":241 + /* "LCEngineV1.pyx":241 * for x in lia: * if x not in no and x in lib: * li.append([a, x, b]) # <<<<<<<<<<<<<< @@ -5822,7 +5822,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_10 = __Pyx_PyList_Append(__pyx_v_li, __pyx_t_1); if (unlikely(__pyx_t_10 == -1)) __PYX_ERR(0, 241, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":240 + /* "LCEngineV1.pyx":240 * li = [] * for x in lia: * if x not in no and x in lib: # <<<<<<<<<<<<<< @@ -5831,7 +5831,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ } - /* "LCEngine.pyx":239 + /* "LCEngineV1.pyx":239 * lib = liN(b) * li = [] * for x in lia: # <<<<<<<<<<<<<< @@ -5841,7 +5841,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ } __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":242 + /* "LCEngineV1.pyx":242 * if x not in no and x in lib: * li.append([a, x, b]) * if li: # <<<<<<<<<<<<<< @@ -5851,7 +5851,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_6 = (__pyx_v_li != Py_None) && (PyList_GET_SIZE(__pyx_v_li) != 0); if (__pyx_t_6) { - /* "LCEngine.pyx":243 + /* "LCEngineV1.pyx":243 * li.append([a, x, b]) * if li: * return li # <<<<<<<<<<<<<< @@ -5863,7 +5863,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_r = __pyx_v_li; goto __pyx_L0; - /* "LCEngine.pyx":242 + /* "LCEngineV1.pyx":242 * if x not in no and x in lib: * li.append([a, x, b]) * if li: # <<<<<<<<<<<<<< @@ -5872,7 +5872,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ } - /* "LCEngine.pyx":245 + /* "LCEngineV1.pyx":245 * return li * * li = [] # <<<<<<<<<<<<<< @@ -5884,7 +5884,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_DECREF_SET(__pyx_v_li, ((PyObject*)__pyx_t_3)); __pyx_t_3 = 0; - /* "LCEngine.pyx":247 + /* "LCEngineV1.pyx":247 * li = [] * * for x in lia: # <<<<<<<<<<<<<< @@ -5933,7 +5933,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_XDECREF_SET(__pyx_v_x, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":248 + /* "LCEngineV1.pyx":248 * * for x in lia: * for y in lib: # <<<<<<<<<<<<<< @@ -5982,7 +5982,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_XDECREF_SET(__pyx_v_y, __pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":249 + /* "LCEngineV1.pyx":249 * for x in lia: * for y in lib: * if x not in no and y not in no: # <<<<<<<<<<<<<< @@ -6002,7 +6002,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_L16_bool_binop_done:; if (__pyx_t_6) { - /* "LCEngine.pyx":250 + /* "LCEngineV1.pyx":250 * for y in lib: * if x not in no and y not in no: * nx = no[:] # <<<<<<<<<<<<<< @@ -6014,7 +6014,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_XDECREF_SET(__pyx_v_nx, __pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":251 + /* "LCEngineV1.pyx":251 * if x not in no and y not in no: * nx = no[:] * nx.append(x) # <<<<<<<<<<<<<< @@ -6023,7 +6023,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ __pyx_t_10 = __Pyx_PyObject_Append(__pyx_v_nx, __pyx_v_x); if (unlikely(__pyx_t_10 == -1)) __PYX_ERR(0, 251, __pyx_L1_error) - /* "LCEngine.pyx":252 + /* "LCEngineV1.pyx":252 * nx = no[:] * nx.append(x) * nx.append(y) # <<<<<<<<<<<<<< @@ -6032,7 +6032,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ __pyx_t_10 = __Pyx_PyObject_Append(__pyx_v_nx, __pyx_v_y); if (unlikely(__pyx_t_10 == -1)) __PYX_ERR(0, 252, __pyx_L1_error) - /* "LCEngine.pyx":253 + /* "LCEngineV1.pyx":253 * nx.append(x) * nx.append(y) * f = knightmoves(x, y, nx, nv + 1, mx) # <<<<<<<<<<<<<< @@ -6102,7 +6102,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_XDECREF_SET(__pyx_v_f, __pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":254 + /* "LCEngineV1.pyx":254 * nx.append(y) * f = knightmoves(x, y, nx, nv + 1, mx) * if f: # <<<<<<<<<<<<<< @@ -6112,7 +6112,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_6 = __Pyx_PyObject_IsTrue(__pyx_v_f); if (unlikely(__pyx_t_6 < 0)) __PYX_ERR(0, 254, __pyx_L1_error) if (__pyx_t_6) { - /* "LCEngine.pyx":255 + /* "LCEngineV1.pyx":255 * f = knightmoves(x, y, nx, nv + 1, mx) * if f: * li.extend(f) # <<<<<<<<<<<<<< @@ -6121,7 +6121,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ __pyx_t_10 = __Pyx_PyList_Extend(__pyx_v_li, __pyx_v_f); if (unlikely(__pyx_t_10 == -1)) __PYX_ERR(0, 255, __pyx_L1_error) - /* "LCEngine.pyx":254 + /* "LCEngineV1.pyx":254 * nx.append(y) * f = knightmoves(x, y, nx, nv + 1, mx) * if f: # <<<<<<<<<<<<<< @@ -6130,7 +6130,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ } - /* "LCEngine.pyx":249 + /* "LCEngineV1.pyx":249 * for x in lia: * for y in lib: * if x not in no and y not in no: # <<<<<<<<<<<<<< @@ -6139,7 +6139,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ } - /* "LCEngine.pyx":248 + /* "LCEngineV1.pyx":248 * * for x in lia: * for y in lib: # <<<<<<<<<<<<<< @@ -6149,7 +6149,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ } __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":247 + /* "LCEngineV1.pyx":247 * li = [] * * for x in lia: # <<<<<<<<<<<<<< @@ -6159,7 +6159,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ } __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":256 + /* "LCEngineV1.pyx":256 * if f: * li.extend(f) * if not li: # <<<<<<<<<<<<<< @@ -6170,7 +6170,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_2 = ((!__pyx_t_6) != 0); if (__pyx_t_2) { - /* "LCEngine.pyx":257 + /* "LCEngineV1.pyx":257 * li.extend(f) * if not li: * return li # <<<<<<<<<<<<<< @@ -6182,7 +6182,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_r = __pyx_v_li; goto __pyx_L0; - /* "LCEngine.pyx":256 + /* "LCEngineV1.pyx":256 * if f: * li.extend(f) * if not li: # <<<<<<<<<<<<<< @@ -6191,7 +6191,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ } - /* "LCEngine.pyx":258 + /* "LCEngineV1.pyx":258 * if not li: * return li * xmin = 9999 # <<<<<<<<<<<<<< @@ -6200,7 +6200,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ __pyx_v_xmin = 0x270F; - /* "LCEngine.pyx":259 + /* "LCEngineV1.pyx":259 * return li * xmin = 9999 * for x in li: # <<<<<<<<<<<<<< @@ -6219,7 +6219,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_XDECREF_SET(__pyx_v_x, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":260 + /* "LCEngineV1.pyx":260 * xmin = 9999 * for x in li: * nx = len(x) # <<<<<<<<<<<<<< @@ -6232,7 +6232,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_XDECREF_SET(__pyx_v_nx, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":261 + /* "LCEngineV1.pyx":261 * for x in li: * nx = len(x) * if nx < xmin: # <<<<<<<<<<<<<< @@ -6247,7 +6247,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; if (__pyx_t_2) { - /* "LCEngine.pyx":262 + /* "LCEngineV1.pyx":262 * nx = len(x) * if nx < xmin: * xmin = nx # <<<<<<<<<<<<<< @@ -6257,7 +6257,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_11 = __Pyx_PyIndex_AsSsize_t(__pyx_v_nx); if (unlikely((__pyx_t_11 == (Py_ssize_t)-1) && PyErr_Occurred())) __PYX_ERR(0, 262, __pyx_L1_error) __pyx_v_xmin = __pyx_t_11; - /* "LCEngine.pyx":261 + /* "LCEngineV1.pyx":261 * for x in li: * nx = len(x) * if nx < xmin: # <<<<<<<<<<<<<< @@ -6266,7 +6266,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ } - /* "LCEngine.pyx":259 + /* "LCEngineV1.pyx":259 * return li * xmin = 9999 * for x in li: # <<<<<<<<<<<<<< @@ -6276,7 +6276,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ } __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":263 + /* "LCEngineV1.pyx":263 * if nx < xmin: * xmin = nx * lidef = [] # <<<<<<<<<<<<<< @@ -6288,7 +6288,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_v_lidef = ((PyObject*)__pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":264 + /* "LCEngineV1.pyx":264 * xmin = nx * lidef = [] * for x in li: # <<<<<<<<<<<<<< @@ -6307,7 +6307,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_XDECREF_SET(__pyx_v_x, __pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":265 + /* "LCEngineV1.pyx":265 * lidef = [] * for x in li: * if len(x) == xmin: # <<<<<<<<<<<<<< @@ -6318,7 +6318,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_2 = ((__pyx_t_11 == __pyx_v_xmin) != 0); if (__pyx_t_2) { - /* "LCEngine.pyx":266 + /* "LCEngineV1.pyx":266 * for x in li: * if len(x) == xmin: * x.insert(0, a) # <<<<<<<<<<<<<< @@ -6374,7 +6374,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":267 + /* "LCEngineV1.pyx":267 * if len(x) == xmin: * x.insert(0, a) * x.append(b) # <<<<<<<<<<<<<< @@ -6383,7 +6383,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ __pyx_t_10 = __Pyx_PyObject_Append(__pyx_v_x, __pyx_v_b); if (unlikely(__pyx_t_10 == -1)) __PYX_ERR(0, 267, __pyx_L1_error) - /* "LCEngine.pyx":268 + /* "LCEngineV1.pyx":268 * x.insert(0, a) * x.append(b) * lidef.append(x) # <<<<<<<<<<<<<< @@ -6392,7 +6392,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ __pyx_t_10 = __Pyx_PyList_Append(__pyx_v_lidef, __pyx_v_x); if (unlikely(__pyx_t_10 == -1)) __PYX_ERR(0, 268, __pyx_L1_error) - /* "LCEngine.pyx":265 + /* "LCEngineV1.pyx":265 * lidef = [] * for x in li: * if len(x) == xmin: # <<<<<<<<<<<<<< @@ -6401,7 +6401,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ } - /* "LCEngine.pyx":264 + /* "LCEngineV1.pyx":264 * xmin = nx * lidef = [] * for x in li: # <<<<<<<<<<<<<< @@ -6411,7 +6411,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ } __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":269 + /* "LCEngineV1.pyx":269 * x.append(b) * lidef.append(x) * return lidef # <<<<<<<<<<<<<< @@ -6423,7 +6423,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __pyx_r = __pyx_v_lidef; goto __pyx_L0; - /* "LCEngine.pyx":231 + /* "LCEngineV1.pyx":231 * dicPB[i] = liP(i, False) * * def knightmoves(a, b, no, nv, mx): # <<<<<<<<<<<<<< @@ -6440,7 +6440,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ __Pyx_XDECREF(__pyx_t_13); __Pyx_XDECREF(__pyx_t_14); __Pyx_XDECREF(__pyx_t_16); - __Pyx_AddTraceback("LCEngine.knightmoves", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.knightmoves", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_lia); @@ -6456,7 +6456,7 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ return __pyx_r; } -/* "LCEngine.pyx":271 +/* "LCEngineV1.pyx":271 * return lidef * * def liNMinimo(x, y, celdas_ocupadas): # <<<<<<<<<<<<<< @@ -6465,9 +6465,9 @@ static PyObject *__pyx_pf_8LCEngine_22knightmoves(CYTHON_UNUSED PyObject *__pyx_ */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_25liNMinimo(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_25liNMinimo = {"liNMinimo", (PyCFunction)__pyx_pw_8LCEngine_25liNMinimo, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_25liNMinimo(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_25liNMinimo(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_25liNMinimo = {"liNMinimo", (PyCFunction)__pyx_pw_10LCEngineV1_25liNMinimo, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_25liNMinimo(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_x = 0; PyObject *__pyx_v_y = 0; PyObject *__pyx_v_celdas_ocupadas = 0; @@ -6521,18 +6521,18 @@ static PyObject *__pyx_pw_8LCEngine_25liNMinimo(PyObject *__pyx_self, PyObject * __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("liNMinimo", 1, 3, 3, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 271, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.liNMinimo", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.liNMinimo", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_24liNMinimo(__pyx_self, __pyx_v_x, __pyx_v_y, __pyx_v_celdas_ocupadas); + __pyx_r = __pyx_pf_10LCEngineV1_24liNMinimo(__pyx_self, __pyx_v_x, __pyx_v_y, __pyx_v_celdas_ocupadas); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_x, PyObject *__pyx_v_y, PyObject *__pyx_v_celdas_ocupadas) { +static PyObject *__pyx_pf_10LCEngineV1_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_x, PyObject *__pyx_v_y, PyObject *__pyx_v_celdas_ocupadas) { int __pyx_v_nv; PyObject *__pyx_v_ot = NULL; PyObject *__pyx_v_li = NULL; @@ -6548,7 +6548,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se int __pyx_t_8; __Pyx_RefNannySetupContext("liNMinimo", 0); - /* "LCEngine.pyx":273 + /* "LCEngineV1.pyx":273 * def liNMinimo(x, y, celdas_ocupadas): * cdef int nv * ot = celdas_ocupadas[:] # <<<<<<<<<<<<<< @@ -6560,7 +6560,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se __pyx_v_ot = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":274 + /* "LCEngineV1.pyx":274 * cdef int nv * ot = celdas_ocupadas[:] * ot.extend([x, y]) # <<<<<<<<<<<<<< @@ -6625,7 +6625,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":275 + /* "LCEngineV1.pyx":275 * ot = celdas_ocupadas[:] * ot.extend([x, y]) * nv = 1 # <<<<<<<<<<<<<< @@ -6634,7 +6634,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se */ __pyx_v_nv = 1; - /* "LCEngine.pyx":276 + /* "LCEngineV1.pyx":276 * ot.extend([x, y]) * nv = 1 * li = knightmoves(x, y, ot, 0, nv) # <<<<<<<<<<<<<< @@ -6704,7 +6704,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se __pyx_v_li = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":277 + /* "LCEngineV1.pyx":277 * nv = 1 * li = knightmoves(x, y, ot, 0, nv) * while len(li) == 0: # <<<<<<<<<<<<<< @@ -6716,7 +6716,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se __pyx_t_8 = ((__pyx_t_7 == 0) != 0); if (!__pyx_t_8) break; - /* "LCEngine.pyx":278 + /* "LCEngineV1.pyx":278 * li = knightmoves(x, y, ot, 0, nv) * while len(li) == 0: * nv += 1 # <<<<<<<<<<<<<< @@ -6725,7 +6725,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se */ __pyx_v_nv = (__pyx_v_nv + 1); - /* "LCEngine.pyx":279 + /* "LCEngineV1.pyx":279 * while len(li) == 0: * nv += 1 * li = knightmoves(x, y, ot, 0, nv) # <<<<<<<<<<<<<< @@ -6796,7 +6796,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se __pyx_t_1 = 0; } - /* "LCEngine.pyx":280 + /* "LCEngineV1.pyx":280 * nv += 1 * li = knightmoves(x, y, ot, 0, nv) * return li # <<<<<<<<<<<<<< @@ -6808,7 +6808,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se __pyx_r = __pyx_v_li; goto __pyx_L0; - /* "LCEngine.pyx":271 + /* "LCEngineV1.pyx":271 * return lidef * * def liNMinimo(x, y, celdas_ocupadas): # <<<<<<<<<<<<<< @@ -6823,7 +6823,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_4); __Pyx_XDECREF(__pyx_t_5); - __Pyx_AddTraceback("LCEngine.liNMinimo", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.liNMinimo", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_ot); @@ -6833,7 +6833,7 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se return __pyx_r; } -/* "LCEngine.pyx":282 +/* "LCEngineV1.pyx":282 * return li * * def xpv2lipv(xpv): # <<<<<<<<<<<<<< @@ -6842,20 +6842,20 @@ static PyObject *__pyx_pf_8LCEngine_24liNMinimo(CYTHON_UNUSED PyObject *__pyx_se */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_27xpv2lipv(PyObject *__pyx_self, PyObject *__pyx_v_xpv); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_27xpv2lipv = {"xpv2lipv", (PyCFunction)__pyx_pw_8LCEngine_27xpv2lipv, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_27xpv2lipv(PyObject *__pyx_self, PyObject *__pyx_v_xpv) { +static PyObject *__pyx_pw_10LCEngineV1_27xpv2lipv(PyObject *__pyx_self, PyObject *__pyx_v_xpv); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_27xpv2lipv = {"xpv2lipv", (PyCFunction)__pyx_pw_10LCEngineV1_27xpv2lipv, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_27xpv2lipv(PyObject *__pyx_self, PyObject *__pyx_v_xpv) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("xpv2lipv (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_26xpv2lipv(__pyx_self, ((PyObject *)__pyx_v_xpv)); + __pyx_r = __pyx_pf_10LCEngineV1_26xpv2lipv(__pyx_self, ((PyObject *)__pyx_v_xpv)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv) { +static PyObject *__pyx_pf_10LCEngineV1_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv) { PyObject *__pyx_v_li = NULL; int __pyx_v_siBlancas; PyObject *__pyx_v_c = NULL; @@ -6878,7 +6878,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel Py_ssize_t __pyx_t_12; __Pyx_RefNannySetupContext("xpv2lipv", 0); - /* "LCEngine.pyx":283 + /* "LCEngineV1.pyx":283 * * def xpv2lipv(xpv): * li = [] # <<<<<<<<<<<<<< @@ -6890,7 +6890,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel __pyx_v_li = ((PyObject*)__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":284 + /* "LCEngineV1.pyx":284 * def xpv2lipv(xpv): * li = [] * siBlancas = True # <<<<<<<<<<<<<< @@ -6899,7 +6899,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel */ __pyx_v_siBlancas = 1; - /* "LCEngine.pyx":285 + /* "LCEngineV1.pyx":285 * li = [] * siBlancas = True * for c in xpv: # <<<<<<<<<<<<<< @@ -6948,7 +6948,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_XDECREF_SET(__pyx_v_c, __pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":286 + /* "LCEngineV1.pyx":286 * siBlancas = True * for c in xpv: * x = ord(c) # <<<<<<<<<<<<<< @@ -6961,7 +6961,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_XDECREF_SET(__pyx_v_x, __pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":287 + /* "LCEngineV1.pyx":287 * for c in xpv: * x = ord(c) * if x >= 58: # <<<<<<<<<<<<<< @@ -6973,7 +6973,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0; if (__pyx_t_6) { - /* "LCEngine.pyx":288 + /* "LCEngineV1.pyx":288 * x = ord(c) * if x >= 58: * move = posA1(x - 58) # <<<<<<<<<<<<<< @@ -7033,7 +7033,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_XDECREF_SET(__pyx_v_move, __pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":289 + /* "LCEngineV1.pyx":289 * if x >= 58: * move = posA1(x - 58) * if siBlancas: # <<<<<<<<<<<<<< @@ -7043,7 +7043,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel __pyx_t_6 = (__pyx_v_siBlancas != 0); if (__pyx_t_6) { - /* "LCEngine.pyx":290 + /* "LCEngineV1.pyx":290 * move = posA1(x - 58) * if siBlancas: * base = move # <<<<<<<<<<<<<< @@ -7053,7 +7053,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_INCREF(__pyx_v_move); __Pyx_XDECREF_SET(__pyx_v_base, __pyx_v_move); - /* "LCEngine.pyx":289 + /* "LCEngineV1.pyx":289 * if x >= 58: * move = posA1(x - 58) * if siBlancas: # <<<<<<<<<<<<<< @@ -7063,7 +7063,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel goto __pyx_L6; } - /* "LCEngine.pyx":292 + /* "LCEngineV1.pyx":292 * base = move * else: * li.append(base + move) # <<<<<<<<<<<<<< @@ -7079,7 +7079,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel } __pyx_L6:; - /* "LCEngine.pyx":293 + /* "LCEngineV1.pyx":293 * else: * li.append(base + move) * siBlancas = not siBlancas # <<<<<<<<<<<<<< @@ -7088,7 +7088,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel */ __pyx_v_siBlancas = (!(__pyx_v_siBlancas != 0)); - /* "LCEngine.pyx":287 + /* "LCEngineV1.pyx":287 * for c in xpv: * x = ord(c) * if x >= 58: # <<<<<<<<<<<<<< @@ -7098,7 +7098,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel goto __pyx_L5; } - /* "LCEngine.pyx":295 + /* "LCEngineV1.pyx":295 * siBlancas = not siBlancas * else: * c = {50: "q", 51: "r", 52: "b", 53: "n"}.get(x, "") # <<<<<<<<<<<<<< @@ -7118,7 +7118,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_DECREF_SET(__pyx_v_c, __pyx_t_7); __pyx_t_7 = 0; - /* "LCEngine.pyx":296 + /* "LCEngineV1.pyx":296 * else: * c = {50: "q", 51: "r", 52: "b", 53: "n"}.get(x, "") * li[-1] += c # <<<<<<<<<<<<<< @@ -7136,7 +7136,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel } __pyx_L5:; - /* "LCEngine.pyx":285 + /* "LCEngineV1.pyx":285 * li = [] * siBlancas = True * for c in xpv: # <<<<<<<<<<<<<< @@ -7146,7 +7146,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel } __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":297 + /* "LCEngineV1.pyx":297 * c = {50: "q", 51: "r", 52: "b", 53: "n"}.get(x, "") * li[-1] += c * return li # <<<<<<<<<<<<<< @@ -7158,7 +7158,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel __pyx_r = __pyx_v_li; goto __pyx_L0; - /* "LCEngine.pyx":282 + /* "LCEngineV1.pyx":282 * return li * * def xpv2lipv(xpv): # <<<<<<<<<<<<<< @@ -7174,7 +7174,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_XDECREF(__pyx_t_8); __Pyx_XDECREF(__pyx_t_9); __Pyx_XDECREF(__pyx_t_10); - __Pyx_AddTraceback("LCEngine.xpv2lipv", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.xpv2lipv", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_li); @@ -7187,7 +7187,7 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel return __pyx_r; } -/* "LCEngine.pyx":299 +/* "LCEngineV1.pyx":299 * return li * * def xpv2pv(xpv): # <<<<<<<<<<<<<< @@ -7196,20 +7196,20 @@ static PyObject *__pyx_pf_8LCEngine_26xpv2lipv(CYTHON_UNUSED PyObject *__pyx_sel */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_29xpv2pv(PyObject *__pyx_self, PyObject *__pyx_v_xpv); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_29xpv2pv = {"xpv2pv", (PyCFunction)__pyx_pw_8LCEngine_29xpv2pv, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_29xpv2pv(PyObject *__pyx_self, PyObject *__pyx_v_xpv) { +static PyObject *__pyx_pw_10LCEngineV1_29xpv2pv(PyObject *__pyx_self, PyObject *__pyx_v_xpv); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_29xpv2pv = {"xpv2pv", (PyCFunction)__pyx_pw_10LCEngineV1_29xpv2pv, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_29xpv2pv(PyObject *__pyx_self, PyObject *__pyx_v_xpv) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("xpv2pv (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_28xpv2pv(__pyx_self, ((PyObject *)__pyx_v_xpv)); + __pyx_r = __pyx_pf_10LCEngineV1_28xpv2pv(__pyx_self, ((PyObject *)__pyx_v_xpv)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_28xpv2pv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv) { +static PyObject *__pyx_pf_10LCEngineV1_28xpv2pv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; @@ -7218,7 +7218,7 @@ static PyObject *__pyx_pf_8LCEngine_28xpv2pv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_t_4 = NULL; __Pyx_RefNannySetupContext("xpv2pv", 0); - /* "LCEngine.pyx":300 + /* "LCEngineV1.pyx":300 * * def xpv2pv(xpv): * return " ".join(xpv2lipv(xpv)) # <<<<<<<<<<<<<< @@ -7278,7 +7278,7 @@ static PyObject *__pyx_pf_8LCEngine_28xpv2pv(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_2 = 0; goto __pyx_L0; - /* "LCEngine.pyx":299 + /* "LCEngineV1.pyx":299 * return li * * def xpv2pv(xpv): # <<<<<<<<<<<<<< @@ -7292,7 +7292,7 @@ static PyObject *__pyx_pf_8LCEngine_28xpv2pv(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_4); - __Pyx_AddTraceback("LCEngine.xpv2pv", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.xpv2pv", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -7300,7 +7300,7 @@ static PyObject *__pyx_pf_8LCEngine_28xpv2pv(CYTHON_UNUSED PyObject *__pyx_self, return __pyx_r; } -/* "LCEngine.pyx":302 +/* "LCEngineV1.pyx":302 * return " ".join(xpv2lipv(xpv)) * * def pv2xpv(pv): # <<<<<<<<<<<<<< @@ -7309,20 +7309,20 @@ static PyObject *__pyx_pf_8LCEngine_28xpv2pv(CYTHON_UNUSED PyObject *__pyx_self, */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_31pv2xpv(PyObject *__pyx_self, PyObject *__pyx_v_pv); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_31pv2xpv = {"pv2xpv", (PyCFunction)__pyx_pw_8LCEngine_31pv2xpv, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_31pv2xpv(PyObject *__pyx_self, PyObject *__pyx_v_pv) { +static PyObject *__pyx_pw_10LCEngineV1_31pv2xpv(PyObject *__pyx_self, PyObject *__pyx_v_pv); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_31pv2xpv = {"pv2xpv", (PyCFunction)__pyx_pw_10LCEngineV1_31pv2xpv, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_31pv2xpv(PyObject *__pyx_self, PyObject *__pyx_v_pv) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("pv2xpv (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_30pv2xpv(__pyx_self, ((PyObject *)__pyx_v_pv)); + __pyx_r = __pyx_pf_10LCEngineV1_30pv2xpv(__pyx_self, ((PyObject *)__pyx_v_pv)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pv) { +static PyObject *__pyx_pf_10LCEngineV1_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pv) { PyObject *__pyx_v_li = NULL; PyObject *__pyx_v_lix = NULL; PyObject *__pyx_v_move = NULL; @@ -7343,7 +7343,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, int __pyx_t_10; __Pyx_RefNannySetupContext("pv2xpv", 0); - /* "LCEngine.pyx":303 + /* "LCEngineV1.pyx":303 * * def pv2xpv(pv): * if pv: # <<<<<<<<<<<<<< @@ -7353,7 +7353,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_v_pv); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 303, __pyx_L1_error) if (__pyx_t_1) { - /* "LCEngine.pyx":304 + /* "LCEngineV1.pyx":304 * def pv2xpv(pv): * if pv: * li = pv.split(" ") # <<<<<<<<<<<<<< @@ -7368,7 +7368,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __pyx_v_li = __pyx_t_3; __pyx_t_3 = 0; - /* "LCEngine.pyx":305 + /* "LCEngineV1.pyx":305 * if pv: * li = pv.split(" ") * lix = [] # <<<<<<<<<<<<<< @@ -7380,7 +7380,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __pyx_v_lix = ((PyObject*)__pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":306 + /* "LCEngineV1.pyx":306 * li = pv.split(" ") * lix = [] * for move in li: # <<<<<<<<<<<<<< @@ -7429,7 +7429,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_XDECREF_SET(__pyx_v_move, __pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":307 + /* "LCEngineV1.pyx":307 * lix = [] * for move in li: * d = chr(a1Pos(move[:2]) + 58) # 58 is an arbitrary number, to remain in range 58..122 # <<<<<<<<<<<<<< @@ -7500,7 +7500,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_XDECREF_SET(__pyx_v_d, __pyx_t_6); __pyx_t_6 = 0; - /* "LCEngine.pyx":308 + /* "LCEngineV1.pyx":308 * for move in li: * d = chr(a1Pos(move[:2]) + 58) # 58 is an arbitrary number, to remain in range 58..122 * h = chr(a1Pos(move[2:4]) + 58) # <<<<<<<<<<<<<< @@ -7571,7 +7571,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_XDECREF_SET(__pyx_v_h, __pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":309 + /* "LCEngineV1.pyx":309 * d = chr(a1Pos(move[:2]) + 58) # 58 is an arbitrary number, to remain in range 58..122 * h = chr(a1Pos(move[2:4]) + 58) * c = move[4:] # <<<<<<<<<<<<<< @@ -7583,7 +7583,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_XDECREF_SET(__pyx_v_c, __pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":310 + /* "LCEngineV1.pyx":310 * h = chr(a1Pos(move[2:4]) + 58) * c = move[4:] * if c: # <<<<<<<<<<<<<< @@ -7593,7 +7593,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_1 = __Pyx_PyObject_IsTrue(__pyx_v_c); if (unlikely(__pyx_t_1 < 0)) __PYX_ERR(0, 310, __pyx_L1_error) if (__pyx_t_1) { - /* "LCEngine.pyx":311 + /* "LCEngineV1.pyx":311 * c = move[4:] * if c: * c = {"q": chr(50), "r": chr(51), "b": chr(52), "n": chr(53)}.get(c.lower(), "") # <<<<<<<<<<<<<< @@ -7645,7 +7645,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_DECREF_SET(__pyx_v_c, __pyx_t_8); __pyx_t_8 = 0; - /* "LCEngine.pyx":310 + /* "LCEngineV1.pyx":310 * h = chr(a1Pos(move[2:4]) + 58) * c = move[4:] * if c: # <<<<<<<<<<<<<< @@ -7654,7 +7654,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, */ } - /* "LCEngine.pyx":312 + /* "LCEngineV1.pyx":312 * if c: * c = {"q": chr(50), "r": chr(51), "b": chr(52), "n": chr(53)}.get(c.lower(), "") * lix.append(d + h + c) # <<<<<<<<<<<<<< @@ -7669,7 +7669,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_10 = __Pyx_PyList_Append(__pyx_v_lix, __pyx_t_6); if (unlikely(__pyx_t_10 == -1)) __PYX_ERR(0, 312, __pyx_L1_error) __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - /* "LCEngine.pyx":306 + /* "LCEngineV1.pyx":306 * li = pv.split(" ") * lix = [] * for move in li: # <<<<<<<<<<<<<< @@ -7679,7 +7679,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, } __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":313 + /* "LCEngineV1.pyx":313 * c = {"q": chr(50), "r": chr(51), "b": chr(52), "n": chr(53)}.get(c.lower(), "") * lix.append(d + h + c) * return "".join(lix) # <<<<<<<<<<<<<< @@ -7693,7 +7693,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_3 = 0; goto __pyx_L0; - /* "LCEngine.pyx":303 + /* "LCEngineV1.pyx":303 * * def pv2xpv(pv): * if pv: # <<<<<<<<<<<<<< @@ -7702,7 +7702,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, */ } - /* "LCEngine.pyx":315 + /* "LCEngineV1.pyx":315 * return "".join(lix) * else: * return "" # <<<<<<<<<<<<<< @@ -7716,7 +7716,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, goto __pyx_L0; } - /* "LCEngine.pyx":302 + /* "LCEngineV1.pyx":302 * return " ".join(xpv2lipv(xpv)) * * def pv2xpv(pv): # <<<<<<<<<<<<<< @@ -7732,7 +7732,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_XDECREF(__pyx_t_7); __Pyx_XDECREF(__pyx_t_8); __Pyx_XDECREF(__pyx_t_9); - __Pyx_AddTraceback("LCEngine.pv2xpv", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.pv2xpv", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_li); @@ -7746,7 +7746,7 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, return __pyx_r; } -/* "LCEngine.pyx":317 +/* "LCEngineV1.pyx":317 * return "" * * def runFen( fen, depth, ms, level ): # <<<<<<<<<<<<<< @@ -7755,9 +7755,9 @@ static PyObject *__pyx_pf_8LCEngine_30pv2xpv(CYTHON_UNUSED PyObject *__pyx_self, */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_33runFen(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_33runFen = {"runFen", (PyCFunction)__pyx_pw_8LCEngine_33runFen, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_33runFen(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_33runFen(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_33runFen = {"runFen", (PyCFunction)__pyx_pw_10LCEngineV1_33runFen, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_33runFen(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_fen = 0; PyObject *__pyx_v_depth = 0; PyObject *__pyx_v_ms = 0; @@ -7820,18 +7820,18 @@ static PyObject *__pyx_pw_8LCEngine_33runFen(PyObject *__pyx_self, PyObject *__p __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("runFen", 1, 4, 4, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 317, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.runFen", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.runFen", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_32runFen(__pyx_self, __pyx_v_fen, __pyx_v_depth, __pyx_v_ms, __pyx_v_level); + __pyx_r = __pyx_pf_10LCEngineV1_32runFen(__pyx_self, __pyx_v_fen, __pyx_v_depth, __pyx_v_ms, __pyx_v_level); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_32runFen(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen, PyObject *__pyx_v_depth, PyObject *__pyx_v_ms, PyObject *__pyx_v_level) { +static PyObject *__pyx_pf_10LCEngineV1_32runFen(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen, PyObject *__pyx_v_depth, PyObject *__pyx_v_ms, PyObject *__pyx_v_level) { char *__pyx_v_x; PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations @@ -7841,7 +7841,7 @@ static PyObject *__pyx_pf_8LCEngine_32runFen(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_t_4 = NULL; __Pyx_RefNannySetupContext("runFen", 0); - /* "LCEngine.pyx":318 + /* "LCEngineV1.pyx":318 * * def runFen( fen, depth, ms, level ): * set_level(level) # <<<<<<<<<<<<<< @@ -7851,7 +7851,7 @@ static PyObject *__pyx_pf_8LCEngine_32runFen(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_1 = __Pyx_PyInt_As_int(__pyx_v_level); if (unlikely((__pyx_t_1 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 318, __pyx_L1_error) set_level(__pyx_t_1); - /* "LCEngine.pyx":319 + /* "LCEngineV1.pyx":319 * def runFen( fen, depth, ms, level ): * set_level(level) * x = playFen(fen, depth, ms) # <<<<<<<<<<<<<< @@ -7863,7 +7863,7 @@ static PyObject *__pyx_pf_8LCEngine_32runFen(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_3 = __Pyx_PyInt_As_int(__pyx_v_ms); if (unlikely((__pyx_t_3 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 319, __pyx_L1_error) __pyx_v_x = playFen(__pyx_t_2, __pyx_t_1, __pyx_t_3); - /* "LCEngine.pyx":320 + /* "LCEngineV1.pyx":320 * set_level(level) * x = playFen(fen, depth, ms) * set_level(0) # <<<<<<<<<<<<<< @@ -7872,7 +7872,7 @@ static PyObject *__pyx_pf_8LCEngine_32runFen(CYTHON_UNUSED PyObject *__pyx_self, */ set_level(0); - /* "LCEngine.pyx":321 + /* "LCEngineV1.pyx":321 * x = playFen(fen, depth, ms) * set_level(0) * return x # <<<<<<<<<<<<<< @@ -7886,7 +7886,7 @@ static PyObject *__pyx_pf_8LCEngine_32runFen(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_4 = 0; goto __pyx_L0; - /* "LCEngine.pyx":317 + /* "LCEngineV1.pyx":317 * return "" * * def runFen( fen, depth, ms, level ): # <<<<<<<<<<<<<< @@ -7897,7 +7897,7 @@ static PyObject *__pyx_pf_8LCEngine_32runFen(CYTHON_UNUSED PyObject *__pyx_self, /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_4); - __Pyx_AddTraceback("LCEngine.runFen", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.runFen", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -7905,7 +7905,7 @@ static PyObject *__pyx_pf_8LCEngine_32runFen(CYTHON_UNUSED PyObject *__pyx_self, return __pyx_r; } -/* "LCEngine.pyx":323 +/* "LCEngineV1.pyx":323 * return x * * def setFen(fen): # <<<<<<<<<<<<<< @@ -7914,27 +7914,27 @@ static PyObject *__pyx_pf_8LCEngine_32runFen(CYTHON_UNUSED PyObject *__pyx_self, */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_35setFen(PyObject *__pyx_self, PyObject *__pyx_v_fen); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_35setFen = {"setFen", (PyCFunction)__pyx_pw_8LCEngine_35setFen, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_35setFen(PyObject *__pyx_self, PyObject *__pyx_v_fen) { +static PyObject *__pyx_pw_10LCEngineV1_35setFen(PyObject *__pyx_self, PyObject *__pyx_v_fen); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_35setFen = {"setFen", (PyCFunction)__pyx_pw_10LCEngineV1_35setFen, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_35setFen(PyObject *__pyx_self, PyObject *__pyx_v_fen) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("setFen (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_34setFen(__pyx_self, ((PyObject *)__pyx_v_fen)); + __pyx_r = __pyx_pf_10LCEngineV1_34setFen(__pyx_self, ((PyObject *)__pyx_v_fen)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_34setFen(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen) { +static PyObject *__pyx_pf_10LCEngineV1_34setFen(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations char *__pyx_t_1; PyObject *__pyx_t_2 = NULL; __Pyx_RefNannySetupContext("setFen", 0); - /* "LCEngine.pyx":324 + /* "LCEngineV1.pyx":324 * * def setFen(fen): * fen_board(fen) # <<<<<<<<<<<<<< @@ -7944,7 +7944,7 @@ static PyObject *__pyx_pf_8LCEngine_34setFen(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_1 = __Pyx_PyObject_AsString(__pyx_v_fen); if (unlikely((!__pyx_t_1) && PyErr_Occurred())) __PYX_ERR(0, 324, __pyx_L1_error) fen_board(__pyx_t_1); - /* "LCEngine.pyx":325 + /* "LCEngineV1.pyx":325 * def setFen(fen): * fen_board(fen) * return movegen() # <<<<<<<<<<<<<< @@ -7958,7 +7958,7 @@ static PyObject *__pyx_pf_8LCEngine_34setFen(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_2 = 0; goto __pyx_L0; - /* "LCEngine.pyx":323 + /* "LCEngineV1.pyx":323 * return x * * def setFen(fen): # <<<<<<<<<<<<<< @@ -7969,7 +7969,7 @@ static PyObject *__pyx_pf_8LCEngine_34setFen(CYTHON_UNUSED PyObject *__pyx_self, /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_2); - __Pyx_AddTraceback("LCEngine.setFen", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.setFen", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -7977,7 +7977,7 @@ static PyObject *__pyx_pf_8LCEngine_34setFen(CYTHON_UNUSED PyObject *__pyx_self, return __pyx_r; } -/* "LCEngine.pyx":327 +/* "LCEngineV1.pyx":327 * return movegen() * * def getFen(): # <<<<<<<<<<<<<< @@ -7986,20 +7986,20 @@ static PyObject *__pyx_pf_8LCEngine_34setFen(CYTHON_UNUSED PyObject *__pyx_self, */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_37getFen(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_37getFen = {"getFen", (PyCFunction)__pyx_pw_8LCEngine_37getFen, METH_NOARGS, 0}; -static PyObject *__pyx_pw_8LCEngine_37getFen(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { +static PyObject *__pyx_pw_10LCEngineV1_37getFen(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_37getFen = {"getFen", (PyCFunction)__pyx_pw_10LCEngineV1_37getFen, METH_NOARGS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_37getFen(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("getFen (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_36getFen(__pyx_self); + __pyx_r = __pyx_pf_10LCEngineV1_36getFen(__pyx_self); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_36getFen(CYTHON_UNUSED PyObject *__pyx_self) { +static PyObject *__pyx_pf_10LCEngineV1_36getFen(CYTHON_UNUSED PyObject *__pyx_self) { char __pyx_v_fen[0x64]; char *__pyx_v_x; PyObject *__pyx_r = NULL; @@ -8007,7 +8007,7 @@ static PyObject *__pyx_pf_8LCEngine_36getFen(CYTHON_UNUSED PyObject *__pyx_self) PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("getFen", 0); - /* "LCEngine.pyx":329 + /* "LCEngineV1.pyx":329 * def getFen(): * cdef char fen[100] * board_fen(fen) # <<<<<<<<<<<<<< @@ -8016,7 +8016,7 @@ static PyObject *__pyx_pf_8LCEngine_36getFen(CYTHON_UNUSED PyObject *__pyx_self) */ board_fen(__pyx_v_fen); - /* "LCEngine.pyx":330 + /* "LCEngineV1.pyx":330 * cdef char fen[100] * board_fen(fen) * x = fen # <<<<<<<<<<<<<< @@ -8025,7 +8025,7 @@ static PyObject *__pyx_pf_8LCEngine_36getFen(CYTHON_UNUSED PyObject *__pyx_self) */ __pyx_v_x = __pyx_v_fen; - /* "LCEngine.pyx":331 + /* "LCEngineV1.pyx":331 * board_fen(fen) * x = fen * return x # <<<<<<<<<<<<<< @@ -8039,7 +8039,7 @@ static PyObject *__pyx_pf_8LCEngine_36getFen(CYTHON_UNUSED PyObject *__pyx_self) __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":327 + /* "LCEngineV1.pyx":327 * return movegen() * * def getFen(): # <<<<<<<<<<<<<< @@ -8050,7 +8050,7 @@ static PyObject *__pyx_pf_8LCEngine_36getFen(CYTHON_UNUSED PyObject *__pyx_self) /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.getFen", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.getFen", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -8058,7 +8058,7 @@ static PyObject *__pyx_pf_8LCEngine_36getFen(CYTHON_UNUSED PyObject *__pyx_self) return __pyx_r; } -/* "LCEngine.pyx":333 +/* "LCEngineV1.pyx":333 * return x * * def getMoves(): # <<<<<<<<<<<<<< @@ -8067,20 +8067,20 @@ static PyObject *__pyx_pf_8LCEngine_36getFen(CYTHON_UNUSED PyObject *__pyx_self) */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_39getMoves(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_39getMoves = {"getMoves", (PyCFunction)__pyx_pw_8LCEngine_39getMoves, METH_NOARGS, 0}; -static PyObject *__pyx_pw_8LCEngine_39getMoves(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { +static PyObject *__pyx_pw_10LCEngineV1_39getMoves(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_39getMoves = {"getMoves", (PyCFunction)__pyx_pw_10LCEngineV1_39getMoves, METH_NOARGS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_39getMoves(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("getMoves (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_38getMoves(__pyx_self); + __pyx_r = __pyx_pf_10LCEngineV1_38getMoves(__pyx_self); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_self) { +static PyObject *__pyx_pf_10LCEngineV1_38getMoves(CYTHON_UNUSED PyObject *__pyx_self) { char __pyx_v_pv[10]; int __pyx_v_nmoves; int __pyx_v_x; @@ -8095,7 +8095,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel int __pyx_t_4; __Pyx_RefNannySetupContext("getMoves", 0); - /* "LCEngine.pyx":336 + /* "LCEngineV1.pyx":336 * cdef char pv[10] * cdef int nmoves, x, nbase * nmoves = numMoves() # <<<<<<<<<<<<<< @@ -8104,7 +8104,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel */ __pyx_v_nmoves = numMoves(); - /* "LCEngine.pyx":338 + /* "LCEngineV1.pyx":338 * nmoves = numMoves() * * nbase = numBaseMove() # <<<<<<<<<<<<<< @@ -8113,7 +8113,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel */ __pyx_v_nbase = numBaseMove(); - /* "LCEngine.pyx":339 + /* "LCEngineV1.pyx":339 * * nbase = numBaseMove() * li = [] # <<<<<<<<<<<<<< @@ -8125,7 +8125,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel __pyx_v_li = ((PyObject*)__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":340 + /* "LCEngineV1.pyx":340 * nbase = numBaseMove() * li = [] * for x in range(nmoves): # <<<<<<<<<<<<<< @@ -8136,7 +8136,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel for (__pyx_t_3 = 0; __pyx_t_3 < __pyx_t_2; __pyx_t_3+=1) { __pyx_v_x = __pyx_t_3; - /* "LCEngine.pyx":341 + /* "LCEngineV1.pyx":341 * li = [] * for x in range(nmoves): * getMove(x+nbase, pv) # <<<<<<<<<<<<<< @@ -8145,7 +8145,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel */ getMove((__pyx_v_x + __pyx_v_nbase), __pyx_v_pv); - /* "LCEngine.pyx":342 + /* "LCEngineV1.pyx":342 * for x in range(nmoves): * getMove(x+nbase, pv) * r = pv # <<<<<<<<<<<<<< @@ -8154,7 +8154,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel */ __pyx_v_r = __pyx_v_pv; - /* "LCEngine.pyx":343 + /* "LCEngineV1.pyx":343 * getMove(x+nbase, pv) * r = pv * li.append(r) # <<<<<<<<<<<<<< @@ -8167,7 +8167,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; } - /* "LCEngine.pyx":344 + /* "LCEngineV1.pyx":344 * r = pv * li.append(r) * return li # <<<<<<<<<<<<<< @@ -8179,7 +8179,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel __pyx_r = __pyx_v_li; goto __pyx_L0; - /* "LCEngine.pyx":333 + /* "LCEngineV1.pyx":333 * return x * * def getMoves(): # <<<<<<<<<<<<<< @@ -8190,7 +8190,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.getMoves", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.getMoves", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_li); @@ -8199,7 +8199,7 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel return __pyx_r; } -/* "LCEngine.pyx":346 +/* "LCEngineV1.pyx":346 * return li * * def getPGN(desdeA1H8, hastaA1H8, coronacion): # <<<<<<<<<<<<<< @@ -8208,9 +8208,9 @@ static PyObject *__pyx_pf_8LCEngine_38getMoves(CYTHON_UNUSED PyObject *__pyx_sel */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_41getPGN(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_41getPGN = {"getPGN", (PyCFunction)__pyx_pw_8LCEngine_41getPGN, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_41getPGN(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_41getPGN(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_41getPGN = {"getPGN", (PyCFunction)__pyx_pw_10LCEngineV1_41getPGN, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_41getPGN(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_desdeA1H8 = 0; PyObject *__pyx_v_hastaA1H8 = 0; PyObject *__pyx_v_coronacion = 0; @@ -8264,18 +8264,18 @@ static PyObject *__pyx_pw_8LCEngine_41getPGN(PyObject *__pyx_self, PyObject *__p __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("getPGN", 1, 3, 3, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 346, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.getPGN", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.getPGN", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_40getPGN(__pyx_self, __pyx_v_desdeA1H8, __pyx_v_hastaA1H8, __pyx_v_coronacion); + __pyx_r = __pyx_pf_10LCEngineV1_40getPGN(__pyx_self, __pyx_v_desdeA1H8, __pyx_v_hastaA1H8, __pyx_v_coronacion); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desdeA1H8, PyObject *__pyx_v_hastaA1H8, PyObject *__pyx_v_coronacion) { +static PyObject *__pyx_pf_10LCEngineV1_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desdeA1H8, PyObject *__pyx_v_hastaA1H8, PyObject *__pyx_v_coronacion) { char __pyx_v_san[10]; int __pyx_v_num; PyObject *__pyx_r = NULL; @@ -8289,7 +8289,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_RefNannySetupContext("getPGN", 0); __Pyx_INCREF(__pyx_v_coronacion); - /* "LCEngine.pyx":349 + /* "LCEngineV1.pyx":349 * cdef char san[10] * * if not coronacion: # <<<<<<<<<<<<<< @@ -8300,7 +8300,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_2 = ((!__pyx_t_1) != 0); if (__pyx_t_2) { - /* "LCEngine.pyx":350 + /* "LCEngineV1.pyx":350 * * if not coronacion: * coronacion = "" # <<<<<<<<<<<<<< @@ -8310,7 +8310,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_INCREF(__pyx_kp_s_); __Pyx_DECREF_SET(__pyx_v_coronacion, __pyx_kp_s_); - /* "LCEngine.pyx":349 + /* "LCEngineV1.pyx":349 * cdef char san[10] * * if not coronacion: # <<<<<<<<<<<<<< @@ -8319,7 +8319,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, */ } - /* "LCEngine.pyx":352 + /* "LCEngineV1.pyx":352 * coronacion = "" * * num = searchMove( desdeA1H8, hastaA1H8, coronacion ) # <<<<<<<<<<<<<< @@ -8331,7 +8331,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_5 = __Pyx_PyObject_AsString(__pyx_v_coronacion); if (unlikely((!__pyx_t_5) && PyErr_Occurred())) __PYX_ERR(0, 352, __pyx_L1_error) __pyx_v_num = searchMove(__pyx_t_3, __pyx_t_4, __pyx_t_5); - /* "LCEngine.pyx":353 + /* "LCEngineV1.pyx":353 * * num = searchMove( desdeA1H8, hastaA1H8, coronacion ) * if num == -1: # <<<<<<<<<<<<<< @@ -8341,7 +8341,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_2 = ((__pyx_v_num == -1L) != 0); if (__pyx_t_2) { - /* "LCEngine.pyx":354 + /* "LCEngineV1.pyx":354 * num = searchMove( desdeA1H8, hastaA1H8, coronacion ) * if num == -1: * return None # <<<<<<<<<<<<<< @@ -8353,7 +8353,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, __pyx_r = Py_None; goto __pyx_L0; - /* "LCEngine.pyx":353 + /* "LCEngineV1.pyx":353 * * num = searchMove( desdeA1H8, hastaA1H8, coronacion ) * if num == -1: # <<<<<<<<<<<<<< @@ -8362,7 +8362,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, */ } - /* "LCEngine.pyx":356 + /* "LCEngineV1.pyx":356 * return None * * toSan(num, san) # <<<<<<<<<<<<<< @@ -8371,7 +8371,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, */ toSan(__pyx_v_num, __pyx_v_san); - /* "LCEngine.pyx":357 + /* "LCEngineV1.pyx":357 * * toSan(num, san) * return san # <<<<<<<<<<<<<< @@ -8385,7 +8385,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_6 = 0; goto __pyx_L0; - /* "LCEngine.pyx":346 + /* "LCEngineV1.pyx":346 * return li * * def getPGN(desdeA1H8, hastaA1H8, coronacion): # <<<<<<<<<<<<<< @@ -8396,7 +8396,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_6); - __Pyx_AddTraceback("LCEngine.getPGN", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.getPGN", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_coronacion); @@ -8405,7 +8405,7 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, return __pyx_r; } -/* "LCEngine.pyx":359 +/* "LCEngineV1.pyx":359 * return san * * def xpv2pgn(xpv): # <<<<<<<<<<<<<< @@ -8414,20 +8414,20 @@ static PyObject *__pyx_pf_8LCEngine_40getPGN(CYTHON_UNUSED PyObject *__pyx_self, */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_43xpv2pgn(PyObject *__pyx_self, PyObject *__pyx_v_xpv); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_43xpv2pgn = {"xpv2pgn", (PyCFunction)__pyx_pw_8LCEngine_43xpv2pgn, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_43xpv2pgn(PyObject *__pyx_self, PyObject *__pyx_v_xpv) { +static PyObject *__pyx_pw_10LCEngineV1_43xpv2pgn(PyObject *__pyx_self, PyObject *__pyx_v_xpv); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_43xpv2pgn = {"xpv2pgn", (PyCFunction)__pyx_pw_10LCEngineV1_43xpv2pgn, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_43xpv2pgn(PyObject *__pyx_self, PyObject *__pyx_v_xpv) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("xpv2pgn (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_42xpv2pgn(__pyx_self, ((PyObject *)__pyx_v_xpv)); + __pyx_r = __pyx_pf_10LCEngineV1_42xpv2pgn(__pyx_self, ((PyObject *)__pyx_v_xpv)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv) { +static PyObject *__pyx_pf_10LCEngineV1_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_xpv) { char __pyx_v_san[10]; int __pyx_v_siW; PyObject *__pyx_v_num = NULL; @@ -8452,7 +8452,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self char *__pyx_t_12; __Pyx_RefNannySetupContext("xpv2pgn", 0); - /* "LCEngine.pyx":361 + /* "LCEngineV1.pyx":361 * def xpv2pgn(xpv): * cdef char san[10] * setFenInicial() # <<<<<<<<<<<<<< @@ -8481,7 +8481,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":362 + /* "LCEngineV1.pyx":362 * cdef char san[10] * setFenInicial() * siW = True # <<<<<<<<<<<<<< @@ -8490,7 +8490,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ __pyx_v_siW = 1; - /* "LCEngine.pyx":363 + /* "LCEngineV1.pyx":363 * setFenInicial() * siW = True * num = 1 # <<<<<<<<<<<<<< @@ -8500,7 +8500,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_INCREF(__pyx_int_1); __pyx_v_num = __pyx_int_1; - /* "LCEngine.pyx":364 + /* "LCEngineV1.pyx":364 * siW = True * num = 1 * li = [] # <<<<<<<<<<<<<< @@ -8512,7 +8512,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __pyx_v_li = ((PyObject*)__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":365 + /* "LCEngineV1.pyx":365 * num = 1 * li = [] * tam = 0 # <<<<<<<<<<<<<< @@ -8522,7 +8522,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_INCREF(__pyx_int_0); __pyx_v_tam = __pyx_int_0; - /* "LCEngine.pyx":366 + /* "LCEngineV1.pyx":366 * li = [] * tam = 0 * for pv in xpv2lipv(xpv): # <<<<<<<<<<<<<< @@ -8617,7 +8617,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_XDECREF_SET(__pyx_v_pv, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":367 + /* "LCEngineV1.pyx":367 * tam = 0 * for pv in xpv2lipv(xpv): * if siW: # <<<<<<<<<<<<<< @@ -8627,7 +8627,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __pyx_t_7 = (__pyx_v_siW != 0); if (__pyx_t_7) { - /* "LCEngine.pyx":368 + /* "LCEngineV1.pyx":368 * for pv in xpv2lipv(xpv): * if siW: * x = str(num)+"." # <<<<<<<<<<<<<< @@ -8648,7 +8648,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_XDECREF_SET(__pyx_v_x, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":369 + /* "LCEngineV1.pyx":369 * if siW: * x = str(num)+"." * tam += len(x) # <<<<<<<<<<<<<< @@ -8664,7 +8664,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_DECREF_SET(__pyx_v_tam, __pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":370 + /* "LCEngineV1.pyx":370 * x = str(num)+"." * tam += len(x) * li.append(x) # <<<<<<<<<<<<<< @@ -8673,7 +8673,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ __pyx_t_9 = __Pyx_PyList_Append(__pyx_v_li, __pyx_v_x); if (unlikely(__pyx_t_9 == -1)) __PYX_ERR(0, 370, __pyx_L1_error) - /* "LCEngine.pyx":371 + /* "LCEngineV1.pyx":371 * tam += len(x) * li.append(x) * num += 1 # <<<<<<<<<<<<<< @@ -8685,7 +8685,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_DECREF_SET(__pyx_v_num, __pyx_t_4); __pyx_t_4 = 0; - /* "LCEngine.pyx":367 + /* "LCEngineV1.pyx":367 * tam = 0 * for pv in xpv2lipv(xpv): * if siW: # <<<<<<<<<<<<<< @@ -8694,7 +8694,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ } - /* "LCEngine.pyx":372 + /* "LCEngineV1.pyx":372 * li.append(x) * num += 1 * siW = not siW # <<<<<<<<<<<<<< @@ -8703,7 +8703,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ __pyx_v_siW = (!(__pyx_v_siW != 0)); - /* "LCEngine.pyx":374 + /* "LCEngineV1.pyx":374 * siW = not siW * * numMove = searchMove( pv[:2], pv[2:4], pv[4:] ) # <<<<<<<<<<<<<< @@ -8724,7 +8724,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":375 + /* "LCEngineV1.pyx":375 * * numMove = searchMove( pv[:2], pv[2:4], pv[4:] ) * if numMove == -1: # <<<<<<<<<<<<<< @@ -8734,7 +8734,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __pyx_t_7 = ((__pyx_v_numMove == -1L) != 0); if (__pyx_t_7) { - /* "LCEngine.pyx":376 + /* "LCEngineV1.pyx":376 * numMove = searchMove( pv[:2], pv[2:4], pv[4:] ) * if numMove == -1: * break # <<<<<<<<<<<<<< @@ -8743,7 +8743,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ goto __pyx_L4_break; - /* "LCEngine.pyx":375 + /* "LCEngineV1.pyx":375 * * numMove = searchMove( pv[:2], pv[2:4], pv[4:] ) * if numMove == -1: # <<<<<<<<<<<<<< @@ -8752,7 +8752,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ } - /* "LCEngine.pyx":377 + /* "LCEngineV1.pyx":377 * if numMove == -1: * break * toSan(numMove, san) # <<<<<<<<<<<<<< @@ -8761,7 +8761,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ toSan(__pyx_v_numMove, __pyx_v_san); - /* "LCEngine.pyx":378 + /* "LCEngineV1.pyx":378 * break * toSan(numMove, san) * x = str(san) # <<<<<<<<<<<<<< @@ -8781,7 +8781,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_XDECREF_SET(__pyx_v_x, __pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":379 + /* "LCEngineV1.pyx":379 * toSan(numMove, san) * x = str(san) * li.append(x) # <<<<<<<<<<<<<< @@ -8790,7 +8790,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ __pyx_t_9 = __Pyx_PyList_Append(__pyx_v_li, __pyx_v_x); if (unlikely(__pyx_t_9 == -1)) __PYX_ERR(0, 379, __pyx_L1_error) - /* "LCEngine.pyx":380 + /* "LCEngineV1.pyx":380 * x = str(san) * li.append(x) * tam += len(x) # <<<<<<<<<<<<<< @@ -8806,7 +8806,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_DECREF_SET(__pyx_v_tam, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":381 + /* "LCEngineV1.pyx":381 * li.append(x) * tam += len(x) * if tam >= 80: # <<<<<<<<<<<<<< @@ -8818,7 +8818,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; if (__pyx_t_7) { - /* "LCEngine.pyx":382 + /* "LCEngineV1.pyx":382 * tam += len(x) * if tam >= 80: * li.append("\n") # <<<<<<<<<<<<<< @@ -8827,7 +8827,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ __pyx_t_9 = __Pyx_PyList_Append(__pyx_v_li, __pyx_kp_s__38); if (unlikely(__pyx_t_9 == -1)) __PYX_ERR(0, 382, __pyx_L1_error) - /* "LCEngine.pyx":383 + /* "LCEngineV1.pyx":383 * if tam >= 80: * li.append("\n") * tam = 0 # <<<<<<<<<<<<<< @@ -8837,7 +8837,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_INCREF(__pyx_int_0); __Pyx_DECREF_SET(__pyx_v_tam, __pyx_int_0); - /* "LCEngine.pyx":381 + /* "LCEngineV1.pyx":381 * li.append(x) * tam += len(x) * if tam >= 80: # <<<<<<<<<<<<<< @@ -8847,7 +8847,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self goto __pyx_L7; } - /* "LCEngine.pyx":385 + /* "LCEngineV1.pyx":385 * tam = 0 * else: * li.append(" ") # <<<<<<<<<<<<<< @@ -8857,7 +8857,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self /*else*/ { __pyx_t_9 = __Pyx_PyList_Append(__pyx_v_li, __pyx_kp_s__25); if (unlikely(__pyx_t_9 == -1)) __PYX_ERR(0, 385, __pyx_L1_error) - /* "LCEngine.pyx":386 + /* "LCEngineV1.pyx":386 * else: * li.append(" ") * tam += 1 # <<<<<<<<<<<<<< @@ -8871,7 +8871,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self } __pyx_L7:; - /* "LCEngine.pyx":387 + /* "LCEngineV1.pyx":387 * li.append(" ") * tam += 1 * make_nummove(numMove) # <<<<<<<<<<<<<< @@ -8880,7 +8880,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ make_nummove(__pyx_v_numMove); - /* "LCEngine.pyx":366 + /* "LCEngineV1.pyx":366 * li = [] * tam = 0 * for pv in xpv2lipv(xpv): # <<<<<<<<<<<<<< @@ -8891,7 +8891,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __pyx_L4_break:; __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":388 + /* "LCEngineV1.pyx":388 * tam += 1 * make_nummove(numMove) * return "".join(li) # <<<<<<<<<<<<<< @@ -8905,7 +8905,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __pyx_t_2 = 0; goto __pyx_L0; - /* "LCEngine.pyx":359 + /* "LCEngineV1.pyx":359 * return san * * def xpv2pgn(xpv): # <<<<<<<<<<<<<< @@ -8919,7 +8919,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_4); - __Pyx_AddTraceback("LCEngine.xpv2pgn", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.xpv2pgn", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_num); @@ -8932,7 +8932,7 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self return __pyx_r; } -/* "LCEngine.pyx":390 +/* "LCEngineV1.pyx":390 * return "".join(li) * * def isCheck(): # <<<<<<<<<<<<<< @@ -8941,26 +8941,26 @@ static PyObject *__pyx_pf_8LCEngine_42xpv2pgn(CYTHON_UNUSED PyObject *__pyx_self */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_45isCheck(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_45isCheck = {"isCheck", (PyCFunction)__pyx_pw_8LCEngine_45isCheck, METH_NOARGS, 0}; -static PyObject *__pyx_pw_8LCEngine_45isCheck(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { +static PyObject *__pyx_pw_10LCEngineV1_45isCheck(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_45isCheck = {"isCheck", (PyCFunction)__pyx_pw_10LCEngineV1_45isCheck, METH_NOARGS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_45isCheck(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("isCheck (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_44isCheck(__pyx_self); + __pyx_r = __pyx_pf_10LCEngineV1_44isCheck(__pyx_self); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_44isCheck(CYTHON_UNUSED PyObject *__pyx_self) { +static PyObject *__pyx_pf_10LCEngineV1_44isCheck(CYTHON_UNUSED PyObject *__pyx_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("isCheck", 0); - /* "LCEngine.pyx":391 + /* "LCEngineV1.pyx":391 * * def isCheck(): * return inCheck() # <<<<<<<<<<<<<< @@ -8974,7 +8974,7 @@ static PyObject *__pyx_pf_8LCEngine_44isCheck(CYTHON_UNUSED PyObject *__pyx_self __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":390 + /* "LCEngineV1.pyx":390 * return "".join(li) * * def isCheck(): # <<<<<<<<<<<<<< @@ -8985,7 +8985,7 @@ static PyObject *__pyx_pf_8LCEngine_44isCheck(CYTHON_UNUSED PyObject *__pyx_self /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.isCheck", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.isCheck", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -8993,7 +8993,7 @@ static PyObject *__pyx_pf_8LCEngine_44isCheck(CYTHON_UNUSED PyObject *__pyx_self return __pyx_r; } -/* "LCEngine.pyx":394 +/* "LCEngineV1.pyx":394 * * class InfoMove(object): * def __init__(self, num): # <<<<<<<<<<<<<< @@ -9002,9 +9002,9 @@ static PyObject *__pyx_pf_8LCEngine_44isCheck(CYTHON_UNUSED PyObject *__pyx_self */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_1__init__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_1__init__ = {"__init__", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_1__init__, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_1__init__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_1__init__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_1__init__ = {"__init__", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_1__init__, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_1__init__(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_self = 0; PyObject *__pyx_v_num = 0; PyObject *__pyx_r = 0; @@ -9049,18 +9049,18 @@ static PyObject *__pyx_pw_8LCEngine_8InfoMove_1__init__(PyObject *__pyx_self, Py __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("__init__", 1, 2, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 394, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.InfoMove.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_8InfoMove___init__(__pyx_self, __pyx_v_self, __pyx_v_num); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove___init__(__pyx_self, __pyx_v_self, __pyx_v_num); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self, PyObject *__pyx_v_num) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove___init__(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self, PyObject *__pyx_v_num) { char __pyx_v_pv[10]; char __pyx_v_info[10]; char __pyx_v_san[10]; @@ -9073,7 +9073,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ int __pyx_t_5; __Pyx_RefNannySetupContext("__init__", 0); - /* "LCEngine.pyx":399 + /* "LCEngineV1.pyx":399 * cdef char san[10] * * getMove(num, pv) # <<<<<<<<<<<<<< @@ -9083,7 +9083,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ __pyx_t_1 = __Pyx_PyInt_As_int(__pyx_v_num); if (unlikely((__pyx_t_1 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 399, __pyx_L1_error) getMove(__pyx_t_1, __pyx_v_pv); - /* "LCEngine.pyx":400 + /* "LCEngineV1.pyx":400 * * getMove(num, pv) * getMoveEx(num, info) # <<<<<<<<<<<<<< @@ -9093,7 +9093,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ __pyx_t_1 = __Pyx_PyInt_As_int(__pyx_v_num); if (unlikely((__pyx_t_1 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 400, __pyx_L1_error) getMoveEx(__pyx_t_1, __pyx_v_info); - /* "LCEngine.pyx":401 + /* "LCEngineV1.pyx":401 * getMove(num, pv) * getMoveEx(num, info) * toSan(num, san) # <<<<<<<<<<<<<< @@ -9103,7 +9103,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ __pyx_t_1 = __Pyx_PyInt_As_int(__pyx_v_num); if (unlikely((__pyx_t_1 == (int)-1) && PyErr_Occurred())) __PYX_ERR(0, 401, __pyx_L1_error) toSan(__pyx_t_1, __pyx_v_san); - /* "LCEngine.pyx":405 + /* "LCEngineV1.pyx":405 * # info = P a1 h8 q [K|Q|] * * self._castle_K = info[6] == "K" # <<<<<<<<<<<<<< @@ -9115,7 +9115,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_castle_K, __pyx_t_2) < 0) __PYX_ERR(0, 405, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":406 + /* "LCEngineV1.pyx":406 * * self._castle_K = info[6] == "K" * self._castle_Q = info[6] == "Q" # <<<<<<<<<<<<<< @@ -9127,7 +9127,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_castle_Q, __pyx_t_2) < 0) __PYX_ERR(0, 406, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":407 + /* "LCEngineV1.pyx":407 * self._castle_K = info[6] == "K" * self._castle_Q = info[6] == "Q" * self._ep = info[7]=="E" # <<<<<<<<<<<<<< @@ -9139,7 +9139,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_ep, __pyx_t_2) < 0) __PYX_ERR(0, 407, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":408 + /* "LCEngineV1.pyx":408 * self._castle_Q = info[6] == "Q" * self._ep = info[7]=="E" * self._pv = pv # <<<<<<<<<<<<<< @@ -9151,7 +9151,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_pv, __pyx_t_2) < 0) __PYX_ERR(0, 408, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":409 + /* "LCEngineV1.pyx":409 * self._ep = info[7]=="E" * self._pv = pv * self._san = san # <<<<<<<<<<<<<< @@ -9163,7 +9163,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_san, __pyx_t_2) < 0) __PYX_ERR(0, 409, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":411 + /* "LCEngineV1.pyx":411 * self._san = san * * self._piece = info[0:1] # <<<<<<<<<<<<<< @@ -9175,7 +9175,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_piece, __pyx_t_2) < 0) __PYX_ERR(0, 411, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":412 + /* "LCEngineV1.pyx":412 * * self._piece = info[0:1] * self._from = info[1:3] # <<<<<<<<<<<<<< @@ -9187,7 +9187,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_from, __pyx_t_2) < 0) __PYX_ERR(0, 412, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":413 + /* "LCEngineV1.pyx":413 * self._piece = info[0:1] * self._from = info[1:3] * self._to = info[3:5] # <<<<<<<<<<<<<< @@ -9199,7 +9199,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_to, __pyx_t_2) < 0) __PYX_ERR(0, 413, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":414 + /* "LCEngineV1.pyx":414 * self._from = info[1:3] * self._to = info[3:5] * self._promotion = info[5:6].strip() # <<<<<<<<<<<<<< @@ -9232,7 +9232,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_promotion, __pyx_t_2) < 0) __PYX_ERR(0, 414, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":415 + /* "LCEngineV1.pyx":415 * self._to = info[3:5] * self._promotion = info[5:6].strip() * self._check = "+" in san # <<<<<<<<<<<<<< @@ -9248,7 +9248,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_check, __pyx_t_2) < 0) __PYX_ERR(0, 415, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":416 + /* "LCEngineV1.pyx":416 * self._promotion = info[5:6].strip() * self._check = "+" in san * self._mate = "#" in san # <<<<<<<<<<<<<< @@ -9264,7 +9264,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_mate, __pyx_t_2) < 0) __PYX_ERR(0, 416, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":417 + /* "LCEngineV1.pyx":417 * self._check = "+" in san * self._mate = "#" in san * self._capture = "x" in san # <<<<<<<<<<<<<< @@ -9280,7 +9280,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ if (__Pyx_PyObject_SetAttrStr(__pyx_v_self, __pyx_n_s_capture, __pyx_t_2) < 0) __PYX_ERR(0, 417, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":394 + /* "LCEngineV1.pyx":394 * * class InfoMove(object): * def __init__(self, num): # <<<<<<<<<<<<<< @@ -9295,7 +9295,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_4); - __Pyx_AddTraceback("LCEngine.InfoMove.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.__init__", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9303,7 +9303,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ return __pyx_r; } -/* "LCEngine.pyx":419 +/* "LCEngineV1.pyx":419 * self._capture = "x" in san * * def desde(self): # <<<<<<<<<<<<<< @@ -9312,26 +9312,26 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove___init__(CYTHON_UNUSED PyObject *_ */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_3desde(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_3desde = {"desde", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_3desde, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_3desde(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_3desde(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_3desde = {"desde", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_3desde, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_3desde(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("desde (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_2desde(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_2desde(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_2desde(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_2desde(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("desde", 0); - /* "LCEngine.pyx":420 + /* "LCEngineV1.pyx":420 * * def desde(self): * return self._from # <<<<<<<<<<<<<< @@ -9345,7 +9345,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_2desde(CYTHON_UNUSED PyObject *__p __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":419 + /* "LCEngineV1.pyx":419 * self._capture = "x" in san * * def desde(self): # <<<<<<<<<<<<<< @@ -9356,7 +9356,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_2desde(CYTHON_UNUSED PyObject *__p /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.InfoMove.desde", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.desde", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9364,7 +9364,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_2desde(CYTHON_UNUSED PyObject *__p return __pyx_r; } -/* "LCEngine.pyx":422 +/* "LCEngineV1.pyx":422 * return self._from * * def hasta(self): # <<<<<<<<<<<<<< @@ -9373,26 +9373,26 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_2desde(CYTHON_UNUSED PyObject *__p */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_5hasta(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_5hasta = {"hasta", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_5hasta, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_5hasta(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_5hasta(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_5hasta = {"hasta", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_5hasta, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_5hasta(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("hasta (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_4hasta(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_4hasta(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_4hasta(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_4hasta(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("hasta", 0); - /* "LCEngine.pyx":423 + /* "LCEngineV1.pyx":423 * * def hasta(self): * return self._to # <<<<<<<<<<<<<< @@ -9406,7 +9406,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_4hasta(CYTHON_UNUSED PyObject *__p __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":422 + /* "LCEngineV1.pyx":422 * return self._from * * def hasta(self): # <<<<<<<<<<<<<< @@ -9417,7 +9417,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_4hasta(CYTHON_UNUSED PyObject *__p /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.InfoMove.hasta", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.hasta", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9425,7 +9425,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_4hasta(CYTHON_UNUSED PyObject *__p return __pyx_r; } -/* "LCEngine.pyx":425 +/* "LCEngineV1.pyx":425 * return self._to * * def coronacion(self): # <<<<<<<<<<<<<< @@ -9434,20 +9434,20 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_4hasta(CYTHON_UNUSED PyObject *__p */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_7coronacion(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_7coronacion = {"coronacion", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_7coronacion, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_7coronacion(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_7coronacion(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_7coronacion = {"coronacion", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_7coronacion, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_7coronacion(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("coronacion (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_6coronacion(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_6coronacion(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_6coronacion(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_6coronacion(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; @@ -9455,7 +9455,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_6coronacion(CYTHON_UNUSED PyObject PyObject *__pyx_t_3 = NULL; __Pyx_RefNannySetupContext("coronacion", 0); - /* "LCEngine.pyx":426 + /* "LCEngineV1.pyx":426 * * def coronacion(self): * return self._promotion.lower() # <<<<<<<<<<<<<< @@ -9490,7 +9490,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_6coronacion(CYTHON_UNUSED PyObject __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":425 + /* "LCEngineV1.pyx":425 * return self._to * * def coronacion(self): # <<<<<<<<<<<<<< @@ -9503,7 +9503,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_6coronacion(CYTHON_UNUSED PyObject __Pyx_XDECREF(__pyx_t_1); __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); - __Pyx_AddTraceback("LCEngine.InfoMove.coronacion", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.coronacion", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9511,7 +9511,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_6coronacion(CYTHON_UNUSED PyObject return __pyx_r; } -/* "LCEngine.pyx":428 +/* "LCEngineV1.pyx":428 * return self._promotion.lower() * * def movimiento(self): # <<<<<<<<<<<<<< @@ -9520,20 +9520,20 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_6coronacion(CYTHON_UNUSED PyObject */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_9movimiento(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_9movimiento = {"movimiento", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_9movimiento, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_9movimiento(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_9movimiento(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_9movimiento = {"movimiento", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_9movimiento, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_9movimiento(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("movimiento (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_8movimiento(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_8movimiento(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_8movimiento(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_8movimiento(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; @@ -9542,7 +9542,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_8movimiento(CYTHON_UNUSED PyObject PyObject *__pyx_t_4 = NULL; __Pyx_RefNannySetupContext("movimiento", 0); - /* "LCEngine.pyx":429 + /* "LCEngineV1.pyx":429 * * def movimiento(self): * return self._from+self._to+self._promotion.lower() # <<<<<<<<<<<<<< @@ -9589,7 +9589,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_8movimiento(CYTHON_UNUSED PyObject __pyx_t_4 = 0; goto __pyx_L0; - /* "LCEngine.pyx":428 + /* "LCEngineV1.pyx":428 * return self._promotion.lower() * * def movimiento(self): # <<<<<<<<<<<<<< @@ -9603,7 +9603,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_8movimiento(CYTHON_UNUSED PyObject __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_4); - __Pyx_AddTraceback("LCEngine.InfoMove.movimiento", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.movimiento", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9611,7 +9611,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_8movimiento(CYTHON_UNUSED PyObject return __pyx_r; } -/* "LCEngine.pyx":431 +/* "LCEngineV1.pyx":431 * return self._from+self._to+self._promotion.lower() * * def jaque(self): # <<<<<<<<<<<<<< @@ -9620,26 +9620,26 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_8movimiento(CYTHON_UNUSED PyObject */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_11jaque(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_11jaque = {"jaque", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_11jaque, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_11jaque(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_11jaque(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_11jaque = {"jaque", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_11jaque, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_11jaque(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("jaque (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_10jaque(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_10jaque(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_10jaque(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_10jaque(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("jaque", 0); - /* "LCEngine.pyx":432 + /* "LCEngineV1.pyx":432 * * def jaque(self): * return self._check # <<<<<<<<<<<<<< @@ -9653,7 +9653,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_10jaque(CYTHON_UNUSED PyObject *__ __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":431 + /* "LCEngineV1.pyx":431 * return self._from+self._to+self._promotion.lower() * * def jaque(self): # <<<<<<<<<<<<<< @@ -9664,7 +9664,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_10jaque(CYTHON_UNUSED PyObject *__ /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.InfoMove.jaque", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.jaque", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9672,7 +9672,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_10jaque(CYTHON_UNUSED PyObject *__ return __pyx_r; } -/* "LCEngine.pyx":434 +/* "LCEngineV1.pyx":434 * return self._check * * def mate(self): # <<<<<<<<<<<<<< @@ -9681,26 +9681,26 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_10jaque(CYTHON_UNUSED PyObject *__ */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_13mate(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_13mate = {"mate", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_13mate, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_13mate(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_13mate(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_13mate = {"mate", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_13mate, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_13mate(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("mate (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_12mate(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_12mate(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_12mate(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_12mate(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("mate", 0); - /* "LCEngine.pyx":435 + /* "LCEngineV1.pyx":435 * * def mate(self): * return self._mate # <<<<<<<<<<<<<< @@ -9714,7 +9714,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_12mate(CYTHON_UNUSED PyObject *__p __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":434 + /* "LCEngineV1.pyx":434 * return self._check * * def mate(self): # <<<<<<<<<<<<<< @@ -9725,7 +9725,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_12mate(CYTHON_UNUSED PyObject *__p /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.InfoMove.mate", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.mate", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9733,7 +9733,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_12mate(CYTHON_UNUSED PyObject *__p return __pyx_r; } -/* "LCEngine.pyx":437 +/* "LCEngineV1.pyx":437 * return self._mate * * def captura(self): # <<<<<<<<<<<<<< @@ -9742,26 +9742,26 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_12mate(CYTHON_UNUSED PyObject *__p */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_15captura(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_15captura = {"captura", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_15captura, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_15captura(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_15captura(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_15captura = {"captura", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_15captura, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_15captura(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("captura (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_14captura(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_14captura(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_14captura(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_14captura(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("captura", 0); - /* "LCEngine.pyx":438 + /* "LCEngineV1.pyx":438 * * def captura(self): * return self._capture # <<<<<<<<<<<<<< @@ -9775,7 +9775,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_14captura(CYTHON_UNUSED PyObject * __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":437 + /* "LCEngineV1.pyx":437 * return self._mate * * def captura(self): # <<<<<<<<<<<<<< @@ -9786,7 +9786,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_14captura(CYTHON_UNUSED PyObject * /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.InfoMove.captura", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.captura", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9794,7 +9794,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_14captura(CYTHON_UNUSED PyObject * return __pyx_r; } -/* "LCEngine.pyx":440 +/* "LCEngineV1.pyx":440 * return self._capture * * def pieza(self): # <<<<<<<<<<<<<< @@ -9803,26 +9803,26 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_14captura(CYTHON_UNUSED PyObject * */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_17pieza(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_17pieza = {"pieza", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_17pieza, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_17pieza(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_17pieza(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_17pieza = {"pieza", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_17pieza, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_17pieza(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("pieza (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_16pieza(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_16pieza(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_16pieza(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_16pieza(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("pieza", 0); - /* "LCEngine.pyx":441 + /* "LCEngineV1.pyx":441 * * def pieza(self): * return self._piece # <<<<<<<<<<<<<< @@ -9836,7 +9836,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_16pieza(CYTHON_UNUSED PyObject *__ __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":440 + /* "LCEngineV1.pyx":440 * return self._capture * * def pieza(self): # <<<<<<<<<<<<<< @@ -9847,7 +9847,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_16pieza(CYTHON_UNUSED PyObject *__ /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.InfoMove.pieza", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.pieza", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9855,7 +9855,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_16pieza(CYTHON_UNUSED PyObject *__ return __pyx_r; } -/* "LCEngine.pyx":443 +/* "LCEngineV1.pyx":443 * return self._piece * * def isCastleK(self): # <<<<<<<<<<<<<< @@ -9864,26 +9864,26 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_16pieza(CYTHON_UNUSED PyObject *__ */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_19isCastleK(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_19isCastleK = {"isCastleK", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_19isCastleK, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_19isCastleK(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_19isCastleK(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_19isCastleK = {"isCastleK", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_19isCastleK, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_19isCastleK(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("isCastleK (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_18isCastleK(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_18isCastleK(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_18isCastleK(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_18isCastleK(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("isCastleK", 0); - /* "LCEngine.pyx":444 + /* "LCEngineV1.pyx":444 * * def isCastleK(self): * return self._castle_K # <<<<<<<<<<<<<< @@ -9897,7 +9897,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_18isCastleK(CYTHON_UNUSED PyObject __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":443 + /* "LCEngineV1.pyx":443 * return self._piece * * def isCastleK(self): # <<<<<<<<<<<<<< @@ -9908,7 +9908,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_18isCastleK(CYTHON_UNUSED PyObject /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.InfoMove.isCastleK", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.isCastleK", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9916,7 +9916,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_18isCastleK(CYTHON_UNUSED PyObject return __pyx_r; } -/* "LCEngine.pyx":446 +/* "LCEngineV1.pyx":446 * return self._castle_K * * def isCastleQ(self): # <<<<<<<<<<<<<< @@ -9925,26 +9925,26 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_18isCastleK(CYTHON_UNUSED PyObject */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_21isCastleQ(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_21isCastleQ = {"isCastleQ", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_21isCastleQ, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_21isCastleQ(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_21isCastleQ(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_21isCastleQ = {"isCastleQ", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_21isCastleQ, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_21isCastleQ(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("isCastleQ (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_20isCastleQ(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_20isCastleQ(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_20isCastleQ(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_20isCastleQ(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("isCastleQ", 0); - /* "LCEngine.pyx":447 + /* "LCEngineV1.pyx":447 * * def isCastleQ(self): * return self._castle_Q # <<<<<<<<<<<<<< @@ -9958,7 +9958,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_20isCastleQ(CYTHON_UNUSED PyObject __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":446 + /* "LCEngineV1.pyx":446 * return self._castle_K * * def isCastleQ(self): # <<<<<<<<<<<<<< @@ -9969,7 +9969,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_20isCastleQ(CYTHON_UNUSED PyObject /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.InfoMove.isCastleQ", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.isCastleQ", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -9977,7 +9977,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_20isCastleQ(CYTHON_UNUSED PyObject return __pyx_r; } -/* "LCEngine.pyx":449 +/* "LCEngineV1.pyx":449 * return self._castle_Q * * def isEnPassant(self): # <<<<<<<<<<<<<< @@ -9986,26 +9986,26 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_20isCastleQ(CYTHON_UNUSED PyObject */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_8InfoMove_23isEnPassant(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_8InfoMove_23isEnPassant = {"isEnPassant", (PyCFunction)__pyx_pw_8LCEngine_8InfoMove_23isEnPassant, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_8InfoMove_23isEnPassant(PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_23isEnPassant(PyObject *__pyx_self, PyObject *__pyx_v_self); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_8InfoMove_23isEnPassant = {"isEnPassant", (PyCFunction)__pyx_pw_10LCEngineV1_8InfoMove_23isEnPassant, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_8InfoMove_23isEnPassant(PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("isEnPassant (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_8InfoMove_22isEnPassant(__pyx_self, ((PyObject *)__pyx_v_self)); + __pyx_r = __pyx_pf_10LCEngineV1_8InfoMove_22isEnPassant(__pyx_self, ((PyObject *)__pyx_v_self)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_8InfoMove_22isEnPassant(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { +static PyObject *__pyx_pf_10LCEngineV1_8InfoMove_22isEnPassant(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_self) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; __Pyx_RefNannySetupContext("isEnPassant", 0); - /* "LCEngine.pyx":450 + /* "LCEngineV1.pyx":450 * * def isEnPassant(self): * return self._ep # <<<<<<<<<<<<<< @@ -10019,7 +10019,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_22isEnPassant(CYTHON_UNUSED PyObje __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":449 + /* "LCEngineV1.pyx":449 * return self._castle_Q * * def isEnPassant(self): # <<<<<<<<<<<<<< @@ -10030,7 +10030,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_22isEnPassant(CYTHON_UNUSED PyObje /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.InfoMove.isEnPassant", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.InfoMove.isEnPassant", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -10038,7 +10038,7 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_22isEnPassant(CYTHON_UNUSED PyObje return __pyx_r; } -/* "LCEngine.pyx":452 +/* "LCEngineV1.pyx":452 * return self._ep * * def getExMoves(): # <<<<<<<<<<<<<< @@ -10047,20 +10047,20 @@ static PyObject *__pyx_pf_8LCEngine_8InfoMove_22isEnPassant(CYTHON_UNUSED PyObje */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_47getExMoves(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_47getExMoves = {"getExMoves", (PyCFunction)__pyx_pw_8LCEngine_47getExMoves, METH_NOARGS, 0}; -static PyObject *__pyx_pw_8LCEngine_47getExMoves(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { +static PyObject *__pyx_pw_10LCEngineV1_47getExMoves(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_47getExMoves = {"getExMoves", (PyCFunction)__pyx_pw_10LCEngineV1_47getExMoves, METH_NOARGS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_47getExMoves(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("getExMoves (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_46getExMoves(__pyx_self); + __pyx_r = __pyx_pf_10LCEngineV1_46getExMoves(__pyx_self); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_self) { +static PyObject *__pyx_pf_10LCEngineV1_46getExMoves(CYTHON_UNUSED PyObject *__pyx_self) { int __pyx_v_nmoves; PyObject *__pyx_v_nbase = NULL; PyObject *__pyx_v_li = NULL; @@ -10079,7 +10079,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s int __pyx_t_9; __Pyx_RefNannySetupContext("getExMoves", 0); - /* "LCEngine.pyx":453 + /* "LCEngineV1.pyx":453 * * def getExMoves(): * nmoves = numMoves() # <<<<<<<<<<<<<< @@ -10088,7 +10088,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s */ __pyx_v_nmoves = numMoves(); - /* "LCEngine.pyx":455 + /* "LCEngineV1.pyx":455 * nmoves = numMoves() * * nbase = numBaseMove() # <<<<<<<<<<<<<< @@ -10100,7 +10100,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s __pyx_v_nbase = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":456 + /* "LCEngineV1.pyx":456 * * nbase = numBaseMove() * li = [] # <<<<<<<<<<<<<< @@ -10112,7 +10112,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s __pyx_v_li = ((PyObject*)__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":457 + /* "LCEngineV1.pyx":457 * nbase = numBaseMove() * li = [] * for x in range(nmoves): # <<<<<<<<<<<<<< @@ -10172,7 +10172,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s __Pyx_XDECREF_SET(__pyx_v_x, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":458 + /* "LCEngineV1.pyx":458 * li = [] * for x in range(nmoves): * mv = InfoMove(x + nbase) # <<<<<<<<<<<<<< @@ -10232,7 +10232,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s __Pyx_XDECREF_SET(__pyx_v_mv, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":459 + /* "LCEngineV1.pyx":459 * for x in range(nmoves): * mv = InfoMove(x + nbase) * li.append(mv) # <<<<<<<<<<<<<< @@ -10241,7 +10241,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s */ __pyx_t_9 = __Pyx_PyList_Append(__pyx_v_li, __pyx_v_mv); if (unlikely(__pyx_t_9 == -1)) __PYX_ERR(0, 459, __pyx_L1_error) - /* "LCEngine.pyx":457 + /* "LCEngineV1.pyx":457 * nbase = numBaseMove() * li = [] * for x in range(nmoves): # <<<<<<<<<<<<<< @@ -10251,7 +10251,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s } __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":460 + /* "LCEngineV1.pyx":460 * mv = InfoMove(x + nbase) * li.append(mv) * return li # <<<<<<<<<<<<<< @@ -10263,7 +10263,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s __pyx_r = __pyx_v_li; goto __pyx_L0; - /* "LCEngine.pyx":452 + /* "LCEngineV1.pyx":452 * return self._ep * * def getExMoves(): # <<<<<<<<<<<<<< @@ -10279,7 +10279,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s __Pyx_XDECREF(__pyx_t_6); __Pyx_XDECREF(__pyx_t_7); __Pyx_XDECREF(__pyx_t_8); - __Pyx_AddTraceback("LCEngine.getExMoves", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.getExMoves", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_nbase); @@ -10291,7 +10291,7 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s return __pyx_r; } -/* "LCEngine.pyx":462 +/* "LCEngineV1.pyx":462 * return li * * def moveExPV(desde, hasta, coronacion): # <<<<<<<<<<<<<< @@ -10300,9 +10300,9 @@ static PyObject *__pyx_pf_8LCEngine_46getExMoves(CYTHON_UNUSED PyObject *__pyx_s */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_49moveExPV(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_49moveExPV = {"moveExPV", (PyCFunction)__pyx_pw_8LCEngine_49moveExPV, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_49moveExPV(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_49moveExPV(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_49moveExPV = {"moveExPV", (PyCFunction)__pyx_pw_10LCEngineV1_49moveExPV, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_49moveExPV(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_desde = 0; PyObject *__pyx_v_hasta = 0; PyObject *__pyx_v_coronacion = 0; @@ -10356,18 +10356,18 @@ static PyObject *__pyx_pw_8LCEngine_49moveExPV(PyObject *__pyx_self, PyObject *_ __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("moveExPV", 1, 3, 3, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 462, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.moveExPV", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.moveExPV", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_48moveExPV(__pyx_self, __pyx_v_desde, __pyx_v_hasta, __pyx_v_coronacion); + __pyx_r = __pyx_pf_10LCEngineV1_48moveExPV(__pyx_self, __pyx_v_desde, __pyx_v_hasta, __pyx_v_coronacion); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desde, PyObject *__pyx_v_hasta, PyObject *__pyx_v_coronacion) { +static PyObject *__pyx_pf_10LCEngineV1_48moveExPV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desde, PyObject *__pyx_v_hasta, PyObject *__pyx_v_coronacion) { int __pyx_v_num; PyObject *__pyx_v_infoMove = NULL; PyObject *__pyx_r = NULL; @@ -10385,7 +10385,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_RefNannySetupContext("moveExPV", 0); __Pyx_INCREF(__pyx_v_coronacion); - /* "LCEngine.pyx":463 + /* "LCEngineV1.pyx":463 * * def moveExPV(desde, hasta, coronacion): * if not coronacion: # <<<<<<<<<<<<<< @@ -10396,7 +10396,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel __pyx_t_2 = ((!__pyx_t_1) != 0); if (__pyx_t_2) { - /* "LCEngine.pyx":464 + /* "LCEngineV1.pyx":464 * def moveExPV(desde, hasta, coronacion): * if not coronacion: * coronacion = "" # <<<<<<<<<<<<<< @@ -10406,7 +10406,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_INCREF(__pyx_kp_s_); __Pyx_DECREF_SET(__pyx_v_coronacion, __pyx_kp_s_); - /* "LCEngine.pyx":463 + /* "LCEngineV1.pyx":463 * * def moveExPV(desde, hasta, coronacion): * if not coronacion: # <<<<<<<<<<<<<< @@ -10415,7 +10415,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel */ } - /* "LCEngine.pyx":466 + /* "LCEngineV1.pyx":466 * coronacion = "" * * num = searchMove( desde, hasta, coronacion ) # <<<<<<<<<<<<<< @@ -10427,7 +10427,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel __pyx_t_5 = __Pyx_PyObject_AsString(__pyx_v_coronacion); if (unlikely((!__pyx_t_5) && PyErr_Occurred())) __PYX_ERR(0, 466, __pyx_L1_error) __pyx_v_num = searchMove(__pyx_t_3, __pyx_t_4, __pyx_t_5); - /* "LCEngine.pyx":467 + /* "LCEngineV1.pyx":467 * * num = searchMove( desde, hasta, coronacion ) * if num == -1: # <<<<<<<<<<<<<< @@ -10437,7 +10437,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel __pyx_t_2 = ((__pyx_v_num == -1L) != 0); if (__pyx_t_2) { - /* "LCEngine.pyx":468 + /* "LCEngineV1.pyx":468 * num = searchMove( desde, hasta, coronacion ) * if num == -1: * return None # <<<<<<<<<<<<<< @@ -10449,7 +10449,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel __pyx_r = Py_None; goto __pyx_L0; - /* "LCEngine.pyx":467 + /* "LCEngineV1.pyx":467 * * num = searchMove( desde, hasta, coronacion ) * if num == -1: # <<<<<<<<<<<<<< @@ -10458,7 +10458,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel */ } - /* "LCEngine.pyx":470 + /* "LCEngineV1.pyx":470 * return None * * infoMove = InfoMove(num) # <<<<<<<<<<<<<< @@ -10518,7 +10518,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel __pyx_v_infoMove = __pyx_t_6; __pyx_t_6 = 0; - /* "LCEngine.pyx":471 + /* "LCEngineV1.pyx":471 * * infoMove = InfoMove(num) * make_nummove(num) # <<<<<<<<<<<<<< @@ -10527,7 +10527,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel */ make_nummove(__pyx_v_num); - /* "LCEngine.pyx":473 + /* "LCEngineV1.pyx":473 * make_nummove(num) * * return infoMove # <<<<<<<<<<<<<< @@ -10539,7 +10539,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel __pyx_r = __pyx_v_infoMove; goto __pyx_L0; - /* "LCEngine.pyx":462 + /* "LCEngineV1.pyx":462 * return li * * def moveExPV(desde, hasta, coronacion): # <<<<<<<<<<<<<< @@ -10554,7 +10554,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel __Pyx_XDECREF(__pyx_t_8); __Pyx_XDECREF(__pyx_t_9); __Pyx_XDECREF(__pyx_t_10); - __Pyx_AddTraceback("LCEngine.moveExPV", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.moveExPV", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_infoMove); @@ -10564,7 +10564,7 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel return __pyx_r; } -/* "LCEngine.pyx":475 +/* "LCEngineV1.pyx":475 * return infoMove * * def movePV(desde, hasta, coronacion): # <<<<<<<<<<<<<< @@ -10573,9 +10573,9 @@ static PyObject *__pyx_pf_8LCEngine_48moveExPV(CYTHON_UNUSED PyObject *__pyx_sel */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_51movePV(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_51movePV = {"movePV", (PyCFunction)__pyx_pw_8LCEngine_51movePV, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_51movePV(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_51movePV(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_51movePV = {"movePV", (PyCFunction)__pyx_pw_10LCEngineV1_51movePV, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_51movePV(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_desde = 0; PyObject *__pyx_v_hasta = 0; PyObject *__pyx_v_coronacion = 0; @@ -10629,18 +10629,18 @@ static PyObject *__pyx_pw_8LCEngine_51movePV(PyObject *__pyx_self, PyObject *__p __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("movePV", 1, 3, 3, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 475, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.movePV", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.movePV", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_50movePV(__pyx_self, __pyx_v_desde, __pyx_v_hasta, __pyx_v_coronacion); + __pyx_r = __pyx_pf_10LCEngineV1_50movePV(__pyx_self, __pyx_v_desde, __pyx_v_hasta, __pyx_v_coronacion); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desde, PyObject *__pyx_v_hasta, PyObject *__pyx_v_coronacion) { +static PyObject *__pyx_pf_10LCEngineV1_50movePV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_desde, PyObject *__pyx_v_hasta, PyObject *__pyx_v_coronacion) { int __pyx_v_num; PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations @@ -10652,7 +10652,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_RefNannySetupContext("movePV", 0); __Pyx_INCREF(__pyx_v_coronacion); - /* "LCEngine.pyx":476 + /* "LCEngineV1.pyx":476 * * def movePV(desde, hasta, coronacion): * if not coronacion: # <<<<<<<<<<<<<< @@ -10663,7 +10663,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_2 = ((!__pyx_t_1) != 0); if (__pyx_t_2) { - /* "LCEngine.pyx":477 + /* "LCEngineV1.pyx":477 * def movePV(desde, hasta, coronacion): * if not coronacion: * coronacion = "" # <<<<<<<<<<<<<< @@ -10673,7 +10673,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_INCREF(__pyx_kp_s_); __Pyx_DECREF_SET(__pyx_v_coronacion, __pyx_kp_s_); - /* "LCEngine.pyx":476 + /* "LCEngineV1.pyx":476 * * def movePV(desde, hasta, coronacion): * if not coronacion: # <<<<<<<<<<<<<< @@ -10682,7 +10682,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, */ } - /* "LCEngine.pyx":479 + /* "LCEngineV1.pyx":479 * coronacion = "" * * num = searchMove( desde, hasta, coronacion ) # <<<<<<<<<<<<<< @@ -10694,7 +10694,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_5 = __Pyx_PyObject_AsString(__pyx_v_coronacion); if (unlikely((!__pyx_t_5) && PyErr_Occurred())) __PYX_ERR(0, 479, __pyx_L1_error) __pyx_v_num = searchMove(__pyx_t_3, __pyx_t_4, __pyx_t_5); - /* "LCEngine.pyx":480 + /* "LCEngineV1.pyx":480 * * num = searchMove( desde, hasta, coronacion ) * if num == -1: # <<<<<<<<<<<<<< @@ -10704,7 +10704,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_2 = ((__pyx_v_num == -1L) != 0); if (__pyx_t_2) { - /* "LCEngine.pyx":481 + /* "LCEngineV1.pyx":481 * num = searchMove( desde, hasta, coronacion ) * if num == -1: * return False # <<<<<<<<<<<<<< @@ -10716,7 +10716,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, __pyx_r = Py_False; goto __pyx_L0; - /* "LCEngine.pyx":480 + /* "LCEngineV1.pyx":480 * * num = searchMove( desde, hasta, coronacion ) * if num == -1: # <<<<<<<<<<<<<< @@ -10725,7 +10725,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, */ } - /* "LCEngine.pyx":483 + /* "LCEngineV1.pyx":483 * return False * * make_nummove(num) # <<<<<<<<<<<<<< @@ -10734,7 +10734,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, */ make_nummove(__pyx_v_num); - /* "LCEngine.pyx":485 + /* "LCEngineV1.pyx":485 * make_nummove(num) * * return True # <<<<<<<<<<<<<< @@ -10746,7 +10746,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, __pyx_r = Py_True; goto __pyx_L0; - /* "LCEngine.pyx":475 + /* "LCEngineV1.pyx":475 * return infoMove * * def movePV(desde, hasta, coronacion): # <<<<<<<<<<<<<< @@ -10756,7 +10756,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, /* function exit code */ __pyx_L1_error:; - __Pyx_AddTraceback("LCEngine.movePV", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.movePV", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_coronacion); @@ -10765,7 +10765,7 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, return __pyx_r; } -/* "LCEngine.pyx":487 +/* "LCEngineV1.pyx":487 * return True * * def makeMove(move): # <<<<<<<<<<<<<< @@ -10774,20 +10774,20 @@ static PyObject *__pyx_pf_8LCEngine_50movePV(CYTHON_UNUSED PyObject *__pyx_self, */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_53makeMove(PyObject *__pyx_self, PyObject *__pyx_v_move); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_53makeMove = {"makeMove", (PyCFunction)__pyx_pw_8LCEngine_53makeMove, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_53makeMove(PyObject *__pyx_self, PyObject *__pyx_v_move) { +static PyObject *__pyx_pw_10LCEngineV1_53makeMove(PyObject *__pyx_self, PyObject *__pyx_v_move); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_53makeMove = {"makeMove", (PyCFunction)__pyx_pw_10LCEngineV1_53makeMove, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_53makeMove(PyObject *__pyx_self, PyObject *__pyx_v_move) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("makeMove (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_52makeMove(__pyx_self, ((PyObject *)__pyx_v_move)); + __pyx_r = __pyx_pf_10LCEngineV1_52makeMove(__pyx_self, ((PyObject *)__pyx_v_move)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_move) { +static PyObject *__pyx_pf_10LCEngineV1_52makeMove(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_move) { PyObject *__pyx_v_desde = NULL; PyObject *__pyx_v_hasta = NULL; PyObject *__pyx_v_coronacion = NULL; @@ -10801,7 +10801,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel int __pyx_t_5; __Pyx_RefNannySetupContext("makeMove", 0); - /* "LCEngine.pyx":488 + /* "LCEngineV1.pyx":488 * * def makeMove(move): * desde = move[:2] # <<<<<<<<<<<<<< @@ -10813,7 +10813,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel __pyx_v_desde = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":489 + /* "LCEngineV1.pyx":489 * def makeMove(move): * desde = move[:2] * hasta = move[2:4] # <<<<<<<<<<<<<< @@ -10825,7 +10825,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel __pyx_v_hasta = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":490 + /* "LCEngineV1.pyx":490 * desde = move[:2] * hasta = move[2:4] * coronacion = move[4:] # <<<<<<<<<<<<<< @@ -10837,7 +10837,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel __pyx_v_coronacion = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":491 + /* "LCEngineV1.pyx":491 * hasta = move[2:4] * coronacion = move[4:] * num = searchMove( desde, hasta, coronacion ) # <<<<<<<<<<<<<< @@ -10849,7 +10849,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel __pyx_t_4 = __Pyx_PyObject_AsString(__pyx_v_coronacion); if (unlikely((!__pyx_t_4) && PyErr_Occurred())) __PYX_ERR(0, 491, __pyx_L1_error) __pyx_v_num = searchMove(__pyx_t_2, __pyx_t_3, __pyx_t_4); - /* "LCEngine.pyx":492 + /* "LCEngineV1.pyx":492 * coronacion = move[4:] * num = searchMove( desde, hasta, coronacion ) * if num == -1: # <<<<<<<<<<<<<< @@ -10859,7 +10859,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel __pyx_t_5 = ((__pyx_v_num == -1L) != 0); if (__pyx_t_5) { - /* "LCEngine.pyx":493 + /* "LCEngineV1.pyx":493 * num = searchMove( desde, hasta, coronacion ) * if num == -1: * return False # <<<<<<<<<<<<<< @@ -10871,7 +10871,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel __pyx_r = Py_False; goto __pyx_L0; - /* "LCEngine.pyx":492 + /* "LCEngineV1.pyx":492 * coronacion = move[4:] * num = searchMove( desde, hasta, coronacion ) * if num == -1: # <<<<<<<<<<<<<< @@ -10880,7 +10880,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel */ } - /* "LCEngine.pyx":495 + /* "LCEngineV1.pyx":495 * return False * * make_nummove(num) # <<<<<<<<<<<<<< @@ -10889,7 +10889,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel */ make_nummove(__pyx_v_num); - /* "LCEngine.pyx":496 + /* "LCEngineV1.pyx":496 * * make_nummove(num) * return True # <<<<<<<<<<<<<< @@ -10901,7 +10901,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel __pyx_r = Py_True; goto __pyx_L0; - /* "LCEngine.pyx":487 + /* "LCEngineV1.pyx":487 * return True * * def makeMove(move): # <<<<<<<<<<<<<< @@ -10912,7 +10912,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel /* function exit code */ __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); - __Pyx_AddTraceback("LCEngine.makeMove", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.makeMove", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_desde); @@ -10923,7 +10923,7 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel return __pyx_r; } -/* "LCEngine.pyx":498 +/* "LCEngineV1.pyx":498 * return True * * def fen2fenM2(fen): # <<<<<<<<<<<<<< @@ -10932,20 +10932,20 @@ static PyObject *__pyx_pf_8LCEngine_52makeMove(CYTHON_UNUSED PyObject *__pyx_sel */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_55fen2fenM2(PyObject *__pyx_self, PyObject *__pyx_v_fen); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_55fen2fenM2 = {"fen2fenM2", (PyCFunction)__pyx_pw_8LCEngine_55fen2fenM2, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_55fen2fenM2(PyObject *__pyx_self, PyObject *__pyx_v_fen) { +static PyObject *__pyx_pw_10LCEngineV1_55fen2fenM2(PyObject *__pyx_self, PyObject *__pyx_v_fen); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_55fen2fenM2 = {"fen2fenM2", (PyCFunction)__pyx_pw_10LCEngineV1_55fen2fenM2, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_55fen2fenM2(PyObject *__pyx_self, PyObject *__pyx_v_fen) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("fen2fenM2 (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_54fen2fenM2(__pyx_self, ((PyObject *)__pyx_v_fen)); + __pyx_r = __pyx_pf_10LCEngineV1_54fen2fenM2(__pyx_self, ((PyObject *)__pyx_v_fen)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen) { +static PyObject *__pyx_pf_10LCEngineV1_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen) { PyObject *__pyx_v_sp1 = NULL; PyObject *__pyx_v_sp2 = NULL; PyObject *__pyx_r = NULL; @@ -10957,7 +10957,7 @@ static PyObject *__pyx_pf_8LCEngine_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_se PyObject *__pyx_t_5 = NULL; __Pyx_RefNannySetupContext("fen2fenM2", 0); - /* "LCEngine.pyx":499 + /* "LCEngineV1.pyx":499 * * def fen2fenM2(fen): * sp1 = fen.rfind(" ") # <<<<<<<<<<<<<< @@ -10972,7 +10972,7 @@ static PyObject *__pyx_pf_8LCEngine_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_se __pyx_v_sp1 = __pyx_t_2; __pyx_t_2 = 0; - /* "LCEngine.pyx":500 + /* "LCEngineV1.pyx":500 * def fen2fenM2(fen): * sp1 = fen.rfind(" ") * sp2 = fen.rfind(" ", 0, sp1) # <<<<<<<<<<<<<< @@ -11032,7 +11032,7 @@ static PyObject *__pyx_pf_8LCEngine_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_se __pyx_v_sp2 = __pyx_t_2; __pyx_t_2 = 0; - /* "LCEngine.pyx":501 + /* "LCEngineV1.pyx":501 * sp1 = fen.rfind(" ") * sp2 = fen.rfind(" ", 0, sp1) * return fen[:sp2] # <<<<<<<<<<<<<< @@ -11046,7 +11046,7 @@ static PyObject *__pyx_pf_8LCEngine_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_se __pyx_t_2 = 0; goto __pyx_L0; - /* "LCEngine.pyx":498 + /* "LCEngineV1.pyx":498 * return True * * def fen2fenM2(fen): # <<<<<<<<<<<<<< @@ -11060,7 +11060,7 @@ static PyObject *__pyx_pf_8LCEngine_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_se __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_5); - __Pyx_AddTraceback("LCEngine.fen2fenM2", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.fen2fenM2", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_sp1); @@ -11070,7 +11070,7 @@ static PyObject *__pyx_pf_8LCEngine_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_se return __pyx_r; } -/* "LCEngine.pyx":503 +/* "LCEngineV1.pyx":503 * return fen[:sp2] * * def setFenInicial(): # <<<<<<<<<<<<<< @@ -11079,20 +11079,20 @@ static PyObject *__pyx_pf_8LCEngine_54fen2fenM2(CYTHON_UNUSED PyObject *__pyx_se */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_57setFenInicial(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_57setFenInicial = {"setFenInicial", (PyCFunction)__pyx_pw_8LCEngine_57setFenInicial, METH_NOARGS, 0}; -static PyObject *__pyx_pw_8LCEngine_57setFenInicial(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { +static PyObject *__pyx_pw_10LCEngineV1_57setFenInicial(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_57setFenInicial = {"setFenInicial", (PyCFunction)__pyx_pw_10LCEngineV1_57setFenInicial, METH_NOARGS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_57setFenInicial(PyObject *__pyx_self, CYTHON_UNUSED PyObject *unused) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("setFenInicial (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_56setFenInicial(__pyx_self); + __pyx_r = __pyx_pf_10LCEngineV1_56setFenInicial(__pyx_self); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_56setFenInicial(CYTHON_UNUSED PyObject *__pyx_self) { +static PyObject *__pyx_pf_10LCEngineV1_56setFenInicial(CYTHON_UNUSED PyObject *__pyx_self) { PyObject *__pyx_v_inifen = NULL; PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations @@ -11102,7 +11102,7 @@ static PyObject *__pyx_pf_8LCEngine_56setFenInicial(CYTHON_UNUSED PyObject *__py PyObject *__pyx_t_4 = NULL; __Pyx_RefNannySetupContext("setFenInicial", 0); - /* "LCEngine.pyx":504 + /* "LCEngineV1.pyx":504 * * def setFenInicial(): * inifen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" # <<<<<<<<<<<<<< @@ -11112,7 +11112,7 @@ static PyObject *__pyx_pf_8LCEngine_56setFenInicial(CYTHON_UNUSED PyObject *__py __Pyx_INCREF(__pyx_kp_s_rnbqkbnr_pppppppp_8_8_8_8_PPPPPP); __pyx_v_inifen = __pyx_kp_s_rnbqkbnr_pppppppp_8_8_8_8_PPPPPP; - /* "LCEngine.pyx":505 + /* "LCEngineV1.pyx":505 * def setFenInicial(): * inifen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" * setFen(inifen) # <<<<<<<<<<<<<< @@ -11166,7 +11166,7 @@ static PyObject *__pyx_pf_8LCEngine_56setFenInicial(CYTHON_UNUSED PyObject *__py __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":503 + /* "LCEngineV1.pyx":503 * return fen[:sp2] * * def setFenInicial(): # <<<<<<<<<<<<<< @@ -11182,7 +11182,7 @@ static PyObject *__pyx_pf_8LCEngine_56setFenInicial(CYTHON_UNUSED PyObject *__py __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_4); - __Pyx_AddTraceback("LCEngine.setFenInicial", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.setFenInicial", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_inifen); @@ -11191,7 +11191,7 @@ static PyObject *__pyx_pf_8LCEngine_56setFenInicial(CYTHON_UNUSED PyObject *__py return __pyx_r; } -/* "LCEngine.pyx":507 +/* "LCEngineV1.pyx":507 * setFen(inifen) * * def makePV(pv): # <<<<<<<<<<<<<< @@ -11200,20 +11200,20 @@ static PyObject *__pyx_pf_8LCEngine_56setFenInicial(CYTHON_UNUSED PyObject *__py */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_59makePV(PyObject *__pyx_self, PyObject *__pyx_v_pv); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_59makePV = {"makePV", (PyCFunction)__pyx_pw_8LCEngine_59makePV, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_59makePV(PyObject *__pyx_self, PyObject *__pyx_v_pv) { +static PyObject *__pyx_pw_10LCEngineV1_59makePV(PyObject *__pyx_self, PyObject *__pyx_v_pv); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_59makePV = {"makePV", (PyCFunction)__pyx_pw_10LCEngineV1_59makePV, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_59makePV(PyObject *__pyx_self, PyObject *__pyx_v_pv) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("makePV (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_58makePV(__pyx_self, ((PyObject *)__pyx_v_pv)); + __pyx_r = __pyx_pf_10LCEngineV1_58makePV(__pyx_self, ((PyObject *)__pyx_v_pv)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pv) { +static PyObject *__pyx_pf_10LCEngineV1_58makePV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_pv) { PyObject *__pyx_v_move = NULL; PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations @@ -11227,7 +11227,7 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_t_8 = NULL; __Pyx_RefNannySetupContext("makePV", 0); - /* "LCEngine.pyx":508 + /* "LCEngineV1.pyx":508 * * def makePV(pv): * setFenInicial() # <<<<<<<<<<<<<< @@ -11256,7 +11256,7 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":509 + /* "LCEngineV1.pyx":509 * def makePV(pv): * setFenInicial() * if pv: # <<<<<<<<<<<<<< @@ -11266,7 +11266,7 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_4 = __Pyx_PyObject_IsTrue(__pyx_v_pv); if (unlikely(__pyx_t_4 < 0)) __PYX_ERR(0, 509, __pyx_L1_error) if (__pyx_t_4) { - /* "LCEngine.pyx":510 + /* "LCEngineV1.pyx":510 * setFenInicial() * if pv: * for move in pv.split(" "): # <<<<<<<<<<<<<< @@ -11321,7 +11321,7 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_XDECREF_SET(__pyx_v_move, __pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":511 + /* "LCEngineV1.pyx":511 * if pv: * for move in pv.split(" "): * makeMove(move) # <<<<<<<<<<<<<< @@ -11375,7 +11375,7 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_DECREF(__pyx_t_3); __pyx_t_3 = 0; __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":510 + /* "LCEngineV1.pyx":510 * setFenInicial() * if pv: * for move in pv.split(" "): # <<<<<<<<<<<<<< @@ -11385,7 +11385,7 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, } __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":509 + /* "LCEngineV1.pyx":509 * def makePV(pv): * setFenInicial() * if pv: # <<<<<<<<<<<<<< @@ -11394,7 +11394,7 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, */ } - /* "LCEngine.pyx":512 + /* "LCEngineV1.pyx":512 * for move in pv.split(" "): * makeMove(move) * return getFen() # <<<<<<<<<<<<<< @@ -11426,7 +11426,7 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_1 = 0; goto __pyx_L0; - /* "LCEngine.pyx":507 + /* "LCEngineV1.pyx":507 * setFen(inifen) * * def makePV(pv): # <<<<<<<<<<<<<< @@ -11441,7 +11441,7 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_7); __Pyx_XDECREF(__pyx_t_8); - __Pyx_AddTraceback("LCEngine.makePV", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.makePV", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_move); @@ -11450,7 +11450,7 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, return __pyx_r; } -/* "LCEngine.pyx":515 +/* "LCEngineV1.pyx":515 * * * def getCapturesFEN(fen): # <<<<<<<<<<<<<< @@ -11459,20 +11459,20 @@ static PyObject *__pyx_pf_8LCEngine_58makePV(CYTHON_UNUSED PyObject *__pyx_self, */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_61getCapturesFEN(PyObject *__pyx_self, PyObject *__pyx_v_fen); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_61getCapturesFEN = {"getCapturesFEN", (PyCFunction)__pyx_pw_8LCEngine_61getCapturesFEN, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_61getCapturesFEN(PyObject *__pyx_self, PyObject *__pyx_v_fen) { +static PyObject *__pyx_pw_10LCEngineV1_61getCapturesFEN(PyObject *__pyx_self, PyObject *__pyx_v_fen); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_61getCapturesFEN = {"getCapturesFEN", (PyCFunction)__pyx_pw_10LCEngineV1_61getCapturesFEN, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_61getCapturesFEN(PyObject *__pyx_self, PyObject *__pyx_v_fen) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("getCapturesFEN (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_60getCapturesFEN(__pyx_self, ((PyObject *)__pyx_v_fen)); + __pyx_r = __pyx_pf_10LCEngineV1_60getCapturesFEN(__pyx_self, ((PyObject *)__pyx_v_fen)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen) { +static PyObject *__pyx_pf_10LCEngineV1_60getCapturesFEN(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen) { int __pyx_v_nmoves; PyObject *__pyx_v_nbase = NULL; PyObject *__pyx_v_li = NULL; @@ -11492,7 +11492,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p int __pyx_t_10; __Pyx_RefNannySetupContext("getCapturesFEN", 0); - /* "LCEngine.pyx":516 + /* "LCEngineV1.pyx":516 * * def getCapturesFEN(fen): * setFen(fen) # <<<<<<<<<<<<<< @@ -11546,7 +11546,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":517 + /* "LCEngineV1.pyx":517 * def getCapturesFEN(fen): * setFen(fen) * nmoves = numMoves() # <<<<<<<<<<<<<< @@ -11555,7 +11555,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p */ __pyx_v_nmoves = numMoves(); - /* "LCEngine.pyx":518 + /* "LCEngineV1.pyx":518 * setFen(fen) * nmoves = numMoves() * nbase = numBaseMove() # <<<<<<<<<<<<<< @@ -11567,7 +11567,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p __pyx_v_nbase = __pyx_t_1; __pyx_t_1 = 0; - /* "LCEngine.pyx":519 + /* "LCEngineV1.pyx":519 * nmoves = numMoves() * nbase = numBaseMove() * li = [] # <<<<<<<<<<<<<< @@ -11579,7 +11579,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p __pyx_v_li = ((PyObject*)__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":520 + /* "LCEngineV1.pyx":520 * nbase = numBaseMove() * li = [] * for x in range(nmoves): # <<<<<<<<<<<<<< @@ -11639,7 +11639,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p __Pyx_XDECREF_SET(__pyx_v_x, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":521 + /* "LCEngineV1.pyx":521 * li = [] * for x in range(nmoves): * mv = InfoMove(x + nbase) # <<<<<<<<<<<<<< @@ -11699,7 +11699,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p __Pyx_XDECREF_SET(__pyx_v_mv, __pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":522 + /* "LCEngineV1.pyx":522 * for x in range(nmoves): * mv = InfoMove(x + nbase) * if mv.captura(): # <<<<<<<<<<<<<< @@ -11730,7 +11730,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; if (__pyx_t_9) { - /* "LCEngine.pyx":523 + /* "LCEngineV1.pyx":523 * mv = InfoMove(x + nbase) * if mv.captura(): * li.append(mv) # <<<<<<<<<<<<<< @@ -11739,7 +11739,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p */ __pyx_t_10 = __Pyx_PyList_Append(__pyx_v_li, __pyx_v_mv); if (unlikely(__pyx_t_10 == -1)) __PYX_ERR(0, 523, __pyx_L1_error) - /* "LCEngine.pyx":522 + /* "LCEngineV1.pyx":522 * for x in range(nmoves): * mv = InfoMove(x + nbase) * if mv.captura(): # <<<<<<<<<<<<<< @@ -11748,7 +11748,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p */ } - /* "LCEngine.pyx":520 + /* "LCEngineV1.pyx":520 * nbase = numBaseMove() * li = [] * for x in range(nmoves): # <<<<<<<<<<<<<< @@ -11758,7 +11758,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p } __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":524 + /* "LCEngineV1.pyx":524 * if mv.captura(): * li.append(mv) * return li # <<<<<<<<<<<<<< @@ -11770,7 +11770,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p __pyx_r = __pyx_v_li; goto __pyx_L0; - /* "LCEngine.pyx":515 + /* "LCEngineV1.pyx":515 * * * def getCapturesFEN(fen): # <<<<<<<<<<<<<< @@ -11786,7 +11786,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p __Pyx_XDECREF(__pyx_t_4); __Pyx_XDECREF(__pyx_t_7); __Pyx_XDECREF(__pyx_t_8); - __Pyx_AddTraceback("LCEngine.getCapturesFEN", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.getCapturesFEN", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_nbase); @@ -11798,7 +11798,7 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p return __pyx_r; } -/* "LCEngine.pyx":526 +/* "LCEngineV1.pyx":526 * return li * * def getCaptures(fen, siMB): # <<<<<<<<<<<<<< @@ -11807,9 +11807,9 @@ static PyObject *__pyx_pf_8LCEngine_60getCapturesFEN(CYTHON_UNUSED PyObject *__p */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_63getCaptures(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_63getCaptures = {"getCaptures", (PyCFunction)__pyx_pw_8LCEngine_63getCaptures, METH_VARARGS|METH_KEYWORDS, 0}; -static PyObject *__pyx_pw_8LCEngine_63getCaptures(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { +static PyObject *__pyx_pw_10LCEngineV1_63getCaptures(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_63getCaptures = {"getCaptures", (PyCFunction)__pyx_pw_10LCEngineV1_63getCaptures, METH_VARARGS|METH_KEYWORDS, 0}; +static PyObject *__pyx_pw_10LCEngineV1_63getCaptures(PyObject *__pyx_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_v_fen = 0; PyObject *__pyx_v_siMB = 0; PyObject *__pyx_r = 0; @@ -11854,18 +11854,18 @@ static PyObject *__pyx_pw_8LCEngine_63getCaptures(PyObject *__pyx_self, PyObject __pyx_L5_argtuple_error:; __Pyx_RaiseArgtupleInvalid("getCaptures", 1, 2, 2, PyTuple_GET_SIZE(__pyx_args)); __PYX_ERR(0, 526, __pyx_L3_error) __pyx_L3_error:; - __Pyx_AddTraceback("LCEngine.getCaptures", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.getCaptures", __pyx_clineno, __pyx_lineno, __pyx_filename); __Pyx_RefNannyFinishContext(); return NULL; __pyx_L4_argument_unpacking_done:; - __pyx_r = __pyx_pf_8LCEngine_62getCaptures(__pyx_self, __pyx_v_fen, __pyx_v_siMB); + __pyx_r = __pyx_pf_10LCEngineV1_62getCaptures(__pyx_self, __pyx_v_fen, __pyx_v_siMB); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_62getCaptures(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen, PyObject *__pyx_v_siMB) { +static PyObject *__pyx_pf_10LCEngineV1_62getCaptures(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen, PyObject *__pyx_v_siMB) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations int __pyx_t_1; @@ -11877,7 +11877,7 @@ static PyObject *__pyx_pf_8LCEngine_62getCaptures(CYTHON_UNUSED PyObject *__pyx_ __Pyx_RefNannySetupContext("getCaptures", 0); __Pyx_INCREF(__pyx_v_fen); - /* "LCEngine.pyx":527 + /* "LCEngineV1.pyx":527 * * def getCaptures(fen, siMB): * if not siMB: # <<<<<<<<<<<<<< @@ -11888,7 +11888,7 @@ static PyObject *__pyx_pf_8LCEngine_62getCaptures(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_2 = ((!__pyx_t_1) != 0); if (__pyx_t_2) { - /* "LCEngine.pyx":528 + /* "LCEngineV1.pyx":528 * def getCaptures(fen, siMB): * if not siMB: * fen = fenOB(fen) # <<<<<<<<<<<<<< @@ -11943,7 +11943,7 @@ static PyObject *__pyx_pf_8LCEngine_62getCaptures(CYTHON_UNUSED PyObject *__pyx_ __Pyx_DECREF_SET(__pyx_v_fen, __pyx_t_3); __pyx_t_3 = 0; - /* "LCEngine.pyx":527 + /* "LCEngineV1.pyx":527 * * def getCaptures(fen, siMB): * if not siMB: # <<<<<<<<<<<<<< @@ -11952,7 +11952,7 @@ static PyObject *__pyx_pf_8LCEngine_62getCaptures(CYTHON_UNUSED PyObject *__pyx_ */ } - /* "LCEngine.pyx":529 + /* "LCEngineV1.pyx":529 * if not siMB: * fen = fenOB(fen) * return getCapturesFEN(fen) # <<<<<<<<<<<<<< @@ -12009,7 +12009,7 @@ static PyObject *__pyx_pf_8LCEngine_62getCaptures(CYTHON_UNUSED PyObject *__pyx_ __pyx_t_3 = 0; goto __pyx_L0; - /* "LCEngine.pyx":526 + /* "LCEngineV1.pyx":526 * return li * * def getCaptures(fen, siMB): # <<<<<<<<<<<<<< @@ -12023,7 +12023,7 @@ static PyObject *__pyx_pf_8LCEngine_62getCaptures(CYTHON_UNUSED PyObject *__pyx_ __Pyx_XDECREF(__pyx_t_4); __Pyx_XDECREF(__pyx_t_5); __Pyx_XDECREF(__pyx_t_6); - __Pyx_AddTraceback("LCEngine.getCaptures", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.getCaptures", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_fen); @@ -12032,7 +12032,7 @@ static PyObject *__pyx_pf_8LCEngine_62getCaptures(CYTHON_UNUSED PyObject *__pyx_ return __pyx_r; } -/* "LCEngine.pyx":531 +/* "LCEngineV1.pyx":531 * return getCapturesFEN(fen) * * def fenOB(fen): # <<<<<<<<<<<<<< @@ -12041,20 +12041,20 @@ static PyObject *__pyx_pf_8LCEngine_62getCaptures(CYTHON_UNUSED PyObject *__pyx_ */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_65fenOB(PyObject *__pyx_self, PyObject *__pyx_v_fen); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_65fenOB = {"fenOB", (PyCFunction)__pyx_pw_8LCEngine_65fenOB, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_65fenOB(PyObject *__pyx_self, PyObject *__pyx_v_fen) { +static PyObject *__pyx_pw_10LCEngineV1_65fenOB(PyObject *__pyx_self, PyObject *__pyx_v_fen); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_65fenOB = {"fenOB", (PyCFunction)__pyx_pw_10LCEngineV1_65fenOB, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_65fenOB(PyObject *__pyx_self, PyObject *__pyx_v_fen) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("fenOB (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_64fenOB(__pyx_self, ((PyObject *)__pyx_v_fen)); + __pyx_r = __pyx_pf_10LCEngineV1_64fenOB(__pyx_self, ((PyObject *)__pyx_v_fen)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen) { +static PyObject *__pyx_pf_10LCEngineV1_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen) { PyObject *__pyx_v_li = NULL; PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations @@ -12063,7 +12063,7 @@ static PyObject *__pyx_pf_8LCEngine_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, int __pyx_t_3; __Pyx_RefNannySetupContext("fenOB", 0); - /* "LCEngine.pyx":532 + /* "LCEngineV1.pyx":532 * * def fenOB(fen): * li = fen.split(" ") # <<<<<<<<<<<<<< @@ -12078,7 +12078,7 @@ static PyObject *__pyx_pf_8LCEngine_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, __pyx_v_li = __pyx_t_2; __pyx_t_2 = 0; - /* "LCEngine.pyx":533 + /* "LCEngineV1.pyx":533 * def fenOB(fen): * li = fen.split(" ") * li[3] = "-" # <<<<<<<<<<<<<< @@ -12087,7 +12087,7 @@ static PyObject *__pyx_pf_8LCEngine_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, */ if (unlikely(__Pyx_SetItemInt(__pyx_v_li, 3, __pyx_kp_s__47, long, 1, __Pyx_PyInt_From_long, 0, 0, 1) < 0)) __PYX_ERR(0, 533, __pyx_L1_error) - /* "LCEngine.pyx":534 + /* "LCEngineV1.pyx":534 * li = fen.split(" ") * li[3] = "-" * li[1] = "w" if li[1] == "b" else "b" # <<<<<<<<<<<<<< @@ -12108,7 +12108,7 @@ static PyObject *__pyx_pf_8LCEngine_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, if (unlikely(__Pyx_SetItemInt(__pyx_v_li, 1, __pyx_t_2, long, 1, __Pyx_PyInt_From_long, 0, 0, 1) < 0)) __PYX_ERR(0, 534, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":535 + /* "LCEngineV1.pyx":535 * li[3] = "-" * li[1] = "w" if li[1] == "b" else "b" * return " ".join(li) # <<<<<<<<<<<<<< @@ -12122,7 +12122,7 @@ static PyObject *__pyx_pf_8LCEngine_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, __pyx_t_2 = 0; goto __pyx_L0; - /* "LCEngine.pyx":531 + /* "LCEngineV1.pyx":531 * return getCapturesFEN(fen) * * def fenOB(fen): # <<<<<<<<<<<<<< @@ -12134,7 +12134,7 @@ static PyObject *__pyx_pf_8LCEngine_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, __pyx_L1_error:; __Pyx_XDECREF(__pyx_t_1); __Pyx_XDECREF(__pyx_t_2); - __Pyx_AddTraceback("LCEngine.fenOB", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.fenOB", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XDECREF(__pyx_v_li); @@ -12143,7 +12143,7 @@ static PyObject *__pyx_pf_8LCEngine_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, return __pyx_r; } -/* "LCEngine.pyx":537 +/* "LCEngineV1.pyx":537 * return " ".join(li) * * def fenTerminado(fen): # <<<<<<<<<<<<<< @@ -12152,20 +12152,20 @@ static PyObject *__pyx_pf_8LCEngine_64fenOB(CYTHON_UNUSED PyObject *__pyx_self, */ /* Python wrapper */ -static PyObject *__pyx_pw_8LCEngine_67fenTerminado(PyObject *__pyx_self, PyObject *__pyx_v_fen); /*proto*/ -static PyMethodDef __pyx_mdef_8LCEngine_67fenTerminado = {"fenTerminado", (PyCFunction)__pyx_pw_8LCEngine_67fenTerminado, METH_O, 0}; -static PyObject *__pyx_pw_8LCEngine_67fenTerminado(PyObject *__pyx_self, PyObject *__pyx_v_fen) { +static PyObject *__pyx_pw_10LCEngineV1_67fenTerminado(PyObject *__pyx_self, PyObject *__pyx_v_fen); /*proto*/ +static PyMethodDef __pyx_mdef_10LCEngineV1_67fenTerminado = {"fenTerminado", (PyCFunction)__pyx_pw_10LCEngineV1_67fenTerminado, METH_O, 0}; +static PyObject *__pyx_pw_10LCEngineV1_67fenTerminado(PyObject *__pyx_self, PyObject *__pyx_v_fen) { PyObject *__pyx_r = 0; __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("fenTerminado (wrapper)", 0); - __pyx_r = __pyx_pf_8LCEngine_66fenTerminado(__pyx_self, ((PyObject *)__pyx_v_fen)); + __pyx_r = __pyx_pf_10LCEngineV1_66fenTerminado(__pyx_self, ((PyObject *)__pyx_v_fen)); /* function exit code */ __Pyx_RefNannyFinishContext(); return __pyx_r; } -static PyObject *__pyx_pf_8LCEngine_66fenTerminado(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen) { +static PyObject *__pyx_pf_10LCEngineV1_66fenTerminado(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_fen) { PyObject *__pyx_r = NULL; __Pyx_RefNannyDeclarations PyObject *__pyx_t_1 = NULL; @@ -12174,7 +12174,7 @@ static PyObject *__pyx_pf_8LCEngine_66fenTerminado(CYTHON_UNUSED PyObject *__pyx PyObject *__pyx_t_4 = NULL; __Pyx_RefNannySetupContext("fenTerminado", 0); - /* "LCEngine.pyx":538 + /* "LCEngineV1.pyx":538 * * def fenTerminado(fen): * return setFen(fen) == 0 # <<<<<<<<<<<<<< @@ -12233,7 +12233,7 @@ static PyObject *__pyx_pf_8LCEngine_66fenTerminado(CYTHON_UNUSED PyObject *__pyx __pyx_t_2 = 0; goto __pyx_L0; - /* "LCEngine.pyx":537 + /* "LCEngineV1.pyx":537 * return " ".join(li) * * def fenTerminado(fen): # <<<<<<<<<<<<<< @@ -12247,7 +12247,7 @@ static PyObject *__pyx_pf_8LCEngine_66fenTerminado(CYTHON_UNUSED PyObject *__pyx __Pyx_XDECREF(__pyx_t_2); __Pyx_XDECREF(__pyx_t_3); __Pyx_XDECREF(__pyx_t_4); - __Pyx_AddTraceback("LCEngine.fenTerminado", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("LCEngineV1.fenTerminado", __pyx_clineno, __pyx_lineno, __pyx_filename); __pyx_r = NULL; __pyx_L0:; __Pyx_XGIVEREF(__pyx_r); @@ -12266,7 +12266,7 @@ static struct PyModuleDef __pyx_moduledef = { #else PyModuleDef_HEAD_INIT, #endif - "LCEngine", + "LCEngineV1", 0, /* m_doc */ -1, /* m_size */ __pyx_methods /* m_methods */, @@ -12293,7 +12293,7 @@ static __Pyx_StringTabEntry __pyx_string_tab[] = { {&__pyx_n_s_InfoMove_mate, __pyx_k_InfoMove_mate, sizeof(__pyx_k_InfoMove_mate), 0, 0, 1, 1}, {&__pyx_n_s_InfoMove_movimiento, __pyx_k_InfoMove_movimiento, sizeof(__pyx_k_InfoMove_movimiento), 0, 0, 1, 1}, {&__pyx_n_s_InfoMove_pieza, __pyx_k_InfoMove_pieza, sizeof(__pyx_k_InfoMove_pieza), 0, 0, 1, 1}, - {&__pyx_n_s_LCEngine, __pyx_k_LCEngine, sizeof(__pyx_k_LCEngine), 0, 0, 1, 1}, + {&__pyx_n_s_LCEngineV1, __pyx_k_LCEngineV1, sizeof(__pyx_k_LCEngineV1), 0, 0, 1, 1}, {&__pyx_n_s_PGNreader, __pyx_k_PGNreader, sizeof(__pyx_k_PGNreader), 0, 0, 1, 1}, {&__pyx_n_s_PGNreader___enter, __pyx_k_PGNreader___enter, sizeof(__pyx_k_PGNreader___enter), 0, 0, 1, 1}, {&__pyx_n_s_PGNreader___exit, __pyx_k_PGNreader___exit, sizeof(__pyx_k_PGNreader___exit), 0, 0, 1, 1}, @@ -12494,7 +12494,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_RefNannyDeclarations __Pyx_RefNannySetupContext("__Pyx_InitCachedConstants", 0); - /* "LCEngine.pyx":98 + /* "LCEngineV1.pyx":98 * * def move2num(a1h8q): * num = a1Pos(a1h8q[:2]) + a1Pos(a1h8q[2:4])*64 # <<<<<<<<<<<<<< @@ -12508,7 +12508,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_slice__3); __Pyx_GIVEREF(__pyx_slice__3); - /* "LCEngine.pyx":118 + /* "LCEngineV1.pyx":118 * liM = [] * fil, col = posFC(npos) * for fi, ci in ( (+1, +1), (+1, -1), (-1, +1), (-1, -1), (+1, 0), (-1, 0), (0, +1), (0, -1) ): # <<<<<<<<<<<<<< @@ -12543,7 +12543,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__12); __Pyx_GIVEREF(__pyx_tuple__12); - /* "LCEngine.pyx":150 + /* "LCEngineV1.pyx":150 * fil, col = posFC(npos) * liM = [] * for fi, ci in ( (+1, +2), (+1, -2), (-1, +2), (-1, -2), (+2, +1), (+2, -1), (-2, +1), (-2, -1) ): # <<<<<<<<<<<<<< @@ -12578,7 +12578,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__21); __Pyx_GIVEREF(__pyx_tuple__21); - /* "LCEngine.pyx":179 + /* "LCEngineV1.pyx":179 * liM.append(sig2) * * for inc in ( +1, -1 ): # <<<<<<<<<<<<<< @@ -12589,7 +12589,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__22); __Pyx_GIVEREF(__pyx_tuple__22); - /* "LCEngine.pyx":250 + /* "LCEngineV1.pyx":250 * for y in lib: * if x not in no and y not in no: * nx = no[:] # <<<<<<<<<<<<<< @@ -12600,7 +12600,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_slice__23); __Pyx_GIVEREF(__pyx_slice__23); - /* "LCEngine.pyx":273 + /* "LCEngineV1.pyx":273 * def liNMinimo(x, y, celdas_ocupadas): * cdef int nv * ot = celdas_ocupadas[:] # <<<<<<<<<<<<<< @@ -12611,7 +12611,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_slice__24); __Pyx_GIVEREF(__pyx_slice__24); - /* "LCEngine.pyx":304 + /* "LCEngineV1.pyx":304 * def pv2xpv(pv): * if pv: * li = pv.split(" ") # <<<<<<<<<<<<<< @@ -12622,7 +12622,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__26); __Pyx_GIVEREF(__pyx_tuple__26); - /* "LCEngine.pyx":307 + /* "LCEngineV1.pyx":307 * lix = [] * for move in li: * d = chr(a1Pos(move[:2]) + 58) # 58 is an arbitrary number, to remain in range 58..122 # <<<<<<<<<<<<<< @@ -12633,7 +12633,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_slice__27); __Pyx_GIVEREF(__pyx_slice__27); - /* "LCEngine.pyx":308 + /* "LCEngineV1.pyx":308 * for move in li: * d = chr(a1Pos(move[:2]) + 58) # 58 is an arbitrary number, to remain in range 58..122 * h = chr(a1Pos(move[2:4]) + 58) # <<<<<<<<<<<<<< @@ -12644,7 +12644,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_slice__28); __Pyx_GIVEREF(__pyx_slice__28); - /* "LCEngine.pyx":309 + /* "LCEngineV1.pyx":309 * d = chr(a1Pos(move[:2]) + 58) # 58 is an arbitrary number, to remain in range 58..122 * h = chr(a1Pos(move[2:4]) + 58) * c = move[4:] # <<<<<<<<<<<<<< @@ -12655,7 +12655,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_slice__29); __Pyx_GIVEREF(__pyx_slice__29); - /* "LCEngine.pyx":311 + /* "LCEngineV1.pyx":311 * c = move[4:] * if c: * c = {"q": chr(50), "r": chr(51), "b": chr(52), "n": chr(53)}.get(c.lower(), "") # <<<<<<<<<<<<<< @@ -12675,7 +12675,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__33); __Pyx_GIVEREF(__pyx_tuple__33); - /* "LCEngine.pyx":374 + /* "LCEngineV1.pyx":374 * siW = not siW * * numMove = searchMove( pv[:2], pv[2:4], pv[4:] ) # <<<<<<<<<<<<<< @@ -12692,7 +12692,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_slice__37); __Pyx_GIVEREF(__pyx_slice__37); - /* "LCEngine.pyx":488 + /* "LCEngineV1.pyx":488 * * def makeMove(move): * desde = move[:2] # <<<<<<<<<<<<<< @@ -12703,7 +12703,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_slice__41); __Pyx_GIVEREF(__pyx_slice__41); - /* "LCEngine.pyx":489 + /* "LCEngineV1.pyx":489 * def makeMove(move): * desde = move[:2] * hasta = move[2:4] # <<<<<<<<<<<<<< @@ -12714,7 +12714,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_slice__42); __Pyx_GIVEREF(__pyx_slice__42); - /* "LCEngine.pyx":490 + /* "LCEngineV1.pyx":490 * desde = move[:2] * hasta = move[2:4] * coronacion = move[4:] # <<<<<<<<<<<<<< @@ -12725,7 +12725,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_slice__43); __Pyx_GIVEREF(__pyx_slice__43); - /* "LCEngine.pyx":499 + /* "LCEngineV1.pyx":499 * * def fen2fenM2(fen): * sp1 = fen.rfind(" ") # <<<<<<<<<<<<<< @@ -12736,7 +12736,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__44); __Pyx_GIVEREF(__pyx_tuple__44); - /* "LCEngine.pyx":510 + /* "LCEngineV1.pyx":510 * setFenInicial() * if pv: * for move in pv.split(" "): # <<<<<<<<<<<<<< @@ -12747,7 +12747,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__45); __Pyx_GIVEREF(__pyx_tuple__45); - /* "LCEngine.pyx":532 + /* "LCEngineV1.pyx":532 * * def fenOB(fen): * li = fen.split(" ") # <<<<<<<<<<<<<< @@ -12758,7 +12758,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__46); __Pyx_GIVEREF(__pyx_tuple__46); - /* "LCEngine.pyx":42 + /* "LCEngineV1.pyx":42 * * class PGNreader: * def __init__(self, fich, depth): # <<<<<<<<<<<<<< @@ -12770,7 +12770,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__48); __pyx_codeobj__49 = (PyObject*)__Pyx_PyCode_New(3, 0, 3, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__48, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_init, 42, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__49)) __PYX_ERR(0, 42, __pyx_L1_error) - /* "LCEngine.pyx":46 + /* "LCEngineV1.pyx":46 * self.depth = depth * * def __enter__(self): # <<<<<<<<<<<<<< @@ -12782,7 +12782,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__50); __pyx_codeobj__51 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__50, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_enter, 46, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__51)) __PYX_ERR(0, 46, __pyx_L1_error) - /* "LCEngine.pyx":50 + /* "LCEngineV1.pyx":50 * return self * * def __exit__(self, type, value, traceback): # <<<<<<<<<<<<<< @@ -12794,7 +12794,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__52); __pyx_codeobj__53 = (PyObject*)__Pyx_PyCode_New(4, 0, 4, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__52, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_exit, 50, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__53)) __PYX_ERR(0, 50, __pyx_L1_error) - /* "LCEngine.pyx":53 + /* "LCEngineV1.pyx":53 * pgn_stop() * * def __iter__(self): # <<<<<<<<<<<<<< @@ -12806,7 +12806,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__54); __pyx_codeobj__55 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__54, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_iter, 53, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__55)) __PYX_ERR(0, 53, __pyx_L1_error) - /* "LCEngine.pyx":56 + /* "LCEngineV1.pyx":56 * return self * * def next(self): # <<<<<<<<<<<<<< @@ -12818,7 +12818,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__56); __pyx_codeobj__57 = (PyObject*)__Pyx_PyCode_New(1, 0, 9, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__56, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_next, 56, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__57)) __PYX_ERR(0, 56, __pyx_L1_error) - /* "LCEngine.pyx":73 + /* "LCEngineV1.pyx":73 * * * def lc_pgn2pv(pgn1): # <<<<<<<<<<<<<< @@ -12830,7 +12830,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__58); __pyx_codeobj__59 = (PyObject*)__Pyx_PyCode_New(1, 0, 3, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__58, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_lc_pgn2pv, 73, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__59)) __PYX_ERR(0, 73, __pyx_L1_error) - /* "LCEngine.pyx":82 + /* "LCEngineV1.pyx":82 * * * def posFC(pos): # <<<<<<<<<<<<<< @@ -12842,7 +12842,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__60); __pyx_codeobj__61 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__60, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_posFC, 82, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__61)) __PYX_ERR(0, 82, __pyx_L1_error) - /* "LCEngine.pyx":85 + /* "LCEngineV1.pyx":85 * return pos / 8, pos % 8 * * def FCpos(f, c): # <<<<<<<<<<<<<< @@ -12854,7 +12854,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__62); __pyx_codeobj__63 = (PyObject*)__Pyx_PyCode_New(2, 0, 2, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__62, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_FCpos, 85, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__63)) __PYX_ERR(0, 85, __pyx_L1_error) - /* "LCEngine.pyx":88 + /* "LCEngineV1.pyx":88 * return f * 8 + c * * def posA1(pos): # <<<<<<<<<<<<<< @@ -12866,7 +12866,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__64); __pyx_codeobj__65 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__64, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_posA1, 88, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__65)) __PYX_ERR(0, 88, __pyx_L1_error) - /* "LCEngine.pyx":91 + /* "LCEngineV1.pyx":91 * return chr(pos % 8 + 97) + chr(pos / 8 + 49) * * def a1Pos(a1): # <<<<<<<<<<<<<< @@ -12878,7 +12878,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__66); __pyx_codeobj__67 = (PyObject*)__Pyx_PyCode_New(1, 0, 3, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__66, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_a1Pos, 91, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__67)) __PYX_ERR(0, 91, __pyx_L1_error) - /* "LCEngine.pyx":97 + /* "LCEngineV1.pyx":97 * return f * 8 + c * * def move2num(a1h8q): # <<<<<<<<<<<<<< @@ -12890,7 +12890,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__68); __pyx_codeobj__69 = (PyObject*)__Pyx_PyCode_New(1, 0, 2, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__68, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_move2num, 97, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__69)) __PYX_ERR(0, 97, __pyx_L1_error) - /* "LCEngine.pyx":103 + /* "LCEngineV1.pyx":103 * return num * * def num2move(num): # <<<<<<<<<<<<<< @@ -12902,7 +12902,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__70); __pyx_codeobj__71 = (PyObject*)__Pyx_PyCode_New(1, 0, 4, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__70, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_num2move, 103, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__71)) __PYX_ERR(0, 103, __pyx_L1_error) - /* "LCEngine.pyx":114 + /* "LCEngineV1.pyx":114 * return a1 + h8 + q * * def liK(npos): # <<<<<<<<<<<<<< @@ -12914,7 +12914,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__72); __pyx_codeobj__73 = (PyObject*)__Pyx_PyCode_New(1, 0, 8, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__72, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_liK, 114, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__73)) __PYX_ERR(0, 114, __pyx_L1_error) - /* "LCEngine.pyx":127 + /* "LCEngineV1.pyx":127 * * * def liBR(npos, fi, ci): # <<<<<<<<<<<<<< @@ -12926,7 +12926,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__74); __pyx_codeobj__75 = (PyObject*)__Pyx_PyCode_New(3, 0, 9, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__74, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_liBR, 127, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__75)) __PYX_ERR(0, 127, __pyx_L1_error) - /* "LCEngine.pyx":145 + /* "LCEngineV1.pyx":145 * * * def liN(npos): # <<<<<<<<<<<<<< @@ -12938,7 +12938,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__76); __pyx_codeobj__77 = (PyObject*)__Pyx_PyCode_New(1, 0, 9, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__76, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_liN, 145, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__77)) __PYX_ERR(0, 145, __pyx_L1_error) - /* "LCEngine.pyx":160 + /* "LCEngineV1.pyx":160 * return tuple(liM) * * def liP(npos, siW): # <<<<<<<<<<<<<< @@ -12950,7 +12950,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__78); __pyx_codeobj__79 = (PyObject*)__Pyx_PyCode_New(2, 0, 14, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__78, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_liP, 160, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__79)) __PYX_ERR(0, 160, __pyx_L1_error) - /* "LCEngine.pyx":189 + /* "LCEngineV1.pyx":189 * * dicK = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -12961,7 +12961,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__80); __Pyx_GIVEREF(__pyx_tuple__80); - /* "LCEngine.pyx":193 + /* "LCEngineV1.pyx":193 * * dicQ = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -12972,7 +12972,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__81); __Pyx_GIVEREF(__pyx_tuple__81); - /* "LCEngine.pyx":195 + /* "LCEngineV1.pyx":195 * for i in range(64): * li = [] * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (-1, 0), (0, 1), (0, -1) ): # <<<<<<<<<<<<<< @@ -13007,7 +13007,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__90); __Pyx_GIVEREF(__pyx_tuple__90); - /* "LCEngine.pyx":202 + /* "LCEngineV1.pyx":202 * * dicB = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -13018,7 +13018,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__91); __Pyx_GIVEREF(__pyx_tuple__91); - /* "LCEngine.pyx":204 + /* "LCEngineV1.pyx":204 * for i in range(64): * li = [] * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1) ): # <<<<<<<<<<<<<< @@ -13041,7 +13041,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__96); __Pyx_GIVEREF(__pyx_tuple__96); - /* "LCEngine.pyx":211 + /* "LCEngineV1.pyx":211 * * dicR = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -13052,7 +13052,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__97); __Pyx_GIVEREF(__pyx_tuple__97); - /* "LCEngine.pyx":213 + /* "LCEngineV1.pyx":213 * for i in range(64): * li = [] * for f_i, c_i in ( (1, 0), (-1, 0), (0, 1), (0, -1) ): # <<<<<<<<<<<<<< @@ -13075,7 +13075,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__102); __Pyx_GIVEREF(__pyx_tuple__102); - /* "LCEngine.pyx":220 + /* "LCEngineV1.pyx":220 * * dicN = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -13086,7 +13086,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__103); __Pyx_GIVEREF(__pyx_tuple__103); - /* "LCEngine.pyx":224 + /* "LCEngineV1.pyx":224 * * dicPW = {} * for i in range(8, 56): # <<<<<<<<<<<<<< @@ -13097,7 +13097,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__104); __Pyx_GIVEREF(__pyx_tuple__104); - /* "LCEngine.pyx":228 + /* "LCEngineV1.pyx":228 * * dicPB = {} * for i in range(8, 56): # <<<<<<<<<<<<<< @@ -13108,7 +13108,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GOTREF(__pyx_tuple__105); __Pyx_GIVEREF(__pyx_tuple__105); - /* "LCEngine.pyx":231 + /* "LCEngineV1.pyx":231 * dicPB[i] = liP(i, False) * * def knightmoves(a, b, no, nv, mx): # <<<<<<<<<<<<<< @@ -13120,7 +13120,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__106); __pyx_codeobj__107 = (PyObject*)__Pyx_PyCode_New(5, 0, 14, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__106, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_knightmoves, 231, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__107)) __PYX_ERR(0, 231, __pyx_L1_error) - /* "LCEngine.pyx":271 + /* "LCEngineV1.pyx":271 * return lidef * * def liNMinimo(x, y, celdas_ocupadas): # <<<<<<<<<<<<<< @@ -13132,7 +13132,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__108); __pyx_codeobj__109 = (PyObject*)__Pyx_PyCode_New(3, 0, 6, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__108, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_liNMinimo, 271, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__109)) __PYX_ERR(0, 271, __pyx_L1_error) - /* "LCEngine.pyx":282 + /* "LCEngineV1.pyx":282 * return li * * def xpv2lipv(xpv): # <<<<<<<<<<<<<< @@ -13144,7 +13144,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__110); __pyx_codeobj__111 = (PyObject*)__Pyx_PyCode_New(1, 0, 7, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__110, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_xpv2lipv, 282, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__111)) __PYX_ERR(0, 282, __pyx_L1_error) - /* "LCEngine.pyx":299 + /* "LCEngineV1.pyx":299 * return li * * def xpv2pv(xpv): # <<<<<<<<<<<<<< @@ -13156,7 +13156,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__112); __pyx_codeobj__113 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__112, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_xpv2pv, 299, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__113)) __PYX_ERR(0, 299, __pyx_L1_error) - /* "LCEngine.pyx":302 + /* "LCEngineV1.pyx":302 * return " ".join(xpv2lipv(xpv)) * * def pv2xpv(pv): # <<<<<<<<<<<<<< @@ -13168,7 +13168,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__114); __pyx_codeobj__115 = (PyObject*)__Pyx_PyCode_New(1, 0, 7, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__114, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_pv2xpv, 302, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__115)) __PYX_ERR(0, 302, __pyx_L1_error) - /* "LCEngine.pyx":317 + /* "LCEngineV1.pyx":317 * return "" * * def runFen( fen, depth, ms, level ): # <<<<<<<<<<<<<< @@ -13180,7 +13180,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__116); __pyx_codeobj__117 = (PyObject*)__Pyx_PyCode_New(4, 0, 5, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__116, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_runFen, 317, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__117)) __PYX_ERR(0, 317, __pyx_L1_error) - /* "LCEngine.pyx":323 + /* "LCEngineV1.pyx":323 * return x * * def setFen(fen): # <<<<<<<<<<<<<< @@ -13192,7 +13192,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__118); __pyx_codeobj__119 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__118, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_setFen, 323, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__119)) __PYX_ERR(0, 323, __pyx_L1_error) - /* "LCEngine.pyx":327 + /* "LCEngineV1.pyx":327 * return movegen() * * def getFen(): # <<<<<<<<<<<<<< @@ -13204,7 +13204,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__120); __pyx_codeobj__121 = (PyObject*)__Pyx_PyCode_New(0, 0, 2, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__120, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_getFen, 327, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__121)) __PYX_ERR(0, 327, __pyx_L1_error) - /* "LCEngine.pyx":333 + /* "LCEngineV1.pyx":333 * return x * * def getMoves(): # <<<<<<<<<<<<<< @@ -13216,7 +13216,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__122); __pyx_codeobj__123 = (PyObject*)__Pyx_PyCode_New(0, 0, 6, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__122, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_getMoves, 333, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__123)) __PYX_ERR(0, 333, __pyx_L1_error) - /* "LCEngine.pyx":346 + /* "LCEngineV1.pyx":346 * return li * * def getPGN(desdeA1H8, hastaA1H8, coronacion): # <<<<<<<<<<<<<< @@ -13228,7 +13228,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__124); __pyx_codeobj__125 = (PyObject*)__Pyx_PyCode_New(3, 0, 5, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__124, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_getPGN, 346, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__125)) __PYX_ERR(0, 346, __pyx_L1_error) - /* "LCEngine.pyx":359 + /* "LCEngineV1.pyx":359 * return san * * def xpv2pgn(xpv): # <<<<<<<<<<<<<< @@ -13240,7 +13240,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__126); __pyx_codeobj__127 = (PyObject*)__Pyx_PyCode_New(1, 0, 9, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__126, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_xpv2pgn, 359, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__127)) __PYX_ERR(0, 359, __pyx_L1_error) - /* "LCEngine.pyx":390 + /* "LCEngineV1.pyx":390 * return "".join(li) * * def isCheck(): # <<<<<<<<<<<<<< @@ -13249,7 +13249,7 @@ static int __Pyx_InitCachedConstants(void) { */ __pyx_codeobj__128 = (PyObject*)__Pyx_PyCode_New(0, 0, 0, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_isCheck, 390, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__128)) __PYX_ERR(0, 390, __pyx_L1_error) - /* "LCEngine.pyx":394 + /* "LCEngineV1.pyx":394 * * class InfoMove(object): * def __init__(self, num): # <<<<<<<<<<<<<< @@ -13261,7 +13261,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__129); __pyx_codeobj__130 = (PyObject*)__Pyx_PyCode_New(2, 0, 5, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__129, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_init, 394, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__130)) __PYX_ERR(0, 394, __pyx_L1_error) - /* "LCEngine.pyx":419 + /* "LCEngineV1.pyx":419 * self._capture = "x" in san * * def desde(self): # <<<<<<<<<<<<<< @@ -13273,7 +13273,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__131); __pyx_codeobj__132 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__131, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_desde, 419, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__132)) __PYX_ERR(0, 419, __pyx_L1_error) - /* "LCEngine.pyx":422 + /* "LCEngineV1.pyx":422 * return self._from * * def hasta(self): # <<<<<<<<<<<<<< @@ -13285,7 +13285,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__133); __pyx_codeobj__134 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__133, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_hasta, 422, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__134)) __PYX_ERR(0, 422, __pyx_L1_error) - /* "LCEngine.pyx":425 + /* "LCEngineV1.pyx":425 * return self._to * * def coronacion(self): # <<<<<<<<<<<<<< @@ -13297,7 +13297,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__135); __pyx_codeobj__136 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__135, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_coronacion, 425, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__136)) __PYX_ERR(0, 425, __pyx_L1_error) - /* "LCEngine.pyx":428 + /* "LCEngineV1.pyx":428 * return self._promotion.lower() * * def movimiento(self): # <<<<<<<<<<<<<< @@ -13309,7 +13309,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__137); __pyx_codeobj__138 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__137, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_movimiento, 428, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__138)) __PYX_ERR(0, 428, __pyx_L1_error) - /* "LCEngine.pyx":431 + /* "LCEngineV1.pyx":431 * return self._from+self._to+self._promotion.lower() * * def jaque(self): # <<<<<<<<<<<<<< @@ -13321,7 +13321,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__139); __pyx_codeobj__140 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__139, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_jaque, 431, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__140)) __PYX_ERR(0, 431, __pyx_L1_error) - /* "LCEngine.pyx":434 + /* "LCEngineV1.pyx":434 * return self._check * * def mate(self): # <<<<<<<<<<<<<< @@ -13333,7 +13333,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__141); __pyx_codeobj__142 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__141, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_mate_2, 434, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__142)) __PYX_ERR(0, 434, __pyx_L1_error) - /* "LCEngine.pyx":437 + /* "LCEngineV1.pyx":437 * return self._mate * * def captura(self): # <<<<<<<<<<<<<< @@ -13345,7 +13345,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__143); __pyx_codeobj__144 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__143, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_captura, 437, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__144)) __PYX_ERR(0, 437, __pyx_L1_error) - /* "LCEngine.pyx":440 + /* "LCEngineV1.pyx":440 * return self._capture * * def pieza(self): # <<<<<<<<<<<<<< @@ -13357,7 +13357,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__145); __pyx_codeobj__146 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__145, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_pieza, 440, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__146)) __PYX_ERR(0, 440, __pyx_L1_error) - /* "LCEngine.pyx":443 + /* "LCEngineV1.pyx":443 * return self._piece * * def isCastleK(self): # <<<<<<<<<<<<<< @@ -13369,7 +13369,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__147); __pyx_codeobj__148 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__147, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_isCastleK, 443, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__148)) __PYX_ERR(0, 443, __pyx_L1_error) - /* "LCEngine.pyx":446 + /* "LCEngineV1.pyx":446 * return self._castle_K * * def isCastleQ(self): # <<<<<<<<<<<<<< @@ -13381,7 +13381,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__149); __pyx_codeobj__150 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__149, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_isCastleQ, 446, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__150)) __PYX_ERR(0, 446, __pyx_L1_error) - /* "LCEngine.pyx":449 + /* "LCEngineV1.pyx":449 * return self._castle_Q * * def isEnPassant(self): # <<<<<<<<<<<<<< @@ -13393,7 +13393,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__151); __pyx_codeobj__152 = (PyObject*)__Pyx_PyCode_New(1, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__151, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_isEnPassant, 449, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__152)) __PYX_ERR(0, 449, __pyx_L1_error) - /* "LCEngine.pyx":452 + /* "LCEngineV1.pyx":452 * return self._ep * * def getExMoves(): # <<<<<<<<<<<<<< @@ -13405,7 +13405,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__153); __pyx_codeobj__154 = (PyObject*)__Pyx_PyCode_New(0, 0, 5, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__153, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_getExMoves, 452, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__154)) __PYX_ERR(0, 452, __pyx_L1_error) - /* "LCEngine.pyx":462 + /* "LCEngineV1.pyx":462 * return li * * def moveExPV(desde, hasta, coronacion): # <<<<<<<<<<<<<< @@ -13417,7 +13417,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__155); __pyx_codeobj__156 = (PyObject*)__Pyx_PyCode_New(3, 0, 5, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__155, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_moveExPV, 462, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__156)) __PYX_ERR(0, 462, __pyx_L1_error) - /* "LCEngine.pyx":475 + /* "LCEngineV1.pyx":475 * return infoMove * * def movePV(desde, hasta, coronacion): # <<<<<<<<<<<<<< @@ -13429,7 +13429,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__157); __pyx_codeobj__158 = (PyObject*)__Pyx_PyCode_New(3, 0, 4, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__157, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_movePV, 475, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__158)) __PYX_ERR(0, 475, __pyx_L1_error) - /* "LCEngine.pyx":487 + /* "LCEngineV1.pyx":487 * return True * * def makeMove(move): # <<<<<<<<<<<<<< @@ -13441,7 +13441,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__159); __pyx_codeobj__160 = (PyObject*)__Pyx_PyCode_New(1, 0, 5, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__159, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_makeMove, 487, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__160)) __PYX_ERR(0, 487, __pyx_L1_error) - /* "LCEngine.pyx":498 + /* "LCEngineV1.pyx":498 * return True * * def fen2fenM2(fen): # <<<<<<<<<<<<<< @@ -13453,7 +13453,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__161); __pyx_codeobj__162 = (PyObject*)__Pyx_PyCode_New(1, 0, 3, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__161, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_fen2fenM2, 498, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__162)) __PYX_ERR(0, 498, __pyx_L1_error) - /* "LCEngine.pyx":503 + /* "LCEngineV1.pyx":503 * return fen[:sp2] * * def setFenInicial(): # <<<<<<<<<<<<<< @@ -13465,7 +13465,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__163); __pyx_codeobj__164 = (PyObject*)__Pyx_PyCode_New(0, 0, 1, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__163, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_setFenInicial, 503, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__164)) __PYX_ERR(0, 503, __pyx_L1_error) - /* "LCEngine.pyx":507 + /* "LCEngineV1.pyx":507 * setFen(inifen) * * def makePV(pv): # <<<<<<<<<<<<<< @@ -13477,7 +13477,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__165); __pyx_codeobj__166 = (PyObject*)__Pyx_PyCode_New(1, 0, 2, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__165, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_makePV, 507, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__166)) __PYX_ERR(0, 507, __pyx_L1_error) - /* "LCEngine.pyx":515 + /* "LCEngineV1.pyx":515 * * * def getCapturesFEN(fen): # <<<<<<<<<<<<<< @@ -13489,7 +13489,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__167); __pyx_codeobj__168 = (PyObject*)__Pyx_PyCode_New(1, 0, 6, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__167, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_getCapturesFEN, 515, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__168)) __PYX_ERR(0, 515, __pyx_L1_error) - /* "LCEngine.pyx":526 + /* "LCEngineV1.pyx":526 * return li * * def getCaptures(fen, siMB): # <<<<<<<<<<<<<< @@ -13501,7 +13501,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__169); __pyx_codeobj__170 = (PyObject*)__Pyx_PyCode_New(2, 0, 2, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__169, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_getCaptures, 526, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__170)) __PYX_ERR(0, 526, __pyx_L1_error) - /* "LCEngine.pyx":531 + /* "LCEngineV1.pyx":531 * return getCapturesFEN(fen) * * def fenOB(fen): # <<<<<<<<<<<<<< @@ -13513,7 +13513,7 @@ static int __Pyx_InitCachedConstants(void) { __Pyx_GIVEREF(__pyx_tuple__171); __pyx_codeobj__172 = (PyObject*)__Pyx_PyCode_New(1, 0, 2, 0, 0, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__171, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_c_correr_lucaschess_pyLC12_LCEng, __pyx_n_s_fenOB, 531, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__172)) __PYX_ERR(0, 531, __pyx_L1_error) - /* "LCEngine.pyx":537 + /* "LCEngineV1.pyx":537 * return " ".join(li) * * def fenTerminado(fen): # <<<<<<<<<<<<<< @@ -13557,11 +13557,11 @@ static int __Pyx_InitGlobals(void) { } #if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC initLCEngine(void); /*proto*/ -PyMODINIT_FUNC initLCEngine(void) +PyMODINIT_FUNC initLCEngineV1(void); /*proto*/ +PyMODINIT_FUNC initLCEngineV1(void) #else -PyMODINIT_FUNC PyInit_LCEngine(void); /*proto*/ -PyMODINIT_FUNC PyInit_LCEngine(void) +PyMODINIT_FUNC PyInit_LCEngineV1(void); /*proto*/ +PyMODINIT_FUNC PyInit_LCEngineV1(void) #endif { PyObject *__pyx_t_1 = NULL; @@ -13589,7 +13589,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) Py_FatalError("failed to import 'refnanny' module"); } #endif - __Pyx_RefNannySetupContext("PyMODINIT_FUNC PyInit_LCEngine(void)", 0); + __Pyx_RefNannySetupContext("PyMODINIT_FUNC PyInit_LCEngineV1(void)", 0); if (__Pyx_check_binary_version() < 0) __PYX_ERR(0, 1, __pyx_L1_error) __pyx_empty_tuple = PyTuple_New(0); if (unlikely(!__pyx_empty_tuple)) __PYX_ERR(0, 1, __pyx_L1_error) __pyx_empty_bytes = PyBytes_FromStringAndSize("", 0); if (unlikely(!__pyx_empty_bytes)) __PYX_ERR(0, 1, __pyx_L1_error) @@ -13618,7 +13618,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) #endif /*--- Module creation code ---*/ #if PY_MAJOR_VERSION < 3 - __pyx_m = Py_InitModule4("LCEngine", __pyx_methods, 0, 0, PYTHON_API_VERSION); Py_XINCREF(__pyx_m); + __pyx_m = Py_InitModule4("LCEngineV1", __pyx_methods, 0, 0, PYTHON_API_VERSION); Py_XINCREF(__pyx_m); #else __pyx_m = PyModule_Create(&__pyx_moduledef); #endif @@ -13635,14 +13635,14 @@ PyMODINIT_FUNC PyInit_LCEngine(void) #if PY_MAJOR_VERSION < 3 && (__PYX_DEFAULT_STRING_ENCODING_IS_ASCII || __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT) if (__Pyx_init_sys_getdefaultencoding_params() < 0) __PYX_ERR(0, 1, __pyx_L1_error) #endif - if (__pyx_module_is_main_LCEngine) { + if (__pyx_module_is_main_LCEngineV1) { if (PyObject_SetAttrString(__pyx_m, "__name__", __pyx_n_s_main) < 0) __PYX_ERR(0, 1, __pyx_L1_error) } #if PY_MAJOR_VERSION >= 3 { PyObject *modules = PyImport_GetModuleDict(); if (unlikely(!modules)) __PYX_ERR(0, 1, __pyx_L1_error) - if (!PyDict_GetItemString(modules, "LCEngine")) { - if (unlikely(PyDict_SetItemString(modules, "LCEngine", __pyx_m) < 0)) __PYX_ERR(0, 1, __pyx_L1_error) + if (!PyDict_GetItemString(modules, "LCEngineV1")) { + if (unlikely(PyDict_SetItemString(modules, "LCEngineV1", __pyx_m) < 0)) __PYX_ERR(0, 1, __pyx_L1_error) } } #endif @@ -13662,77 +13662,77 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (__Pyx_patch_abc() < 0) __PYX_ERR(0, 1, __pyx_L1_error) #endif - /* "LCEngine.pyx":41 + /* "LCEngineV1.pyx":41 * * * class PGNreader: # <<<<<<<<<<<<<< * def __init__(self, fich, depth): * self.fich = fich */ - __pyx_t_1 = __Pyx_Py3MetaclassPrepare((PyObject *) NULL, __pyx_empty_tuple, __pyx_n_s_PGNreader, __pyx_n_s_PGNreader, (PyObject *) NULL, __pyx_n_s_LCEngine, (PyObject *) NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 41, __pyx_L1_error) + __pyx_t_1 = __Pyx_Py3MetaclassPrepare((PyObject *) NULL, __pyx_empty_tuple, __pyx_n_s_PGNreader, __pyx_n_s_PGNreader, (PyObject *) NULL, __pyx_n_s_LCEngineV1, (PyObject *) NULL); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 41, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); - /* "LCEngine.pyx":42 + /* "LCEngineV1.pyx":42 * * class PGNreader: * def __init__(self, fich, depth): # <<<<<<<<<<<<<< * self.fich = fich * self.depth = depth */ - __pyx_t_2 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_9PGNreader_1__init__, 0, __pyx_n_s_PGNreader___init, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__49)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 42, __pyx_L1_error) + __pyx_t_2 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_9PGNreader_1__init__, 0, __pyx_n_s_PGNreader___init, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__49)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 42, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); if (PyObject_SetItem(__pyx_t_1, __pyx_n_s_init, __pyx_t_2) < 0) __PYX_ERR(0, 42, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":46 + /* "LCEngineV1.pyx":46 * self.depth = depth * * def __enter__(self): # <<<<<<<<<<<<<< * pgn_start(self.fich, self.depth) * return self */ - __pyx_t_2 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_9PGNreader_3__enter__, 0, __pyx_n_s_PGNreader___enter, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__51)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 46, __pyx_L1_error) + __pyx_t_2 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_9PGNreader_3__enter__, 0, __pyx_n_s_PGNreader___enter, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__51)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 46, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); if (PyObject_SetItem(__pyx_t_1, __pyx_n_s_enter, __pyx_t_2) < 0) __PYX_ERR(0, 46, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":50 + /* "LCEngineV1.pyx":50 * return self * * def __exit__(self, type, value, traceback): # <<<<<<<<<<<<<< * pgn_stop() * */ - __pyx_t_2 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_9PGNreader_5__exit__, 0, __pyx_n_s_PGNreader___exit, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__53)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 50, __pyx_L1_error) + __pyx_t_2 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_9PGNreader_5__exit__, 0, __pyx_n_s_PGNreader___exit, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__53)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 50, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); if (PyObject_SetItem(__pyx_t_1, __pyx_n_s_exit, __pyx_t_2) < 0) __PYX_ERR(0, 50, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":53 + /* "LCEngineV1.pyx":53 * pgn_stop() * * def __iter__(self): # <<<<<<<<<<<<<< * return self * */ - __pyx_t_2 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_9PGNreader_7__iter__, 0, __pyx_n_s_PGNreader___iter, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__55)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 53, __pyx_L1_error) + __pyx_t_2 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_9PGNreader_7__iter__, 0, __pyx_n_s_PGNreader___iter, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__55)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 53, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); if (PyObject_SetItem(__pyx_t_1, __pyx_n_s_iter, __pyx_t_2) < 0) __PYX_ERR(0, 53, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":56 + /* "LCEngineV1.pyx":56 * return self * * def next(self): # <<<<<<<<<<<<<< * n = pgn_read() * if n: */ - __pyx_t_2 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_9PGNreader_9next, 0, __pyx_n_s_PGNreader_next, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__57)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 56, __pyx_L1_error) + __pyx_t_2 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_9PGNreader_9next, 0, __pyx_n_s_PGNreader_next, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__57)); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 56, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_2); if (PyObject_SetItem(__pyx_t_1, __pyx_n_s_next, __pyx_t_2) < 0) __PYX_ERR(0, 56, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":41 + /* "LCEngineV1.pyx":41 * * * class PGNreader: # <<<<<<<<<<<<<< @@ -13745,139 +13745,139 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":73 + /* "LCEngineV1.pyx":73 * * * def lc_pgn2pv(pgn1): # <<<<<<<<<<<<<< * cdef char pv[10]; * resp = pgn2pv(pgn1, pv) */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_1lc_pgn2pv, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 73, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_1lc_pgn2pv, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 73, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_lc_pgn2pv, __pyx_t_1) < 0) __PYX_ERR(0, 73, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":82 + /* "LCEngineV1.pyx":82 * * * def posFC(pos): # <<<<<<<<<<<<<< * return pos / 8, pos % 8 * */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_3posFC, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 82, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_3posFC, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 82, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_posFC, __pyx_t_1) < 0) __PYX_ERR(0, 82, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":85 + /* "LCEngineV1.pyx":85 * return pos / 8, pos % 8 * * def FCpos(f, c): # <<<<<<<<<<<<<< * return f * 8 + c * */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_5FCpos, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 85, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_5FCpos, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 85, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_FCpos, __pyx_t_1) < 0) __PYX_ERR(0, 85, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":88 + /* "LCEngineV1.pyx":88 * return f * 8 + c * * def posA1(pos): # <<<<<<<<<<<<<< * return chr(pos % 8 + 97) + chr(pos / 8 + 49) * */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_7posA1, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 88, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_7posA1, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 88, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_posA1, __pyx_t_1) < 0) __PYX_ERR(0, 88, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":91 + /* "LCEngineV1.pyx":91 * return chr(pos % 8 + 97) + chr(pos / 8 + 49) * * def a1Pos(a1): # <<<<<<<<<<<<<< * cdef int f, c * f = ord(a1[1]) - 49 */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_9a1Pos, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 91, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_9a1Pos, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 91, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_a1Pos, __pyx_t_1) < 0) __PYX_ERR(0, 91, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":97 + /* "LCEngineV1.pyx":97 * return f * 8 + c * * def move2num(a1h8q): # <<<<<<<<<<<<<< * num = a1Pos(a1h8q[:2]) + a1Pos(a1h8q[2:4])*64 * if len(a1h8q)>4: */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_11move2num, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 97, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_11move2num, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 97, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_move2num, __pyx_t_1) < 0) __PYX_ERR(0, 97, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":103 + /* "LCEngineV1.pyx":103 * return num * * def num2move(num): # <<<<<<<<<<<<<< * a1 = posA1(num%64) * num /= 64 */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_13num2move, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 103, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_13num2move, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 103, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_num2move, __pyx_t_1) < 0) __PYX_ERR(0, 103, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":114 + /* "LCEngineV1.pyx":114 * return a1 + h8 + q * * def liK(npos): # <<<<<<<<<<<<<< * cdef int fil, col, ft, ct * liM = [] */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_15liK, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 114, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_15liK, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 114, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_liK, __pyx_t_1) < 0) __PYX_ERR(0, 114, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":127 + /* "LCEngineV1.pyx":127 * * * def liBR(npos, fi, ci): # <<<<<<<<<<<<<< * cdef int fil, col, ft, ct * */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_17liBR, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 127, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_17liBR, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 127, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_liBR, __pyx_t_1) < 0) __PYX_ERR(0, 127, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":145 + /* "LCEngineV1.pyx":145 * * * def liN(npos): # <<<<<<<<<<<<<< * cdef int fil, col, ft, ct * */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_19liN, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 145, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_19liN, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 145, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_liN, __pyx_t_1) < 0) __PYX_ERR(0, 145, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":160 + /* "LCEngineV1.pyx":160 * return tuple(liM) * * def liP(npos, siW): # <<<<<<<<<<<<<< * cdef int fil, col, ft, ct, inc * */ - __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_21liP, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 160, __pyx_L1_error) + __pyx_t_1 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_21liP, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 160, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_1); if (PyDict_SetItem(__pyx_d, __pyx_n_s_liP, __pyx_t_1) < 0) __PYX_ERR(0, 160, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":188 + /* "LCEngineV1.pyx":188 * return tuple(liM), tuple(liX) * * dicK = {} # <<<<<<<<<<<<<< @@ -13889,7 +13889,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_dicK, __pyx_t_1) < 0) __PYX_ERR(0, 188, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":189 + /* "LCEngineV1.pyx":189 * * dicK = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -13941,7 +13941,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_i, __pyx_t_1) < 0) __PYX_ERR(0, 189, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":190 + /* "LCEngineV1.pyx":190 * dicK = {} * for i in range(64): * dicK[i] = liK(i) # <<<<<<<<<<<<<< @@ -14007,7 +14007,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":189 + /* "LCEngineV1.pyx":189 * * dicK = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -14017,7 +14017,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) } __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":192 + /* "LCEngineV1.pyx":192 * dicK[i] = liK(i) * * dicQ = {} # <<<<<<<<<<<<<< @@ -14029,7 +14029,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_dicQ, __pyx_t_2) < 0) __PYX_ERR(0, 192, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":193 + /* "LCEngineV1.pyx":193 * * dicQ = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -14081,7 +14081,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_i, __pyx_t_2) < 0) __PYX_ERR(0, 193, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":194 + /* "LCEngineV1.pyx":194 * dicQ = {} * for i in range(64): * li = [] # <<<<<<<<<<<<<< @@ -14093,7 +14093,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_li, __pyx_t_2) < 0) __PYX_ERR(0, 194, __pyx_L1_error) __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":195 + /* "LCEngineV1.pyx":195 * for i in range(64): * li = [] * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (-1, 0), (0, 1), (0, -1) ): # <<<<<<<<<<<<<< @@ -14141,7 +14141,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_c_i, __pyx_t_6) < 0) __PYX_ERR(0, 195, __pyx_L1_error) __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - /* "LCEngine.pyx":196 + /* "LCEngineV1.pyx":196 * li = [] * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (-1, 0), (0, 1), (0, -1) ): * lin = liBR(i, f_i, c_i) # <<<<<<<<<<<<<< @@ -14213,7 +14213,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_lin, __pyx_t_8) < 0) __PYX_ERR(0, 196, __pyx_L1_error) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; - /* "LCEngine.pyx":197 + /* "LCEngineV1.pyx":197 * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (-1, 0), (0, 1), (0, -1) ): * lin = liBR(i, f_i, c_i) * if lin: # <<<<<<<<<<<<<< @@ -14226,7 +14226,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; if (__pyx_t_14) { - /* "LCEngine.pyx":198 + /* "LCEngineV1.pyx":198 * lin = liBR(i, f_i, c_i) * if lin: * li.append(lin) # <<<<<<<<<<<<<< @@ -14241,7 +14241,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - /* "LCEngine.pyx":197 + /* "LCEngineV1.pyx":197 * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (-1, 0), (0, 1), (0, -1) ): * lin = liBR(i, f_i, c_i) * if lin: # <<<<<<<<<<<<<< @@ -14250,7 +14250,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) */ } - /* "LCEngine.pyx":195 + /* "LCEngineV1.pyx":195 * for i in range(64): * li = [] * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (-1, 0), (0, 1), (0, -1) ): # <<<<<<<<<<<<<< @@ -14260,7 +14260,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) } __Pyx_DECREF(__pyx_t_2); __pyx_t_2 = 0; - /* "LCEngine.pyx":199 + /* "LCEngineV1.pyx":199 * if lin: * li.append(lin) * dicQ[i] = tuple(li) # <<<<<<<<<<<<<< @@ -14281,7 +14281,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - /* "LCEngine.pyx":193 + /* "LCEngineV1.pyx":193 * * dicQ = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -14291,7 +14291,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) } __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":201 + /* "LCEngineV1.pyx":201 * dicQ[i] = tuple(li) * * dicB = {} # <<<<<<<<<<<<<< @@ -14303,7 +14303,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_dicB, __pyx_t_1) < 0) __PYX_ERR(0, 201, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":202 + /* "LCEngineV1.pyx":202 * * dicB = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -14355,7 +14355,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_i, __pyx_t_1) < 0) __PYX_ERR(0, 202, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":203 + /* "LCEngineV1.pyx":203 * dicB = {} * for i in range(64): * li = [] # <<<<<<<<<<<<<< @@ -14367,7 +14367,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_li, __pyx_t_1) < 0) __PYX_ERR(0, 203, __pyx_L1_error) __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":204 + /* "LCEngineV1.pyx":204 * for i in range(64): * li = [] * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1) ): # <<<<<<<<<<<<<< @@ -14415,7 +14415,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_c_i, __pyx_t_13) < 0) __PYX_ERR(0, 204, __pyx_L1_error) __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":205 + /* "LCEngineV1.pyx":205 * li = [] * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1) ): * lin = liBR(i, f_i, c_i) # <<<<<<<<<<<<<< @@ -14487,7 +14487,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_lin, __pyx_t_8) < 0) __PYX_ERR(0, 205, __pyx_L1_error) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; - /* "LCEngine.pyx":206 + /* "LCEngineV1.pyx":206 * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1) ): * lin = liBR(i, f_i, c_i) * if lin: # <<<<<<<<<<<<<< @@ -14500,7 +14500,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; if (__pyx_t_14) { - /* "LCEngine.pyx":207 + /* "LCEngineV1.pyx":207 * lin = liBR(i, f_i, c_i) * if lin: * li.append(lin) # <<<<<<<<<<<<<< @@ -14515,7 +14515,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":206 + /* "LCEngineV1.pyx":206 * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1) ): * lin = liBR(i, f_i, c_i) * if lin: # <<<<<<<<<<<<<< @@ -14524,7 +14524,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) */ } - /* "LCEngine.pyx":204 + /* "LCEngineV1.pyx":204 * for i in range(64): * li = [] * for f_i, c_i in ( (1, 1), (1, -1), (-1, 1), (-1, -1) ): # <<<<<<<<<<<<<< @@ -14534,7 +14534,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) } __Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0; - /* "LCEngine.pyx":208 + /* "LCEngineV1.pyx":208 * if lin: * li.append(lin) * dicB[i] = tuple(li) # <<<<<<<<<<<<<< @@ -14555,7 +14555,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":202 + /* "LCEngineV1.pyx":202 * * dicB = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -14565,7 +14565,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) } __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - /* "LCEngine.pyx":210 + /* "LCEngineV1.pyx":210 * dicB[i] = tuple(li) * * dicR = {} # <<<<<<<<<<<<<< @@ -14577,7 +14577,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_dicR, __pyx_t_6) < 0) __PYX_ERR(0, 210, __pyx_L1_error) __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - /* "LCEngine.pyx":211 + /* "LCEngineV1.pyx":211 * * dicR = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -14629,7 +14629,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_i, __pyx_t_6) < 0) __PYX_ERR(0, 211, __pyx_L1_error) __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - /* "LCEngine.pyx":212 + /* "LCEngineV1.pyx":212 * dicR = {} * for i in range(64): * li = [] # <<<<<<<<<<<<<< @@ -14641,7 +14641,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_li, __pyx_t_6) < 0) __PYX_ERR(0, 212, __pyx_L1_error) __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - /* "LCEngine.pyx":213 + /* "LCEngineV1.pyx":213 * for i in range(64): * li = [] * for f_i, c_i in ( (1, 0), (-1, 0), (0, 1), (0, -1) ): # <<<<<<<<<<<<<< @@ -14689,7 +14689,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_c_i, __pyx_t_11) < 0) __PYX_ERR(0, 213, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":214 + /* "LCEngineV1.pyx":214 * li = [] * for f_i, c_i in ( (1, 0), (-1, 0), (0, 1), (0, -1) ): * lin = liBR(i, f_i, c_i) # <<<<<<<<<<<<<< @@ -14761,7 +14761,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_lin, __pyx_t_8) < 0) __PYX_ERR(0, 214, __pyx_L1_error) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; - /* "LCEngine.pyx":215 + /* "LCEngineV1.pyx":215 * for f_i, c_i in ( (1, 0), (-1, 0), (0, 1), (0, -1) ): * lin = liBR(i, f_i, c_i) * if lin: # <<<<<<<<<<<<<< @@ -14774,7 +14774,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; if (__pyx_t_14) { - /* "LCEngine.pyx":216 + /* "LCEngineV1.pyx":216 * lin = liBR(i, f_i, c_i) * if lin: * li.append(lin) # <<<<<<<<<<<<<< @@ -14789,7 +14789,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":215 + /* "LCEngineV1.pyx":215 * for f_i, c_i in ( (1, 0), (-1, 0), (0, 1), (0, -1) ): * lin = liBR(i, f_i, c_i) * if lin: # <<<<<<<<<<<<<< @@ -14798,7 +14798,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) */ } - /* "LCEngine.pyx":213 + /* "LCEngineV1.pyx":213 * for i in range(64): * li = [] * for f_i, c_i in ( (1, 0), (-1, 0), (0, 1), (0, -1) ): # <<<<<<<<<<<<<< @@ -14808,7 +14808,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) } __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; - /* "LCEngine.pyx":217 + /* "LCEngineV1.pyx":217 * if lin: * li.append(lin) * dicR[i] = tuple(li) # <<<<<<<<<<<<<< @@ -14829,7 +14829,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_8); __pyx_t_8 = 0; __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":211 + /* "LCEngineV1.pyx":211 * * dicR = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -14839,7 +14839,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) } __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":219 + /* "LCEngineV1.pyx":219 * dicR[i] = tuple(li) * * dicN = {} # <<<<<<<<<<<<<< @@ -14851,7 +14851,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_dicN, __pyx_t_13) < 0) __PYX_ERR(0, 219, __pyx_L1_error) __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":220 + /* "LCEngineV1.pyx":220 * * dicN = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -14903,7 +14903,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_i, __pyx_t_13) < 0) __PYX_ERR(0, 220, __pyx_L1_error) __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":221 + /* "LCEngineV1.pyx":221 * dicN = {} * for i in range(64): * dicN[i] = liN(i) # <<<<<<<<<<<<<< @@ -14969,7 +14969,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_10); __pyx_t_10 = 0; __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":220 + /* "LCEngineV1.pyx":220 * * dicN = {} * for i in range(64): # <<<<<<<<<<<<<< @@ -14979,7 +14979,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) } __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":223 + /* "LCEngineV1.pyx":223 * dicN[i] = liN(i) * * dicPW = {} # <<<<<<<<<<<<<< @@ -14991,7 +14991,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_dicPW, __pyx_t_11) < 0) __PYX_ERR(0, 223, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":224 + /* "LCEngineV1.pyx":224 * * dicPW = {} * for i in range(8, 56): # <<<<<<<<<<<<<< @@ -15043,7 +15043,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_i, __pyx_t_11) < 0) __PYX_ERR(0, 224, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":225 + /* "LCEngineV1.pyx":225 * dicPW = {} * for i in range(8, 56): * dicPW[i] = liP(i, True) # <<<<<<<<<<<<<< @@ -15110,7 +15110,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":224 + /* "LCEngineV1.pyx":224 * * dicPW = {} * for i in range(8, 56): # <<<<<<<<<<<<<< @@ -15120,7 +15120,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) } __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":227 + /* "LCEngineV1.pyx":227 * dicPW[i] = liP(i, True) * * dicPB = {} # <<<<<<<<<<<<<< @@ -15132,7 +15132,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_dicPB, __pyx_t_13) < 0) __PYX_ERR(0, 227, __pyx_L1_error) __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":228 + /* "LCEngineV1.pyx":228 * * dicPB = {} * for i in range(8, 56): # <<<<<<<<<<<<<< @@ -15184,7 +15184,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) if (PyDict_SetItem(__pyx_d, __pyx_n_s_i, __pyx_t_13) < 0) __PYX_ERR(0, 228, __pyx_L1_error) __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":229 + /* "LCEngineV1.pyx":229 * dicPB = {} * for i in range(8, 56): * dicPB[i] = liP(i, False) # <<<<<<<<<<<<<< @@ -15251,7 +15251,7 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0; __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; - /* "LCEngine.pyx":228 + /* "LCEngineV1.pyx":228 * * dicPB = {} * for i in range(8, 56): # <<<<<<<<<<<<<< @@ -15261,151 +15261,151 @@ PyMODINIT_FUNC PyInit_LCEngine(void) } __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":231 + /* "LCEngineV1.pyx":231 * dicPB[i] = liP(i, False) * * def knightmoves(a, b, no, nv, mx): # <<<<<<<<<<<<<< * if nv > mx: * return [] */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_23knightmoves, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 231, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_23knightmoves, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 231, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_knightmoves, __pyx_t_11) < 0) __PYX_ERR(0, 231, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":271 + /* "LCEngineV1.pyx":271 * return lidef * * def liNMinimo(x, y, celdas_ocupadas): # <<<<<<<<<<<<<< * cdef int nv * ot = celdas_ocupadas[:] */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_25liNMinimo, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 271, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_25liNMinimo, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 271, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_liNMinimo, __pyx_t_11) < 0) __PYX_ERR(0, 271, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":282 + /* "LCEngineV1.pyx":282 * return li * * def xpv2lipv(xpv): # <<<<<<<<<<<<<< * li = [] * siBlancas = True */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_27xpv2lipv, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 282, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_27xpv2lipv, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 282, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_xpv2lipv, __pyx_t_11) < 0) __PYX_ERR(0, 282, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":299 + /* "LCEngineV1.pyx":299 * return li * * def xpv2pv(xpv): # <<<<<<<<<<<<<< * return " ".join(xpv2lipv(xpv)) * */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_29xpv2pv, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 299, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_29xpv2pv, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 299, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_xpv2pv, __pyx_t_11) < 0) __PYX_ERR(0, 299, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":302 + /* "LCEngineV1.pyx":302 * return " ".join(xpv2lipv(xpv)) * * def pv2xpv(pv): # <<<<<<<<<<<<<< * if pv: * li = pv.split(" ") */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_31pv2xpv, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 302, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_31pv2xpv, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 302, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_pv2xpv, __pyx_t_11) < 0) __PYX_ERR(0, 302, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":317 + /* "LCEngineV1.pyx":317 * return "" * * def runFen( fen, depth, ms, level ): # <<<<<<<<<<<<<< * set_level(level) * x = playFen(fen, depth, ms) */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_33runFen, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 317, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_33runFen, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 317, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_runFen, __pyx_t_11) < 0) __PYX_ERR(0, 317, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":323 + /* "LCEngineV1.pyx":323 * return x * * def setFen(fen): # <<<<<<<<<<<<<< * fen_board(fen) * return movegen() */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_35setFen, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 323, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_35setFen, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 323, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_setFen, __pyx_t_11) < 0) __PYX_ERR(0, 323, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":327 + /* "LCEngineV1.pyx":327 * return movegen() * * def getFen(): # <<<<<<<<<<<<<< * cdef char fen[100] * board_fen(fen) */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_37getFen, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 327, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_37getFen, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 327, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_getFen, __pyx_t_11) < 0) __PYX_ERR(0, 327, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":333 + /* "LCEngineV1.pyx":333 * return x * * def getMoves(): # <<<<<<<<<<<<<< * cdef char pv[10] * cdef int nmoves, x, nbase */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_39getMoves, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 333, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_39getMoves, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 333, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_getMoves, __pyx_t_11) < 0) __PYX_ERR(0, 333, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":346 + /* "LCEngineV1.pyx":346 * return li * * def getPGN(desdeA1H8, hastaA1H8, coronacion): # <<<<<<<<<<<<<< * cdef char san[10] * */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_41getPGN, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 346, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_41getPGN, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 346, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_getPGN, __pyx_t_11) < 0) __PYX_ERR(0, 346, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":359 + /* "LCEngineV1.pyx":359 * return san * * def xpv2pgn(xpv): # <<<<<<<<<<<<<< * cdef char san[10] * setFenInicial() */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_43xpv2pgn, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 359, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_43xpv2pgn, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 359, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_xpv2pgn, __pyx_t_11) < 0) __PYX_ERR(0, 359, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":390 + /* "LCEngineV1.pyx":390 * return "".join(li) * * def isCheck(): # <<<<<<<<<<<<<< * return inCheck() * */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_45isCheck, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 390, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_45isCheck, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 390, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_isCheck, __pyx_t_11) < 0) __PYX_ERR(0, 390, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":393 + /* "LCEngineV1.pyx":393 * return inCheck() * * class InfoMove(object): # <<<<<<<<<<<<<< @@ -15419,154 +15419,154 @@ PyMODINIT_FUNC PyInit_LCEngine(void) PyTuple_SET_ITEM(__pyx_t_11, 0, __pyx_builtin_object); __pyx_t_13 = __Pyx_CalculateMetaclass(NULL, __pyx_t_11); if (unlikely(!__pyx_t_13)) __PYX_ERR(0, 393, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_13); - __pyx_t_6 = __Pyx_Py3MetaclassPrepare(__pyx_t_13, __pyx_t_11, __pyx_n_s_InfoMove, __pyx_n_s_InfoMove, (PyObject *) NULL, __pyx_n_s_LCEngine, (PyObject *) NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 393, __pyx_L1_error) + __pyx_t_6 = __Pyx_Py3MetaclassPrepare(__pyx_t_13, __pyx_t_11, __pyx_n_s_InfoMove, __pyx_n_s_InfoMove, (PyObject *) NULL, __pyx_n_s_LCEngineV1, (PyObject *) NULL); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 393, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_6); - /* "LCEngine.pyx":394 + /* "LCEngineV1.pyx":394 * * class InfoMove(object): * def __init__(self, num): # <<<<<<<<<<<<<< * cdef char pv[10] * cdef char info[10] */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_1__init__, 0, __pyx_n_s_InfoMove___init, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__130)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 394, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_1__init__, 0, __pyx_n_s_InfoMove___init, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__130)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 394, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_init, __pyx_t_5) < 0) __PYX_ERR(0, 394, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":419 + /* "LCEngineV1.pyx":419 * self._capture = "x" in san * * def desde(self): # <<<<<<<<<<<<<< * return self._from * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_3desde, 0, __pyx_n_s_InfoMove_desde, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__132)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 419, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_3desde, 0, __pyx_n_s_InfoMove_desde, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__132)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 419, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_desde, __pyx_t_5) < 0) __PYX_ERR(0, 419, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":422 + /* "LCEngineV1.pyx":422 * return self._from * * def hasta(self): # <<<<<<<<<<<<<< * return self._to * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_5hasta, 0, __pyx_n_s_InfoMove_hasta, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__134)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 422, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_5hasta, 0, __pyx_n_s_InfoMove_hasta, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__134)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 422, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_hasta, __pyx_t_5) < 0) __PYX_ERR(0, 422, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":425 + /* "LCEngineV1.pyx":425 * return self._to * * def coronacion(self): # <<<<<<<<<<<<<< * return self._promotion.lower() * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_7coronacion, 0, __pyx_n_s_InfoMove_coronacion, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__136)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 425, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_7coronacion, 0, __pyx_n_s_InfoMove_coronacion, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__136)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 425, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_coronacion, __pyx_t_5) < 0) __PYX_ERR(0, 425, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":428 + /* "LCEngineV1.pyx":428 * return self._promotion.lower() * * def movimiento(self): # <<<<<<<<<<<<<< * return self._from+self._to+self._promotion.lower() * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_9movimiento, 0, __pyx_n_s_InfoMove_movimiento, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__138)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 428, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_9movimiento, 0, __pyx_n_s_InfoMove_movimiento, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__138)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 428, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_movimiento, __pyx_t_5) < 0) __PYX_ERR(0, 428, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":431 + /* "LCEngineV1.pyx":431 * return self._from+self._to+self._promotion.lower() * * def jaque(self): # <<<<<<<<<<<<<< * return self._check * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_11jaque, 0, __pyx_n_s_InfoMove_jaque, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__140)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 431, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_11jaque, 0, __pyx_n_s_InfoMove_jaque, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__140)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 431, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_jaque, __pyx_t_5) < 0) __PYX_ERR(0, 431, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":434 + /* "LCEngineV1.pyx":434 * return self._check * * def mate(self): # <<<<<<<<<<<<<< * return self._mate * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_13mate, 0, __pyx_n_s_InfoMove_mate, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__142)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 434, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_13mate, 0, __pyx_n_s_InfoMove_mate, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__142)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 434, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_mate_2, __pyx_t_5) < 0) __PYX_ERR(0, 434, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":437 + /* "LCEngineV1.pyx":437 * return self._mate * * def captura(self): # <<<<<<<<<<<<<< * return self._capture * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_15captura, 0, __pyx_n_s_InfoMove_captura, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__144)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 437, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_15captura, 0, __pyx_n_s_InfoMove_captura, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__144)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 437, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_captura, __pyx_t_5) < 0) __PYX_ERR(0, 437, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":440 + /* "LCEngineV1.pyx":440 * return self._capture * * def pieza(self): # <<<<<<<<<<<<<< * return self._piece * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_17pieza, 0, __pyx_n_s_InfoMove_pieza, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__146)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 440, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_17pieza, 0, __pyx_n_s_InfoMove_pieza, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__146)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 440, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_pieza, __pyx_t_5) < 0) __PYX_ERR(0, 440, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":443 + /* "LCEngineV1.pyx":443 * return self._piece * * def isCastleK(self): # <<<<<<<<<<<<<< * return self._castle_K * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_19isCastleK, 0, __pyx_n_s_InfoMove_isCastleK, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__148)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 443, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_19isCastleK, 0, __pyx_n_s_InfoMove_isCastleK, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__148)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 443, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_isCastleK, __pyx_t_5) < 0) __PYX_ERR(0, 443, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":446 + /* "LCEngineV1.pyx":446 * return self._castle_K * * def isCastleQ(self): # <<<<<<<<<<<<<< * return self._castle_Q * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_21isCastleQ, 0, __pyx_n_s_InfoMove_isCastleQ, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__150)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 446, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_21isCastleQ, 0, __pyx_n_s_InfoMove_isCastleQ, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__150)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 446, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_isCastleQ, __pyx_t_5) < 0) __PYX_ERR(0, 446, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":449 + /* "LCEngineV1.pyx":449 * return self._castle_Q * * def isEnPassant(self): # <<<<<<<<<<<<<< * return self._ep * */ - __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_8LCEngine_8InfoMove_23isEnPassant, 0, __pyx_n_s_InfoMove_isEnPassant, NULL, __pyx_n_s_LCEngine, __pyx_d, ((PyObject *)__pyx_codeobj__152)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 449, __pyx_L1_error) + __pyx_t_5 = __Pyx_CyFunction_NewEx(&__pyx_mdef_10LCEngineV1_8InfoMove_23isEnPassant, 0, __pyx_n_s_InfoMove_isEnPassant, NULL, __pyx_n_s_LCEngineV1, __pyx_d, ((PyObject *)__pyx_codeobj__152)); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 449, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_5); if (PyObject_SetItem(__pyx_t_6, __pyx_n_s_isEnPassant, __pyx_t_5) < 0) __PYX_ERR(0, 449, __pyx_L1_error) __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0; - /* "LCEngine.pyx":393 + /* "LCEngineV1.pyx":393 * return inCheck() * * class InfoMove(object): # <<<<<<<<<<<<<< @@ -15581,139 +15581,139 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_DECREF(__pyx_t_13); __pyx_t_13 = 0; __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":452 + /* "LCEngineV1.pyx":452 * return self._ep * * def getExMoves(): # <<<<<<<<<<<<<< * nmoves = numMoves() * */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_47getExMoves, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 452, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_47getExMoves, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 452, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_getExMoves, __pyx_t_11) < 0) __PYX_ERR(0, 452, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":462 + /* "LCEngineV1.pyx":462 * return li * * def moveExPV(desde, hasta, coronacion): # <<<<<<<<<<<<<< * if not coronacion: * coronacion = "" */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_49moveExPV, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 462, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_49moveExPV, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 462, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_moveExPV, __pyx_t_11) < 0) __PYX_ERR(0, 462, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":475 + /* "LCEngineV1.pyx":475 * return infoMove * * def movePV(desde, hasta, coronacion): # <<<<<<<<<<<<<< * if not coronacion: * coronacion = "" */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_51movePV, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 475, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_51movePV, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 475, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_movePV, __pyx_t_11) < 0) __PYX_ERR(0, 475, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":487 + /* "LCEngineV1.pyx":487 * return True * * def makeMove(move): # <<<<<<<<<<<<<< * desde = move[:2] * hasta = move[2:4] */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_53makeMove, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 487, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_53makeMove, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 487, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_makeMove, __pyx_t_11) < 0) __PYX_ERR(0, 487, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":498 + /* "LCEngineV1.pyx":498 * return True * * def fen2fenM2(fen): # <<<<<<<<<<<<<< * sp1 = fen.rfind(" ") * sp2 = fen.rfind(" ", 0, sp1) */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_55fen2fenM2, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 498, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_55fen2fenM2, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 498, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_fen2fenM2, __pyx_t_11) < 0) __PYX_ERR(0, 498, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":503 + /* "LCEngineV1.pyx":503 * return fen[:sp2] * * def setFenInicial(): # <<<<<<<<<<<<<< * inifen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" * setFen(inifen) */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_57setFenInicial, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 503, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_57setFenInicial, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 503, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_setFenInicial, __pyx_t_11) < 0) __PYX_ERR(0, 503, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":507 + /* "LCEngineV1.pyx":507 * setFen(inifen) * * def makePV(pv): # <<<<<<<<<<<<<< * setFenInicial() * if pv: */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_59makePV, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 507, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_59makePV, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 507, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_makePV, __pyx_t_11) < 0) __PYX_ERR(0, 507, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":515 + /* "LCEngineV1.pyx":515 * * * def getCapturesFEN(fen): # <<<<<<<<<<<<<< * setFen(fen) * nmoves = numMoves() */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_61getCapturesFEN, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 515, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_61getCapturesFEN, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 515, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_getCapturesFEN, __pyx_t_11) < 0) __PYX_ERR(0, 515, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":526 + /* "LCEngineV1.pyx":526 * return li * * def getCaptures(fen, siMB): # <<<<<<<<<<<<<< * if not siMB: * fen = fenOB(fen) */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_63getCaptures, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 526, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_63getCaptures, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 526, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_getCaptures, __pyx_t_11) < 0) __PYX_ERR(0, 526, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":531 + /* "LCEngineV1.pyx":531 * return getCapturesFEN(fen) * * def fenOB(fen): # <<<<<<<<<<<<<< * li = fen.split(" ") * li[3] = "-" */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_65fenOB, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 531, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_65fenOB, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 531, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_fenOB, __pyx_t_11) < 0) __PYX_ERR(0, 531, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":537 + /* "LCEngineV1.pyx":537 * return " ".join(li) * * def fenTerminado(fen): # <<<<<<<<<<<<<< * return setFen(fen) == 0 * */ - __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_8LCEngine_67fenTerminado, NULL, __pyx_n_s_LCEngine); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 537, __pyx_L1_error) + __pyx_t_11 = PyCFunction_NewEx(&__pyx_mdef_10LCEngineV1_67fenTerminado, NULL, __pyx_n_s_LCEngineV1); if (unlikely(!__pyx_t_11)) __PYX_ERR(0, 537, __pyx_L1_error) __Pyx_GOTREF(__pyx_t_11); if (PyDict_SetItem(__pyx_d, __pyx_n_s_fenTerminado, __pyx_t_11) < 0) __PYX_ERR(0, 537, __pyx_L1_error) __Pyx_DECREF(__pyx_t_11); __pyx_t_11 = 0; - /* "LCEngine.pyx":1 + /* "LCEngineV1.pyx":1 * cimport cython # <<<<<<<<<<<<<< * * @@ -15738,11 +15738,11 @@ PyMODINIT_FUNC PyInit_LCEngine(void) __Pyx_XDECREF(__pyx_t_13); if (__pyx_m) { if (__pyx_d) { - __Pyx_AddTraceback("init LCEngine", __pyx_clineno, __pyx_lineno, __pyx_filename); + __Pyx_AddTraceback("init LCEngineV1", __pyx_clineno, __pyx_lineno, __pyx_filename); } Py_DECREF(__pyx_m); __pyx_m = 0; } else if (!PyErr_Occurred()) { - PyErr_SetString(PyExc_ImportError, "init LCEngine"); + PyErr_SetString(PyExc_ImportError, "init LCEngineV1"); } __pyx_L0:; __Pyx_RefNannyFinishContext(); diff --git a/LCEngine/LCEngineV1.pyd b/LCEngine/LCEngineV1.pyd new file mode 100644 index 0000000..89a8a82 Binary files /dev/null and b/LCEngine/LCEngineV1.pyd differ diff --git a/LCEngine/LCEngine.pyx b/LCEngine/LCEngineV1.pyx similarity index 100% rename from LCEngine/LCEngine.pyx rename to LCEngine/LCEngineV1.pyx diff --git a/LCEngine/build/temp.win32-2.7/Release/LCEngine.exp b/LCEngine/build/temp.win32-2.7/Release/LCEngine.exp index 3f217bf..a034fea 100644 Binary files a/LCEngine/build/temp.win32-2.7/Release/LCEngine.exp and b/LCEngine/build/temp.win32-2.7/Release/LCEngine.exp differ diff --git a/LCEngine/build/temp.win32-2.7/Release/LCEngine.lib b/LCEngine/build/temp.win32-2.7/Release/LCEngine.lib index 7afa0dc..1e1cab3 100644 Binary files a/LCEngine/build/temp.win32-2.7/Release/LCEngine.lib and b/LCEngine/build/temp.win32-2.7/Release/LCEngine.lib differ diff --git a/LCEngine/build/temp.win32-2.7/Release/LCEngine.obj b/LCEngine/build/temp.win32-2.7/Release/LCEngine.obj index 855147b..971449d 100644 Binary files a/LCEngine/build/temp.win32-2.7/Release/LCEngine.obj and b/LCEngine/build/temp.win32-2.7/Release/LCEngine.obj differ diff --git a/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.exp b/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.exp new file mode 100644 index 0000000..d3b6e1c Binary files /dev/null and b/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.exp differ diff --git a/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.lib b/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.lib new file mode 100644 index 0000000..51b8fd1 Binary files /dev/null and b/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.lib differ diff --git a/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.obj b/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.obj new file mode 100644 index 0000000..a1ea4b9 Binary files /dev/null and b/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.obj differ diff --git a/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.pyd.manifest b/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.pyd.manifest new file mode 100644 index 0000000..7256947 --- /dev/null +++ b/LCEngine/build/temp.win32-2.7/Release/LCEngineV1.pyd.manifest @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/LCEngine/irina/pgn.c b/LCEngine/irina/pgn.c index 758e49a..54de560 100644 --- a/LCEngine/irina/pgn.c +++ b/LCEngine/irina/pgn.c @@ -451,7 +451,8 @@ int pgn_gen_pv(void) { while ( *c && *c != '}' ) c++; } - if( *c == ')' ) par--; + if( *c == '(' ) par++; + else if( *c == ')' ) par--; } if(raw) raw = false; break; diff --git a/LCEngine/setup.py b/LCEngine/setup.py index 22bc09d..c3d1158 100644 --- a/LCEngine/setup.py +++ b/LCEngine/setup.py @@ -4,5 +4,5 @@ from Cython.Build import cythonize setup( - ext_modules = cythonize([Extension("LCEngine", ["LCEngine.pyx"], libraries=["irina"])]) + ext_modules = cythonize([Extension("LCEngineV1", ["LCEngineV1.pyx"], libraries=["irina"])]) ) diff --git a/LCEngine/xcython_VC.bat b/LCEngine/xcython_VC.bat index 58dc484..59477e4 100644 --- a/LCEngine/xcython_VC.bat +++ b/LCEngine/xcython_VC.bat @@ -14,5 +14,5 @@ set LIBPATH=%VCINSTALLDIR%\Lib;%WindowsSdkDir%\Lib;%LIBPATH% python setup.py build_ext --inplace -copy LCEngine.pyd ..\Engines\Windows\_tools +copy LCEngineV1.pyd ..\Engines\Windows\_tools diff --git a/Linux/LinuxInstallation.md b/Linux/LinuxInstallation.md index 6aef53b..a49fb52 100644 --- a/Linux/LinuxInstallation.md +++ b/Linux/LinuxInstallation.md @@ -15,6 +15,7 @@ Linux installation from sources - sudo pip install Pillow - sudo pip install PhotoHash - sudo pip install Cython + - sudo pip install scandir 3) chmod +x -R Engines/Linux64/ - to ensure all engines have exec-permissions @@ -22,7 +23,7 @@ Linux installation from sources 4) 64 bits - cd LCEngine/irina - ./xmk_linux.sh - - cd .. #LXEngine + - cd .. - ./xcython_linux.sh diff --git a/Linux/xlinux_install.sh b/Linux/xlinux_install.sh index ffd964e..5e5ae86 100644 --- a/Linux/xlinux_install.sh +++ b/Linux/xlinux_install.sh @@ -1,12 +1,33 @@ #!/usr/bin/env bash +# execute as an user who is member of the sudoers, i.e. can elevate to root sudo apt-get install python-pip +# added next line to make sure to have most current pip version +pip install --upgrade pip sudo apt-get install python-dev sudo apt-get install python-qt4 sudo apt-get install python-pyaudio -sudo pip install psutil -sudo pip install pygal -sudo pip install chardet -sudo apt-get install python-chess -sudo pip install Pillow -sudo pip install PhotoHash -sudo pip install Cython +# added install of setuptools, is required for psutils, python-chess and PhotoHash +pip install setuptools +pip install psutil +pip install pygal +pip install chardet +pip install python-chess +pip install Pillow +pip install PhotoHash +pip install Cython + +# !! modify path to your installation directory of lucaschess!! +cd /usr/share/lucaschess + +# give read rights to group and others +sudo chmod -R 755 * +# give execution right to all executable files, especially chess engines +sudo chmod -R +x * +# give all rights to ./UsrData in order for all users to share settings +sudo chmod 777 UsrData + +# build Irina engine etc, refresh existing libirina.so and LCEngine.so +cd LCEngine/irina +./xmk_linux.sh +cd .. +./xcython_linux.sh \ No newline at end of file diff --git a/Locale/br/LC_MESSAGES/lucaschess.mo b/Locale/br/LC_MESSAGES/lucaschess.mo index 39b5bf5..3789990 100644 Binary files a/Locale/br/LC_MESSAGES/lucaschess.mo and b/Locale/br/LC_MESSAGES/lucaschess.mo differ diff --git a/Locale/es/LC_MESSAGES/lucaschess.mo b/Locale/es/LC_MESSAGES/lucaschess.mo index d2ecbd3..cce4c52 100644 Binary files a/Locale/es/LC_MESSAGES/lucaschess.mo and b/Locale/es/LC_MESSAGES/lucaschess.mo differ diff --git a/Locale/fr/LC_MESSAGES/lucaschess.mo b/Locale/fr/LC_MESSAGES/lucaschess.mo index fd2803a..4953e3c 100644 Binary files a/Locale/fr/LC_MESSAGES/lucaschess.mo and b/Locale/fr/LC_MESSAGES/lucaschess.mo differ diff --git a/Locale/it/LC_MESSAGES/lucaschess.mo b/Locale/it/LC_MESSAGES/lucaschess.mo index 736ad4b..1ee2a71 100644 Binary files a/Locale/it/LC_MESSAGES/lucaschess.mo and b/Locale/it/LC_MESSAGES/lucaschess.mo differ diff --git a/Locale/ro/LC_MESSAGES/lucaschess.mo b/Locale/ro/LC_MESSAGES/lucaschess.mo index 2c825c2..6f3eba5 100644 Binary files a/Locale/ro/LC_MESSAGES/lucaschess.mo and b/Locale/ro/LC_MESSAGES/lucaschess.mo differ diff --git a/Locale/ro/lang.ini b/Locale/ro/lang.ini index 7f13531..46e54df 100644 --- a/Locale/ro/lang.ini +++ b/Locale/ro/lang.ini @@ -1,3 +1,3 @@ NAME=Română AUTHOR=Dan-Alexandru Raportaru -%=19 \ No newline at end of file +%=18 \ No newline at end of file diff --git a/Locale/tr/LC_MESSAGES/lucaschess.mo b/Locale/tr/LC_MESSAGES/lucaschess.mo index bc8cc01..644c03b 100644 Binary files a/Locale/tr/LC_MESSAGES/lucaschess.mo and b/Locale/tr/LC_MESSAGES/lucaschess.mo differ diff --git a/Locale/vi/LC_MESSAGES/lucaschess.mo b/Locale/vi/LC_MESSAGES/lucaschess.mo index 5af8dbc..4acf8f8 100644 Binary files a/Locale/vi/LC_MESSAGES/lucaschess.mo and b/Locale/vi/LC_MESSAGES/lucaschess.mo differ diff --git a/Lucas.py b/Lucas.py index 82bd6c3..f42dda4 100644 --- a/Lucas.py +++ b/Lucas.py @@ -22,6 +22,7 @@ + import os import sip from imp import reload diff --git a/bug.log b/bug.log index 7f8e4c8..85cb165 100644 --- a/bug.log +++ b/bug.log @@ -1 +1 @@ -Version 11.05b +Version 11.06