def test_gating_king_move(self): board = LBoard(SCHESS) board.applyFen(SCHESSSTART) for move in "e2e3 e7e5 d1h5 d8e7 h5g4 b8c6 g1f3 d7d5 g4h4 e7c5 h4a4 e5e4 d2d4 c5e7 f3d2 e7b4 a4b4 c6b4".split(): board.applyMove(parseAN(board, move)) print("--------") print(board) moves = set() for move in genAllMoves(board): moves.add(toAN(board, move)) self.assertIn("e1d1e", moves) self.assertIn("e1d1h", moves) fen = board.asFen() move = parseAN(board, "e1d1e") board.applyMove(move) print("e1d1e") print(board) self.assertEqual(placement(board.asFen()), "r1b1kbnr/ppp2ppp/8/3p4/1n1Pp3/4P3/PPPN1PPP/RNBKEB1R[heH]") board.popMove() self.assertEqual(placement(board.asFen()), placement(fen))
def test_gating_castle_at_rook_wOO(self): FEN = "r2qk2r/pppbbppp/4pn2/1N1p4/1n1P4/4PN2/PPPBBPPP/R2QK2R[heHE] w KQkq - 10 8" board = LBoard(SCHESS) board.applyFen(FEN) moves = set() for move in genAllMoves(board): moves.add(toAN(board, move)) print("--------") print(board) self.assertIn("h1e1h", moves) self.assertIn("h1e1e", moves) self.assertEqual(repr(Move.Move(parseSAN(board, 'O-O/Hh1'))), 'h1e1h') self.assertEqual(repr(Move.Move(parseSAN(board, 'O-O/Eh1'))), 'h1e1e') self.assertEqual(toSAN(board, newMove(H1, E1, HAWK_GATE_AT_ROOK)), "O-O/Hh1") self.assertEqual(toSAN(board, newMove(H1, E1, ELEPHANT_GATE_AT_ROOK)), "O-O/Eh1") fen = board.asFen() move = parseAN(board, "h1e1e") board.applyMove(move) print("h1e1e") print(board) self.assertEqual(placement(board.asFen()), "r2qk2r/pppbbppp/4pn2/1N1p4/1n1P4/4PN2/PPPBBPPP/R2Q1RKE[heH]") board.popMove() self.assertEqual(placement(board.asFen()), placement(fen))
def test_default_gating_avail(self): board = LBoard(SCHESS) board.applyFen(SCHESSSTART) print(board) print(toString(board.virgin[0])) print(toString(board.virgin[1])) self.assertEqual(board.virgin[0], brank8[1]) self.assertEqual(board.virgin[1], brank8[0]) board = LBoard(SCHESS) FEN = "r1hqerk1/pp1nbppp/2pp1nb1/4p3/2PPP3/2N1B1PP/PP2NPB1/RH1QK2R/E w KQHEDA - 2 10" board.applyFen(FEN) print("-----------") print(board) move = parseSAN(board, "O-O") board.applyMove(move) castl = board.asFen().split()[2] self.assertNotIn("Q", castl) self.assertNotIn("K", castl) self.assertNotIn("E", castl) self.assertNotIn("H", castl)
def test_gating_castle_at_rook_bOO(self): board = LBoard(SCHESS) board.applyFen(SCHESSSTART) for move in "e2e3 e7e6 g1f3 c7c6 c2c3 d7d5 d2d4 g8f6 h2h3 f6e4 d1c2 b8d7 f1d3 f7f5 e1g1e d8c7 e1e2 f8e7 c2d1".split(): board.applyMove(parseAN(board, move)) moves = set() for move in genAllMoves(board): moves.add(toAN(board, move)) print("--------") print(board) self.assertIn("h8e8h", moves) self.assertIn("h8e8e", moves) fen = board.asFen() move = parseAN(board, "h8e8h") board.applyMove(move) print("h8e8e") print(board) self.assertEqual(placement(board.asFen()), "r1b2rkh/ppqnb1pp/2p1p3/3p1p2/3Pn3/2PBPN1P/PP2EPP1/RNBQ1RK1[eH]") board.popMove() self.assertEqual(placement(board.asFen()), placement(fen))
def test_gating_castle_at_rook_wOOO(self): board = LBoard(SCHESS) board.applyFen(SCHESSSTART) for move in "e2e4 d7d6 d1e2 c8d7 g1f3 g8f6 e4e5 f6d5 e2e4 d5b6 d2d3 d7c6 e4f5 c6d7 f5g5 g7g6 f3d4 h7h5 g5f4 d6e5 f4h4 f8g7 d4f3 d7g4 b1c3 f7f6 c3e4 d8d5 e4c3 d5c5 c1e3 c5d6 h2h3 g4f3 g2f3 e8g8h".split(): board.applyMove(parseAN(board, move)) moves = set() for move in genAllMoves(board): moves.add(toAN(board, move)) print("--------") print(board) self.assertIn("a1e1h", moves) self.assertIn("a1e1e", moves) fen = board.asFen() parseAN(board, "a1e1e") board.applyMove(move) print("a1e1e") print(board) self.assertEqual(placement(board.asFen()), "rn2hrk1/ppp1p1b1/1n1q1pp1/4p2p/7Q/2NPBP1P/PPP2P2/E1KR1B1R[eH]") board.popMove() self.assertEqual(placement(board.asFen()), placement(fen))
def test_pdb_database_get_bitboards(self): """Testing .pdb database get_bitboards""" db = self.load_test_pgn() board = LBoard() board.applyFen(FEN_START) for ply in range(4): bb_candidates = {} for lmove in genAllMoves(board): board.applyMove(lmove) if board.opIsChecked(): board.popMove() continue bb_candidates[board.friends[0] | board.friends[1]] = lmove board.popMove() bitboards = db.get_bitboards(ply, bb_candidates) print("==========") for row in bitboards: print(row) print("----------") self.assertEqual(len(bitboards), BITBOARD_COUNT[ply]) self.assertEqual(sum([row[1] for row in bitboards]), GAME_COUNT)
def test_parseFAN(self): """Testing parseFAN""" board = LBoard() board.applyFen("rnbqkbnr/8/8/8/8/8/8/RNBQKBNR w KQkq - 0 1") for lmove in genAllMoves(board): board.applyMove(lmove) if board.opIsChecked(): board.popMove() continue board.popMove() fan = toFAN(board, lmove) self.assertEqual(parseFAN(board, fan), lmove)
def test_moves_from_startpos(self): board = LBoard(SCHESS) board.applyFen(SCHESSSTART) moves = set() for move in genAllMoves(board): moves.add(toAN(board, move)) board.applyMove(move) board.popMove() self.assertEqual(placement(board.asFen()), placement(SCHESSSTART)) self.assertIn("b1a3e", moves) self.assertIn("b1a3h", moves) self.assertIn("b1c3e", moves) self.assertIn("b1c3h", moves) self.assertIn("g1f3e", moves) self.assertIn("g1f3h", moves) self.assertIn("g1h3e", moves) self.assertIn("g1h3h", moves)
def test_pdb_database_get_bitboards(self): """Testing .pdb database get_bitboards""" db = self.load_test_pgn() board = LBoard() board.applyFen(FEN_START) def get_bb_candidates(board): bb_candidates = {} for lmove in genAllMoves(board): board.applyMove(lmove) if board.opIsChecked(): board.popMove() continue bb_candidates[board.friends[0] | board.friends[1]] = lmove board.popMove() return bb_candidates # ply 1 bitboards = db.get_bitboards(1, get_bb_candidates(board)) print("==========") for row in bitboards: print(row) print("----------") self.assertEqual(len(bitboards), BITBOARD_COUNT[1]) self.assertEqual(sum([row[1] for row in bitboards]), GAME_COUNT) self.assertEqual(bitboards[0][5], 2712) # ply 2 board.applyMove(newMove(E2, E4)) bitboards = db.get_bitboards(2, get_bb_candidates(board)) print("==========") for row in bitboards: print(row) print("----------") self.assertEqual(len(bitboards), BITBOARD_COUNT[2]) self.assertEqual(sum([row[1] for row in bitboards]), GAME_COUNT)
def test_apply_pop(self): """Testing Atomic applyMove popMove""" board = LBoard(variant=ATOMICCHESS) board.applyFen(FEN) print(board) hist_exploding_around0 = [a[:] for a in board.hist_exploding_around] print_apply_pop = False for lmove1 in genAllMoves(board): board.applyMove(lmove1) if board.opIsChecked(): if print_apply_pop: print("popMove1 (invalid)", Move(lmove1)) board.popMove() continue hist_exploding_around1 = [ a[:] for a in board.hist_exploding_around ] for lmove2 in genAllMoves(board): board.applyMove(lmove2) if print_apply_pop: print(" applyMove2", Move(lmove2)) if board.opIsChecked(): if print_apply_pop: print(" popMove2 (invalid)", Move(lmove2)) board.popMove() continue hist_exploding_around2 = [ a[:] for a in board.hist_exploding_around ] for lmove3 in genAllMoves(board): board.applyMove(lmove3) if print_apply_pop: print(" applyMove3", Move(lmove3)) if board.opIsChecked(): if print_apply_pop: print(" popMove3 (invalid)", Move(lmove3)) board.popMove() continue board.popMove() if print_apply_pop: print(" popMove3", Move(lmove3)) self.assertEqual(hist_exploding_around2, board.hist_exploding_around) board.popMove() if print_apply_pop: print(" popMove2", Move(lmove2)) self.assertEqual(hist_exploding_around1, board.hist_exploding_around) board.popMove() if print_apply_pop: print("popMove1", Move(lmove1)) self.assertEqual(hist_exploding_around0, board.hist_exploding_around)
def test_apply_pop(self): """Testing Atomic applyMove popMove""" board = LBoard(variant=ATOMICCHESS) board.applyFen(FEN1) print(board) hist_exploding_around0 = [a[:] for a in board.hist_exploding_around] print_apply_pop = False for lmove1 in genAllMoves(board): board.applyMove(lmove1) if board.opIsChecked(): if print_apply_pop: print("popMove1 (invalid)", Move(lmove1)) board.popMove() continue hist_exploding_around1 = [a[:] for a in board.hist_exploding_around] for lmove2 in genAllMoves(board): board.applyMove(lmove2) if print_apply_pop: print(" applyMove2", Move(lmove2)) if board.opIsChecked(): if print_apply_pop: print(" popMove2 (invalid)", Move(lmove2)) board.popMove() continue hist_exploding_around2 = [a[:] for a in board.hist_exploding_around] for lmove3 in genAllMoves(board): board.applyMove(lmove3) if print_apply_pop: print(" applyMove3", Move(lmove3)) if board.opIsChecked(): if print_apply_pop: print(" popMove3 (invalid)", Move(lmove3)) board.popMove() continue board.popMove() if print_apply_pop: print(" popMove3", Move(lmove3)) self.assertEqual(hist_exploding_around2, board.hist_exploding_around) board.popMove() if print_apply_pop: print(" popMove2", Move(lmove2)) self.assertEqual(hist_exploding_around1, board.hist_exploding_around) board.popMove() if print_apply_pop: print("popMove1", Move(lmove1)) self.assertEqual(hist_exploding_around0, board.hist_exploding_around)
class PyChessCECP(PyChess): def __init__(self): PyChess.__init__(self) self.board = LBoard(NORMALCHESS) self.board.applyFen(FEN_START) self.forced = False self.analyzing = False self.thread = None self.basetime = 0 self.features = { "ping": 1, "setboard": 1, "playother": 1, "san": 1, "usermove": 1, "time": 1, "draw": 1, "sigint": 0, "sigterm": 0, "reuse": 1, "analyze": 1, "myname": "PyChess %s" % pychess.VERSION, "variants": "normal,wildcastle,nocastle,fischerandom,crazyhouse,losers,suicide,atomic," + \ "kingofthehill,3check,asean,cambodian,makruk,sittuyin", "colors": 0, "ics": 0, "name": 0, "pause": 0, # Unimplemented "nps": 0, # Unimplemented "debug": 1, "memory": 0, # Unimplemented "smp": 0, # Unimplemented "egt": "gaviota", "option": "skipPruneChance -slider 0 0 100" } python = sys.executable.split("/")[-1] python_version = "%s.%s.%s" % sys.version_info[0:3] self.print("# %s [%s %s]" % (self.features["myname"], python, python_version)) def handle_sigterm(self, *args): self.__stopSearching() sys.exit(0) def makeReady(self): signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGTERM, self.handle_sigterm) def run(self): while True: try: line = raw_input() except EOFError: line = "quit" lines = line.split() try: if not lines: continue log.debug(line, extra={"task": "xboard"}) ########## CECP commands ########## # See http://home.hccnet.nl/h.g.muller/engine-intf.html if lines[0] == "xboard": pass elif lines[0] == "protover": stringPairs = [ "=".join( [k, '"%s"' % v if isinstance(v, str) else str(v)]) for k, v in self.features.items() ] self.print("feature %s" % " ".join(stringPairs)) self.print("feature done=1") elif lines[0] in ("accepted", "rejected"): # We only really care about one case: if tuple(lines) == ("rejected", "debug"): self.debug = False elif lines[0] == "new": self.__stopSearching() self.board = LBoard(NORMALCHESS) self.board.applyFen(FEN_START) self.outOfBook = False self.forced = False self.playingAs = BLACK self.clock[:] = self.basetime, self.basetime self.searchtime = 0 self.sd = MAXPLY if self.analyzing: self.__analyze() elif lines[0] == "variant": if len(lines) > 1: if lines[1] == "fischerandom": self.board.variant = FISCHERRANDOMCHESS elif lines[1] == "crazyhouse": self.board.variant = CRAZYHOUSECHESS self.board.iniHouse() elif lines[1] == "wildcastle": self.board.variant = WILDCASTLESHUFFLECHESS elif lines[1] == "losers": self.board.variant = LOSERSCHESS elif lines[1] == "suicide": self.board.variant = SUICIDECHESS elif lines[1] == "atomic": self.board.variant = ATOMICCHESS self.board.iniAtomic() elif lines[1] == "3check": self.board.variant = THREECHECKCHESS elif lines[1] == "kingofthehill": self.board.variant = KINGOFTHEHILLCHESS self.print("setup (PNBRQKpnbrqk) 8x8+0_fairy %s" % FEN_START) elif lines[1] == "asean": self.board = LBoard(ASEANCHESS) self.board.applyFen(ASEANSTART) elif lines[1] == "makruk": self.board = LBoard(MAKRUKCHESS) self.board.applyFen(MAKRUKSTART) elif lines[1] == "cambodian": self.board = LBoard(CAMBODIANCHESS) self.board.applyFen(KAMBODIANSTART) self.print( "setup (PN.R.M....SKpn.r.m....sk) 8x8+0_makruk %s" % KAMBODIANSTART) self.print("piece K& KiN") self.print("piece M& FifD") elif lines[1] == "sittuyin": self.board = LBoard(SITTUYINCHESS) self.board.applyFen(SITTUYINSTART) self.print( "setup (PN.R.F....SKpn.r.f....sk) 8x8+6_bughouse %s" % SITTUYINSTART) self.print("piece N& Nj@3") self.print("piece S& FfWj@3") self.print("piece F& Fjb@3") self.print("piece R& R@1") self.print("piece K& Kj@3") self.print("piece P& fmWfcFj@3") elif lines[0] == "quit": self.forced = True self.__stopSearching() sys.exit(0) elif lines[0] == "random": leval.random = True elif lines[0] == "force": if not self.forced and not self.analyzing: self.forced = True self.__stopSearching() elif lines[0] == "go": self.playingAs = self.board.color self.forced = False self.__go() elif lines[0] == "playother": self.playingAs = 1 - self.board.color self.forced = False # TODO: start pondering, if possible elif lines[0] in ("black", "white"): newColor = lines[0] == "black" and BLACK or WHITE self.__stopSearching() self.playingAs = 1 - newColor if self.board.color != newColor: self.board.setColor(newColor) self.board.setEnpassant(None) if self.analyzing: self.__analyze() elif lines[0] == "level": self.movestogo = int(lines[1]) inc = int(lines[3]) minutes = lines[2].split(":") # Per protocol spec, strip off any non-numeric suffixes. for i in range(len(minutes)): minutes[i] = re.match(r'\d*', minutes[i]).group() self.basetime = int(minutes[0]) * 60 if len(minutes) > 1 and minutes[1]: self.basetime += int(minutes[1]) self.clock[:] = self.basetime, self.basetime self.increment = inc, inc elif lines[0] == "st": self.searchtime = float(lines[1]) elif lines[0] == "sd": self.sd = int(lines[1]) # Unimplemented: nps elif lines[0] == "time": self.clock[self.playingAs] = float(lines[1]) / 100. elif lines[0] == "otim": self.clock[1 - self.playingAs] = float(lines[1]) / 100. elif lines[0] == "usermove": self.__stopSearching() try: move = parseAny(self.board, lines[1]) except ParsingError as e: self.print("Error (unknown command): %s" % lines[1]) self.print(self.board) continue if not validateMove(self.board, move): self.print("Illegal move: %s" % lines[1]) self.print(self.board) continue self.board.applyMove(move) self.playingAs = self.board.color if not self.forced and not self.analyzing: self.__go() if self.analyzing: self.__analyze() elif lines[0] == "?": if not self.forced and not self.analyzing: self.__stopSearching() elif lines[0] == "ping": self.print("pong %s" % lines[1]) elif lines[0] == "draw": if self.__willingToDraw(): self.print("offer draw") elif lines[0] == "result": # We don't really care what the result is at the moment. pass elif lines[0] == "setboard": self.__stopSearching() try: self.board = LBoard(self.board.variant) fen = " ".join(lines[1:]) self.board.applyFen( fen.replace("[", "/").replace("]", "")) except SyntaxError as e: self.print("tellusererror Illegal position: %s" % str(e)) # "edit" is unimplemented. See docs. Exiting edit mode returns to analyze mode. elif lines[0] == "hint": pass # TODO: Respond "Hint: MOVE" if we have an expected reply elif lines[0] == "bk": entries = getOpenings(self.board) if entries: totalWeight = sum(entry[1] for entry in entries) for entry in entries: self.print("\t%s\t%02.2f%%" % (toSAN(self.board, entry[0]), entry[1] * 100.0 / totalWeight)) elif lines[0] == "undo": self.__stopSearching() self.board.popMove() if self.analyzing: self.__analyze() elif lines[0] == "remove": self.__stopSearching() self.board.popMove() self.board.popMove() if self.analyzing: self.__analyze() elif lines[0] in ("hard", "easy"): self.ponder = (lines[0] == "hard") elif lines[0] in ("post", "nopost"): self.post = (lines[0] == "post") elif lines[0] == "analyze": self.analyzing = True self.__analyze() elif lines[0] in ("name", "rating", "ics", "computer"): pass # We don't care. # Unimplemented: pause, resume elif lines[0] == "memory": # FIXME: this is supposed to control the *total* memory use. if lsearch.searching: self.print("Error (already searching):", line) else: limit = int(lines[1]) if limit < 1: self.print("Error (limit too low):", line) else: pass # TODO implement #lsearch.setHashSize(limit) elif lines[0] == "cores": pass # We aren't SMP-capable. elif lines[0] == "egtpath": if len(lines) >= 3 and lines[1] == "gaviota": conf.set("egtb_path", conf.get("egtb_path", lines[2])) from pychess.Utils.lutils.lsearch import enableEGTB enableEGTB() elif lines[0] == "option" and len(lines) > 1: name, eq, value = lines[1].partition("=") if value: value = int( value ) # CECP spec says option values are *always* numeric if name == "skipPruneChance": if 0 <= value <= 100: self.skipPruneChance = value / 100.0 else: self.print( "Error (argument must be an integer 0..100): %s" % line) ########## CECP analyze mode commands ########## # See http://www.gnu.org/software/xboard/engine-intf.html#11 elif lines[0] == "exit": if self.analyzing: self.__stopSearching() self.analyzing = False # Periodic updates (".") are not implemented. ########## Custom commands ########## elif lines[0] == "moves": self.print(self.board) self.print([ toSAN(self.board, move) for move in genAllMoves(self.board) ]) elif lines[0] == "captures": self.print(self.board) self.print([ toSAN(self.board, move) for move in genCaptures(self.board) ]) elif lines[0] == "evasions": self.print(self.board) self.print([ toSAN(self.board, move) for move in genCheckEvasions(self.board) ]) elif lines[0] == "benchmark": benchmark() elif lines[0] == "profile": if len(lines) > 1: import cProfile cProfile.runctx("benchmark()", locals(), globals(), lines[1]) else: self.print("Usage: profile outputfilename") elif lines[0] == "perft": root = "0" if len(lines) < 3 else lines[2] depth = "1" if len(lines) == 1 else lines[1] if root.isdigit() and depth.isdigit(): perft(self.board, int(depth), int(root)) else: self.print("Error (arguments must be integer") elif len(lines) == 1: # A GUI without usermove support might try to send a move. try: move = parseAny(self.board, line) except: self.print("Error (unknown command): %s" % line) continue if not validateMove(self.board, move): self.print("Illegal move: %s" % lines[0]) self.print(self.board) continue self.__stopSearching() self.board.applyMove(move) self.playingAs = self.board.color if not self.forced and not self.analyzing: self.__go() if self.analyzing: self.__analyze() else: self.print("Error (unknown command): %s" % line) except IndexError: self.print("Error (missing argument): %s" % line) def __stopSearching(self): lsearch.searching = False if self.thread: self.thread.join() def __go(self): def ondone(result): if not self.forced: self.board.applyMove(parseSAN(self.board, result)) self.print("move %s" % result) # TODO: start pondering, if enabled self.thread = Thread(target=PyChess._PyChess__go, name=fident(PyChess._PyChess__go), args=(self, ondone)) self.thread.daemon = True self.thread.start() def __analyze(self): self.thread = Thread(target=PyChess._PyChess__analyze, name=fident(PyChess._PyChess__analyze), args=(self, )) self.thread.daemon = True self.thread.start() def __willingToDraw(self): return self.scr <= 0 # FIXME: this misbehaves in all but the simplest use cases
def test_apply_pop(self): """Testing Crazyhouse applyMove popMove""" board = LBoard(variant=CRAZYHOUSECHESS) board.applyFen(FEN2) holding0 = (board.holding[0].copy(), board.holding[1].copy()) promoted0 = board.promoted[:] capture_promoting0 = board.capture_promoting hist_capture_promoting0 = board.hist_capture_promoting[:] print_board_promoted = False print_apply_pop = False for lmove1 in genAllMoves(board): # if lmove1 != parseAN(board, "c7b8=Q"): # continue board.applyMove(lmove1) if print_apply_pop: print("applyMove1", Move(lmove1), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) if board.opIsChecked(): if print_apply_pop: print("popMove1 (invalid)", Move(lmove1)) board.popMove() continue holding1 = (board.holding[0].copy(), board.holding[1].copy()) promoted1 = board.promoted[:] capture_promoting1 = board.capture_promoting hist_capture_promoting1 = board.hist_capture_promoting[:] for lmove2 in genAllMoves(board): # if lmove2 != parseAN(board, "e8f7"): # continue board.applyMove(lmove2) if print_apply_pop: print(" applyMove2", Move(lmove2), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) if board.opIsChecked(): if print_apply_pop: print(" popMove2 (invalid)", Move(lmove2)) board.popMove() continue holding2 = (board.holding[0].copy(), board.holding[1].copy()) promoted2 = board.promoted[:] capture_promoting2 = board.capture_promoting hist_capture_promoting2 = board.hist_capture_promoting[:] for lmove3 in genAllMoves(board): # if lmove3 != parseAN(board, "b8c8"): # continue board.applyMove(lmove3) if print_apply_pop: print(" applyMove3", Move(lmove3), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) if board.opIsChecked(): if print_apply_pop: print(" popMove3 (invalid)", Move(lmove3)) board.popMove() continue board.popMove() if print_apply_pop: print(" popMove3", Move(lmove3), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) self.assertEqual(holding2, board.holding) self.assertEqual(promoted2, board.promoted) self.assertEqual(capture_promoting2, board.capture_promoting) self.assertEqual(hist_capture_promoting2, board.hist_capture_promoting) board.popMove() if print_apply_pop: print(" popMove2", Move(lmove2), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) self.assertEqual(holding1, board.holding) self.assertEqual(promoted1, board.promoted) self.assertEqual(capture_promoting1, board.capture_promoting) self.assertEqual(hist_capture_promoting1, board.hist_capture_promoting) board.popMove() if print_apply_pop: print("popMove1", Move(lmove1), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) self.assertEqual(holding0, board.holding) self.assertEqual(promoted0, board.promoted) self.assertEqual(capture_promoting0, board.capture_promoting) self.assertEqual(hist_capture_promoting0, board.hist_capture_promoting)
class PyChessFICS(PyChess): def __init__(self, password, from_address, to_address): PyChess.__init__(self) self.ports = (23, 5000) if not password: self.username = "******" else: self.username = "******" self.owner = "Lobais" self.password = password self.from_address = "The PyChess Bot <%s>" % from_address self.to_address = "Thomas Dybdahl Ahle <%s>" % to_address # Possible start times self.minutes = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10) self.gains = (0, 5, 10, 15, 20) # Possible colors. None == random self.colors = (WHITE, BLACK, None) # The amount of random challenges, that PyChess sends with each seek self.challenges = 10 enableEGTB() self.sudos = set() self.ownerOnline = False self.waitingForPassword = None self.log = [] self.acceptedTimesettings = [] self.worker = None repeat_sleep(self.sendChallenges, 60 * 1) def __triangular(self, low, high, mode): """Triangular distribution. Continuous distribution bounded by given lower and upper limits, and having a given mode value in-between. http://en.wikipedia.org/wiki/Triangular_distribution """ u = random.random() c = (mode - low) / (high - low) if u > c: u = 1 - u c = 1 - c low, high = high, low tri = low + (high - low) * (u * c)**0.5 if tri < mode: return int(tri) elif tri > mode: return int(math.ceil(tri)) return int(round(tri)) def sendChallenges(self): if self.connection.bm.isPlaying(): return True statsbased = ((0.39197722779282, 3, 0), (0.59341408108783, 5, 0), (0.77320877377846, 1, 0), (0.8246379941394, 10, 0), (0.87388717406441, 2, 12), (0.91443760169489, 15, 0), (0.9286423058163, 4, 0), (0.93891977227793, 2, 0), (0.94674539138335, 20, 0), (0.95321476842423, 2, 2), (0.9594588808257, 5, 2), (0.96564528079889, 3, 2), (0.97173859621034, 7, 0), (0.97774906636184, 3, 1), (0.98357243654425, 5, 12), (0.98881309737017, 5, 5), (0.99319644938247, 6, 0), (0.99675879556023, 3, 12), (1, 5, 3)) #n = random.random() #for culminativeChance, minute, gain in statsbased: # if n < culminativeChance: # break culminativeChance, minute, gain = random.choice(statsbased) #type = random.choice((TYPE_LIGHTNING, TYPE_BLITZ, TYPE_STANDARD)) #if type == TYPE_LIGHTNING: # minute = self.__triangular(0,2+1,1) # mingain = not minute and 1 or 0 # maxgain = int((3-minute)*3/2) # gain = random.randint(mingain, maxgain) #elif type == TYPE_BLITZ: # minute = self.__triangular(0,14+1,5) # mingain = max(int((3-minute)*3/2+1), 0) # maxgain = int((15-minute)*3/2) # gain = random.randint(mingain, maxgain) #elif type == TYPE_STANDARD: # minute = self.__triangular(0,20+1,12) # mingain = max(int((15-minute)*3/2+1), 0) # maxgain = int((20-minute)*3/2) # gain = self.__triangular(mingain, maxgain, mingain) #color = random.choice(self.colors) self.extendlog(["Seeking %d %d" % (minute, gain)]) self.connection.glm.seek(minute, gain, True) opps = random.sample(self.connection.players.get_online_playernames(), self.challenges) self.extendlog("Challenging %s" % op for op in opps) for player in opps: self.connection.om.challenge(player, minute, gain, True) return True def makeReady(self): signal.signal(signal.SIGINT, Gtk.main_quit) PyChess.makeReady(self) self.connection = FICSMainConnection("freechess.org", self.ports, self.username, self.password) self.connection.connect("connectingMsg", self.__showConnectLog) self.connection._connect() self.connection.glm.connect("addPlayer", self.__onAddPlayer) self.connection.glm.connect("removePlayer", self.__onRemovePlayer) self.connection.cm.connect("privateMessage", self.__onTell) self.connection.alm.connect("logOut", self.__onLogOut) self.connection.bm.connect("playGameCreated", self.__onGameCreated) self.connection.bm.connect("curGameEnded", self.__onGameEnded) self.connection.bm.connect("boardUpdate", self.__onBoardUpdate) self.connection.om.connect("onChallengeAdd", self.__onChallengeAdd) self.connection.om.connect("onOfferAdd", self.__onOfferAdd) self.connection.adm.connect("onAdjournmentsList", self.__onAdjournmentsList) self.connection.em.connect("onAmbiguousMove", self.__onAmbiguousMove) self.connection.em.connect("onIllegalMove", self.__onAmbiguousMove) self.connection.adm.queryAdjournments() self.connection.lvm.setVariable("autoflag", 1) self.connection.fm.setFingerNote( 1, "PyChess is the chess engine bundled with the PyChess %s " % pychess.VERSION + "chess client. This instance is owned by %s, but acts " % self.owner + "quite autonomously.") self.connection.fm.setFingerNote( 2, "PyChess is 100% Python code and is released under the terms of " + "the GPL. The evalution function is largely equal to the one of" + "GnuChess, but it plays quite differently.") self.connection.fm.setFingerNote( 3, "PyChess runs on an elderly AMD Sempron(tm) Processor 3200+, 512 " + "MB DDR2 Ram, but is built to take use of 64bit calculating when " + "accessible, through the gpm library.") self.connection.fm.setFingerNote( 4, "PyChess uses a small 500 KB openingbook based solely on Kasparov " + "games. The engine doesn't have much endgame knowledge, but might " + "in some cases access an online endgamedatabase.") self.connection.fm.setFingerNote( 5, "PyChess will allow any pause/resume and adjourn wishes, but will " + "deny takebacks. Draw, abort and switch offers are accepted, " + "if they are found to be an advance. Flag is auto called, but " + "PyChess never resigns. We don't want you to forget your basic " + "mating skills.") def main(self): self.connection.run() self.extendlog([str(self.acceptedTimesettings)]) self.phoneHome("Session ended\n" + "\n".join(self.log)) print("Session ended") def run(self): t = Thread(target=self.main, name=fident(self.main)) t.daemon = True t.start() Gdk.threads_init() Gtk.main() #=========================================================================== # General #=========================================================================== def __showConnectLog(self, connection, message): print(message) def __onLogOut(self, autoLogoutManager): self.connection.close() #sys.exit() def __onAddPlayer(self, gameListManager, player): if player["name"] in self.sudos: self.sudos.remove(player["name"]) if player["name"] == self.owner: self.connection.cm.tellPlayer(self.owner, "Greetings") self.ownerOnline = True def __onRemovePlayer(self, gameListManager, playername): if playername == self.owner: self.ownerOnline = False def __onAdjournmentsList(self, adjournManager, adjournments): for adjournment in adjournments: if adjournment["online"]: adjournManager.challenge(adjournment["opponent"]) def __usage(self): return "|| PyChess bot help file || " +\ "# help 'Displays this help file' " +\ "# sudo <password> <command> 'Lets PyChess execute the given command' "+\ "# sendlog 'Makes PyChess send you its current log'" def __onTell(self, chatManager, name, title, isadmin, text): if self.waitingForPassword: if text.strip() == self.password or (not self.password and text == "none"): self.sudos.add(name) self.tellHome("%s gained sudo access" % name) self.connection.client.run_command(self.waitingForPassword) else: chatManager.tellPlayer(name, "Wrong password") self.tellHome("%s failed sudo access" % name) self.waitingForPassword = None return args = text.split() #if args == ["help"]: # chatManager.tellPlayer(name, self.__usage()) if args[0] == "sudo": command = " ".join(args[1:]) if name in self.sudos or name == self.owner: # Notice: This can be used to make nasty loops print(command, file=self.connection.client) else: print(repr(name), self.sudos) chatManager.tellPlayer(name, "Please send me the password") self.waitingForPassword = command elif args == ["sendlog"]: if self.log: # TODO: Consider email chatManager.tellPlayer(name, "\\n".join(self.log)) else: chatManager.tellPlayer(name, "The log is currently empty") else: if self.ownerOnline: self.tellHome("%s told me '%s'" % (name, text)) else: def onlineanswer(message): data = urlopen( "http://www.pandorabots.com/pandora/talk?botid=8d034368fe360895", urlencode({ "message": message, "botcust2": "x" }).encode("utf-8")).read().decode('utf-8') ss = "<b>DMPGirl:</b>" es = "<br>" answer = data[data.find(ss) + len(ss):data.find(es, data.find(ss))] chatManager.tellPlayer(name, answer) t = Thread(target=onlineanswer, name=fident(onlineanswer), args=(text, )) t.daemon = True t.start() #chatManager.tellPlayer(name, "Sorry, your request was nonsense.\n"+\ # "Please read my help file for more info") #=========================================================================== # Challenges and other offers #=========================================================================== def __onChallengeAdd(self, offerManager, index, match): #match = {"tp": type, "w": fname, "rt": rating, "color": color, # "r": rated, "t": mins, "i": incr} offerManager.acceptIndex(index) def __onOfferAdd(self, offerManager, offer): if offer.type in (PAUSE_OFFER, RESUME_OFFER, ADJOURN_OFFER): offerManager.accept(offer) elif offer.type in (TAKEBACK_OFFER, ): offerManager.decline(offer) elif offer.type in (DRAW_OFFER, ABORT_OFFER, SWITCH_OFFER): if self.__willingToDraw(): offerManager.accept(offer) else: offerManager.decline(offer) #=========================================================================== # Playing #=========================================================================== def __onGameCreated(self, boardManager, ficsgame): base = int(ficsgame.minutes) * 60 inc = int(ficsgame.inc) self.clock[:] = base, base self.increment[:] = inc, inc self.gameno = ficsgame.gameno self.lastPly = -1 self.acceptedTimesettings.append((base, inc)) self.tellHome( "Starting a game (%s, %s) gameno: %s" % (ficsgame.wplayer.name, ficsgame.bplayer.name, ficsgame.gameno)) if ficsgame.bplayer.name.lower() == self.connection.getUsername( ).lower(): self.playingAs = BLACK else: self.playingAs = WHITE self.board = LBoard(NORMALCHESS) # Now we wait until we recieve the board. def __go(self): if self.worker: self.worker.cancel() self.worker = GtkWorker( lambda worker: PyChess._PyChess__go(self, worker)) self.worker.connect("published", lambda w, msg: self.extendlog(msg)) self.worker.connect("done", self.__onMoveCalculated) self.worker.execute() def __willingToDraw(self): return self.scr <= 0 # FIXME: this misbehaves in all but the simplest use cases def __onGameEnded(self, boardManager, ficsgame): self.tellHome(reprResult_long[ficsgame.result] + " " + reprReason_long[ficsgame.reason]) lsearch.searching = False if self.worker: self.worker.cancel() self.worker = None def __onMoveCalculated(self, worker, sanmove): if worker.isCancelled() or not sanmove: return self.board.applyMove(parseSAN(self.board, sanmove)) self.connection.bm.sendMove(sanmove) self.extendlog(["Move sent %s" % sanmove]) def __onBoardUpdate(self, boardManager, gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms): self.extendlog( ["", "I got move %d %s for gameno %s" % (ply, lastmove, gameno)]) if self.gameno != gameno: return self.board.applyFen(fen) self.clock[:] = wms / 1000., bms / 1000. if curcol == self.playingAs: self.__go() def __onAmbiguousMove(self, errorManager, move): # This is really a fix for fics, but sometimes it is necessary if determineAlgebraicNotation(move) == SAN: self.board.popMove() move_ = parseSAN(self.board, move) lanmove = toLAN(self.board, move_) self.board.applyMove(move_) self.connection.bm.sendMove(lanmove) else: self.connection.cm.tellOpponent( "I'm sorry, I wanted to move %s, but FICS called " % move + "it 'Ambigious'. I can't find another way to express it, " + "so you can win") self.connection.bm.resign() #=========================================================================== # Utils #=========================================================================== def extendlog(self, messages): [log.info(m + "\n") for m in messages] self.log.extend(messages) del self.log[:-10] def tellHome(self, message): print(message) if self.ownerOnline: self.connection.cm.tellPlayer(self.owner, message) def phoneHome(self, message): SENDMAIL = '/usr/sbin/sendmail' SUBJECT = "Besked fra botten" p = subprocess.Popen([ SENDMAIL, '-f', email.Utils.parseaddr(self.from_address)[1], email.Utils.parseaddr(self.to_address)[1] ], stdin=subprocess.PIPE) print("MIME-Version: 1.0", file=p.stdin) print("Content-Type: text/plain; charset=UTF-8", file=p.stdin) print("Content-Disposition: inline", file=p.stdin) print("From: %s" % self.from_address, file=p.stdin) print("To: %s" % self.to_address, file=p.stdin) print("Subject: %s" % SUBJECT, file=p.stdin) print(file=p.stdin) print(message, file=p.stdin) print("Cheers", file=p.stdin) p.stdin.close() p.wait()
def test_apply_pop(self): """Testing Crazyhouse applyMove popMove""" board = LBoard(variant=CRAZYHOUSECHESS) board.applyFen(FEN1) holding0 = (board.holding[0].copy(), board.holding[1].copy()) promoted0 = board.promoted[:] capture_promoting0 = board.capture_promoting hist_capture_promoting0 = board.hist_capture_promoting[:] print_board_promoted = False print_apply_pop = False for lmove1 in genAllMoves(board): #if lmove1 != parseAN(board, "c7b8=Q"): # continue board.applyMove(lmove1) if print_apply_pop: print("applyMove1", Move(lmove1), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) if board.opIsChecked(): if print_apply_pop: print("popMove1 (invalid)", Move(lmove1)) board.popMove() continue holding1 = (board.holding[0].copy(), board.holding[1].copy()) promoted1 = board.promoted[:] capture_promoting1 = board.capture_promoting hist_capture_promoting1 = board.hist_capture_promoting[:] for lmove2 in genAllMoves(board): #if lmove2 != parseAN(board, "e8f7"): # continue board.applyMove(lmove2) if print_apply_pop: print(" applyMove2", Move(lmove2), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) if board.opIsChecked(): if print_apply_pop: print(" popMove2 (invalid)", Move(lmove2)) board.popMove() continue holding2 = (board.holding[0].copy(), board.holding[1].copy()) promoted2 = board.promoted[:] capture_promoting2 = board.capture_promoting hist_capture_promoting2 = board.hist_capture_promoting[:] for lmove3 in genAllMoves(board): #if lmove3 != parseAN(board, "b8c8"): # continue board.applyMove(lmove3) if print_apply_pop: print(" applyMove3", Move(lmove3), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) if board.opIsChecked(): if print_apply_pop: print(" popMove3 (invalid)", Move(lmove3)) board.popMove() continue board.popMove() if print_apply_pop: print(" popMove3", Move(lmove3), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) self.assertEqual(holding2, board.holding) self.assertEqual(promoted2, board.promoted) self.assertEqual(capture_promoting2, board.capture_promoting) self.assertEqual(hist_capture_promoting2, board.hist_capture_promoting) board.popMove() if print_apply_pop: print(" popMove2", Move(lmove2), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) self.assertEqual(holding1, board.holding) self.assertEqual(promoted1, board.promoted) self.assertEqual(capture_promoting1, board.capture_promoting) self.assertEqual(hist_capture_promoting1, board.hist_capture_promoting) board.popMove() if print_apply_pop: print("popMove1", Move(lmove1), board.holding, board.capture_promoting) if print_board_promoted: print(board.promoted) self.assertEqual(holding0, board.holding) self.assertEqual(promoted0, board.promoted) self.assertEqual(capture_promoting0, board.capture_promoting) self.assertEqual(hist_capture_promoting0, board.hist_capture_promoting)
def download_game(self): # Download the page if self.id is None: return None url = 'https://chess24.com/en/game/%s' % self.id page = self.download(url, userAgent=True) # Else HTTP 403 Forbidden if page is None: return None # Extract the JSON of the game lines = page.split("\n") for line in lines: line = line.strip() pos1 = line.find('.initGameSession({') pos2 = line.find('});', pos1) if -1 in [pos1, pos2]: continue # Read the game from JSON bourne = self.json_loads(line[pos1 + 17:pos2 + 1]) chessgame = self.json_field(bourne, 'chessGame') moves = self.json_field(chessgame, 'moves') if '' in [chessgame, moves]: continue # Build the header of the PGN file game = {} game['_moves'] = '' game['_url'] = url game['Event'] = self.json_field(chessgame, 'meta/Event') game['Site'] = self.json_field(chessgame, 'meta/Site') game['Date'] = self.json_field(chessgame, 'meta/Date') game['Round'] = self.json_field(chessgame, 'meta/Round') game['White'] = self.json_field(chessgame, 'meta/White/Name') game['WhiteElo'] = self.json_field(chessgame, 'meta/White/Elo') game['Black'] = self.json_field(chessgame, 'meta/Black/Name') game['BlackElo'] = self.json_field(chessgame, 'meta/Black/Elo') game['Result'] = self.json_field(chessgame, 'meta/Result') # Build the PGN board = LBoard(variant=FISCHERRANDOMCHESS) head_complete = False for move in moves: # Info from the knot kid = self.json_field(move, 'knotId') if kid == '': break kmove = self.json_field(move, 'move') # FEN initialization if kid == 0: kfen = self.json_field(move, 'fen') if kfen == '': break try: board.applyFen(kfen) except Exception: return None game['Variant'] = 'Fischerandom' game['SetUp'] = '1' game['FEN'] = kfen head_complete = True else: if not head_complete: return None # Execution of the move if kmove == '': break try: if self.use_an: kmove = parseAny(board, kmove) game['_moves'] += toSAN(board, kmove) + ' ' board.applyMove(kmove) else: game['_moves'] += kmove + ' ' except Exception: return None # Rebuild the PGN game return self.rebuild_pgn(game) return None
class PyChessFICS(PyChess): def __init__ (self, password, from_address, to_address): PyChess.__init__(self) self.ports = (23, 5000) if not password: self.username = "******" else: self.username = "******" self.owner = "Lobais" self.password = password self.from_address = "The PyChess Bot <%s>" % from_address self.to_address = "Thomas Dybdahl Ahle <%s>" % to_address # Possible start times self.minutes = (1,2,3,4,5,6,7,8,9,10) self.gains = (0,5,10,15,20) # Possible colors. None == random self.colors = (WHITE, BLACK, None) # The amount of random challenges, that PyChess sends with each seek self.challenges = 10 enableEGTB() self.sudos = set() self.ownerOnline = False self.waitingForPassword = None self.log = [] self.acceptedTimesettings = [] self.worker = None repeat_sleep(self.sendChallenges, 60*1) def __triangular(self, low, high, mode): """Triangular distribution. Continuous distribution bounded by given lower and upper limits, and having a given mode value in-between. http://en.wikipedia.org/wiki/Triangular_distribution """ u = random.random() c = (mode - low) / (high - low) if u > c: u = 1 - u c = 1 - c low, high = high, low tri = low + (high - low) * (u * c) ** 0.5 if tri < mode: return int(tri) elif tri > mode: return int(math.ceil(tri)) return int(round(tri)) def sendChallenges(self): if self.connection.bm.isPlaying(): return True statsbased = ((0.39197722779282, 3, 0), (0.59341408108783, 5, 0), (0.77320877377846, 1, 0), (0.8246379941394, 10, 0), (0.87388717406441, 2, 12), (0.91443760169489, 15, 0), (0.9286423058163, 4, 0), (0.93891977227793, 2, 0), (0.94674539138335, 20, 0), (0.95321476842423, 2, 2), (0.9594588808257, 5, 2), (0.96564528079889, 3, 2), (0.97173859621034, 7, 0), (0.97774906636184, 3, 1), (0.98357243654425, 5, 12), (0.98881309737017, 5, 5), (0.99319644938247, 6, 0), (0.99675879556023, 3, 12), (1, 5, 3)) #n = random.random() #for culminativeChance, minute, gain in statsbased: # if n < culminativeChance: # break culminativeChance, minute, gain = random.choice(statsbased) #type = random.choice((TYPE_LIGHTNING, TYPE_BLITZ, TYPE_STANDARD)) #if type == TYPE_LIGHTNING: # minute = self.__triangular(0,2+1,1) # mingain = not minute and 1 or 0 # maxgain = int((3-minute)*3/2) # gain = random.randint(mingain, maxgain) #elif type == TYPE_BLITZ: # minute = self.__triangular(0,14+1,5) # mingain = max(int((3-minute)*3/2+1), 0) # maxgain = int((15-minute)*3/2) # gain = random.randint(mingain, maxgain) #elif type == TYPE_STANDARD: # minute = self.__triangular(0,20+1,12) # mingain = max(int((15-minute)*3/2+1), 0) # maxgain = int((20-minute)*3/2) # gain = self.__triangular(mingain, maxgain, mingain) #color = random.choice(self.colors) self.extendlog(["Seeking %d %d" % (minute, gain)]) self.connection.glm.seek(minute, gain, True) opps = random.sample(self.connection.players.get_online_playernames(), self.challenges) self.extendlog("Challenging %s" % op for op in opps) for player in opps: self.connection.om.challenge(player, minute, gain, True) return True def makeReady(self): signal.signal(signal.SIGINT, Gtk.main_quit) PyChess.makeReady(self) self.connection = FICSMainConnection("freechess.org", self.ports, self.username, self.password) self.connection.connect("connectingMsg", self.__showConnectLog) self.connection._connect() self.connection.glm.connect("addPlayer", self.__onAddPlayer) self.connection.glm.connect("removePlayer", self.__onRemovePlayer) self.connection.cm.connect("privateMessage", self.__onTell) self.connection.alm.connect("logOut", self.__onLogOut) self.connection.bm.connect("playGameCreated", self.__onGameCreated) self.connection.bm.connect("curGameEnded", self.__onGameEnded) self.connection.bm.connect("boardUpdate", self.__onBoardUpdate) self.connection.om.connect("onChallengeAdd", self.__onChallengeAdd) self.connection.om.connect("onOfferAdd", self.__onOfferAdd) self.connection.adm.connect("onAdjournmentsList", self.__onAdjournmentsList) self.connection.em.connect("onAmbiguousMove", self.__onAmbiguousMove) self.connection.em.connect("onIllegalMove", self.__onAmbiguousMove) self.connection.adm.queryAdjournments() self.connection.lvm.setVariable("autoflag", 1) self.connection.fm.setFingerNote(1, "PyChess is the chess engine bundled with the PyChess %s " % pychess.VERSION + "chess client. This instance is owned by %s, but acts " % self.owner + "quite autonomously.") self.connection.fm.setFingerNote(2, "PyChess is 100% Python code and is released under the terms of " + "the GPL. The evalution function is largely equal to the one of" + "GnuChess, but it plays quite differently.") self.connection.fm.setFingerNote(3, "PyChess runs on an elderly AMD Sempron(tm) Processor 3200+, 512 " + "MB DDR2 Ram, but is built to take use of 64bit calculating when " + "accessible, through the gpm library.") self.connection.fm.setFingerNote(4, "PyChess uses a small 500 KB openingbook based solely on Kasparov " + "games. The engine doesn't have much endgame knowledge, but might " + "in some cases access an online endgamedatabase.") self.connection.fm.setFingerNote(5, "PyChess will allow any pause/resume and adjourn wishes, but will " + "deny takebacks. Draw, abort and switch offers are accepted, " + "if they are found to be an advance. Flag is auto called, but " + "PyChess never resigns. We don't want you to forget your basic " + "mating skills.") def main(self): self.connection.run() self.extendlog([str(self.acceptedTimesettings)]) self.phoneHome("Session ended\n"+"\n".join(self.log)) print("Session ended") def run(self): t = Thread(target=self.main, name=fident(self.main)) t.daemon = True t.start() Gdk.threads_init() Gtk.main() #=========================================================================== # General #=========================================================================== def __showConnectLog (self, connection, message): print(message) def __onLogOut (self, autoLogoutManager): self.connection.close() #sys.exit() def __onAddPlayer (self, gameListManager, player): if player["name"] in self.sudos: self.sudos.remove(player["name"]) if player["name"] == self.owner: self.connection.cm.tellPlayer(self.owner, "Greetings") self.ownerOnline = True def __onRemovePlayer (self, gameListManager, playername): if playername == self.owner: self.ownerOnline = False def __onAdjournmentsList (self, adjournManager, adjournments): for adjournment in adjournments: if adjournment["online"]: adjournManager.challenge(adjournment["opponent"]) def __usage (self): return "|| PyChess bot help file || " +\ "# help 'Displays this help file' " +\ "# sudo <password> <command> 'Lets PyChess execute the given command' "+\ "# sendlog 'Makes PyChess send you its current log'" def __onTell (self, chatManager, name, title, isadmin, text): if self.waitingForPassword: if text.strip() == self.password or (not self.password and text == "none"): self.sudos.add(name) self.tellHome("%s gained sudo access" % name) self.connection.client.run_command(self.waitingForPassword) else: chatManager.tellPlayer(name, "Wrong password") self.tellHome("%s failed sudo access" % name) self.waitingForPassword = None return args = text.split() #if args == ["help"]: # chatManager.tellPlayer(name, self.__usage()) if args[0] == "sudo": command = " ".join(args[1:]) if name in self.sudos or name == self.owner: # Notice: This can be used to make nasty loops print(command, file=self.connection.client) else: print(repr(name), self.sudos) chatManager.tellPlayer(name, "Please send me the password") self.waitingForPassword = command elif args == ["sendlog"]: if self.log: # TODO: Consider email chatManager.tellPlayer(name, "\\n".join(self.log)) else: chatManager.tellPlayer(name, "The log is currently empty") else: if self.ownerOnline: self.tellHome("%s told me '%s'" % (name, text)) else: def onlineanswer (message): data = urlopen("http://www.pandorabots.com/pandora/talk?botid=8d034368fe360895", urlencode({"message":message, "botcust2":"x"}).encode("utf-8")).read().decode('utf-8') ss = "<b>DMPGirl:</b>" es = "<br>" answer = data[data.find(ss)+len(ss) : data.find(es,data.find(ss))] chatManager.tellPlayer(name, answer) t = Thread(target=onlineanswer, name=fident(onlineanswer), args=(text,)) t.daemon = True t.start() #chatManager.tellPlayer(name, "Sorry, your request was nonsense.\n"+\ # "Please read my help file for more info") #=========================================================================== # Challenges and other offers #=========================================================================== def __onChallengeAdd (self, offerManager, index, match): #match = {"tp": type, "w": fname, "rt": rating, "color": color, # "r": rated, "t": mins, "i": incr} offerManager.acceptIndex(index) def __onOfferAdd (self, offerManager, offer): if offer.type in (PAUSE_OFFER, RESUME_OFFER, ADJOURN_OFFER): offerManager.accept(offer) elif offer.type in (TAKEBACK_OFFER,): offerManager.decline(offer) elif offer.type in (DRAW_OFFER, ABORT_OFFER, SWITCH_OFFER): if self.__willingToDraw(): offerManager.accept(offer) else: offerManager.decline(offer) #=========================================================================== # Playing #=========================================================================== def __onGameCreated (self, boardManager, ficsgame): base = int(ficsgame.minutes)*60 inc = int(ficsgame.inc) self.clock[:] = base, base self.increment[:] = inc, inc self.gameno = ficsgame.gameno self.lastPly = -1 self.acceptedTimesettings.append((base, inc)) self.tellHome("Starting a game (%s, %s) gameno: %s" % (ficsgame.wplayer.name, ficsgame.bplayer.name, ficsgame.gameno)) if ficsgame.bplayer.name.lower() == self.connection.getUsername().lower(): self.playingAs = BLACK else: self.playingAs = WHITE self.board = LBoard(NORMALCHESS) # Now we wait until we recieve the board. def __go (self): if self.worker: self.worker.cancel() self.worker = GtkWorker(lambda worker: PyChess._PyChess__go(self, worker)) self.worker.connect("published", lambda w, msg: self.extendlog(msg)) self.worker.connect("done", self.__onMoveCalculated) self.worker.execute() def __willingToDraw (self): return self.scr <= 0 # FIXME: this misbehaves in all but the simplest use cases def __onGameEnded (self, boardManager, ficsgame): self.tellHome(reprResult_long[ficsgame.result] + " " + reprReason_long[ficsgame.reason]) lsearch.searching = False if self.worker: self.worker.cancel() self.worker = None def __onMoveCalculated (self, worker, sanmove): if worker.isCancelled() or not sanmove: return self.board.applyMove(parseSAN(self.board,sanmove)) self.connection.bm.sendMove(sanmove) self.extendlog(["Move sent %s" % sanmove]) def __onBoardUpdate (self, boardManager, gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms): self.extendlog(["","I got move %d %s for gameno %s" % (ply, lastmove, gameno)]) if self.gameno != gameno: return self.board.applyFen(fen) self.clock[:] = wms/1000., bms/1000. if curcol == self.playingAs: self.__go() def __onAmbiguousMove (self, errorManager, move): # This is really a fix for fics, but sometimes it is necessary if determineAlgebraicNotation(move) == SAN: self.board.popMove() move_ = parseSAN(self.board, move) lanmove = toLAN(self.board, move_) self.board.applyMove(move_) self.connection.bm.sendMove(lanmove) else: self.connection.cm.tellOpponent( "I'm sorry, I wanted to move %s, but FICS called " % move + "it 'Ambigious'. I can't find another way to express it, " + "so you can win") self.connection.bm.resign() #=========================================================================== # Utils #=========================================================================== def extendlog(self, messages): [log.info(m+"\n") for m in messages] self.log.extend(messages) del self.log[:-10] def tellHome(self, message): print(message) if self.ownerOnline: self.connection.cm.tellPlayer(self.owner, message) def phoneHome(self, message): SENDMAIL = '/usr/sbin/sendmail' SUBJECT = "Besked fra botten" p = subprocess.Popen([SENDMAIL, '-f', email.Utils.parseaddr(self.from_address)[1], email.Utils.parseaddr(self.to_address)[1]], stdin=subprocess.PIPE) print("MIME-Version: 1.0", file=p.stdin) print("Content-Type: text/plain; charset=UTF-8", file=p.stdin) print("Content-Disposition: inline", file=p.stdin) print("From: %s" % self.from_address, file=p.stdin) print("To: %s" % self.to_address, file=p.stdin) print("Subject: %s" % SUBJECT, file=p.stdin) print(file=p.stdin) print(message, file=p.stdin) print("Cheers", file=p.stdin) p.stdin.close() p.wait()
def download_game(self): # Check if self.id is None: return None # Fetch the page to retrieve the encrypted user name url = 'https://chess.org/play/%s' % self.id page = self.download(url) if page is None: return None lines = page.split("\n") name = '' for line in lines: pos1 = line.find('encryptedUsername') if pos1 != -1: pos1 = line.find("'", pos1) pos2 = line.find("'", pos1 + 1) if pos2 > pos1: name = line[pos1 + 1:pos2] break if name == '': return None # Random elements to get a unique URL rndI = random.randint(1, 1000) rndS = ''.join(random.choice(string.ascii_lowercase) for i in range(8)) # Open a websocket to retrieve the chess data @asyncio.coroutine def coro(): url = 'wss://chess.org:443/play-sockjs/%d/%s/websocket' % (rndI, rndS) log.debug('Websocket connecting to %s' % url) ws = yield from websockets.connect(url, origin="https://chess.org:443") try: # Server: Hello data = yield from ws.recv() if data != 'o': # Open yield from ws.close() return None # Client: I am XXX, please open the game YYY yield from ws.send('["%s %s"]' % (name, self.id)) data = yield from ws.recv() # Server: some data if data[:1] != 'a': yield from ws.close() return None return data[3:-2] finally: yield from ws.close() data = self.async_from_sync(coro()) if data is None or data == '': return None # Parses the game chessgame = self.json_loads(data.replace('\\"', '"')) game = {} game['_url'] = url board = LBoard(variant=FISCHERRANDOMCHESS) # Player info if self.json_field(chessgame, 'creatorColor') == '1': # White=1, Black=0 creator = 'White' opponent = 'Black' else: creator = 'Black' opponent = 'White' game[creator] = self.json_field(chessgame, 'creatorId') elo = self.json_field(chessgame, 'creatorPoint') if elo not in ['', '0', 0]: game[creator + 'Elo'] = elo game[opponent] = self.json_field(chessgame, 'opponentId') elo = self.json_field(chessgame, 'opponentPoint') if elo not in ['', '0', 0]: game[opponent + 'Elo'] = elo # Game info startPos = self.json_field(chessgame, 'startPos') if startPos not in ['', 'startpos']: game['SetUp'] = '1' game['FEN'] = startPos game['Variant'] = 'Fischerandom' try: board.applyFen(startPos) except Exception: return None else: board.applyFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w AHah - 0 1') time = self.json_field(chessgame, 'timeLimitSecs') inc = self.json_field(chessgame, 'timeBonusSecs') if '' not in [time, inc]: game['TimeControl'] = '%s+%s' % (time, inc) resultTable = [(0, '*', 'Game started'), (1, '1-0', 'White checkmated'), (2, '0-1', 'Black checkmated'), (3, '1/2-1/2', 'Stalemate'), (5, '1/2-1/2', 'Insufficient material'), (8, '1/2-1/2', 'Mutual agreement'), (9, '0-1', 'White resigned'), (10, '1-0', 'Black resigned'), (13, '1-0', 'White out of time'), (14, '0-1', 'Black out of time')] # TODO List to be completed state = self.json_field(chessgame, 'state') result = '*' reason = 'Unknown reason %d' % state for rtID, rtScore, rtMsg in resultTable: if rtID == state: result = rtScore reason = rtMsg break game['Result'] = result game['_reason'] = reason # Moves game['_moves'] = '' moves = self.json_field(chessgame, 'lans') if moves == '': return None moves = moves.split(' ') for move in moves: try: if self.use_an: move = parseAny(board, move) game['_moves'] += toSAN(board, move) + ' ' board.applyMove(move) else: game['_moves'] += move + ' ' except Exception: return None # Rebuild the PGN game return self.rebuild_pgn(game)
class OpeningTreePanel(Gtk.TreeView): def __init__(self, persp): GObject.GObject.__init__(self) self.persp = persp self.filtered = False self.persp.connect("chessfile_imported", self.on_chessfile_imported) self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.liststore = Gtk.ListStore(int, str, int, int) self.modelsort = Gtk.TreeModelSort(self.liststore) self.modelsort.set_sort_column_id(2, Gtk.SortType.DESCENDING) self.set_model(self.modelsort) self.set_headers_visible(True) column = Gtk.TreeViewColumn(_("Move"), Gtk.CellRendererText(), text=1) column.set_sort_column_id(1) column.connect("clicked", self.column_clicked, 1) self.append_column(column) column = Gtk.TreeViewColumn(_("Games"), Gtk.CellRendererText(), text=2) column.set_sort_column_id(2) column.connect("clicked", self.column_clicked, 2) self.append_column(column) column = Gtk.TreeViewColumn(_("Winning %"), Gtk.CellRendererProgress(), value=3) column.set_min_width(80) column.set_sort_column_id(3) column.connect("clicked", self.column_clicked, 3) self.append_column(column) self.conid = self.connect_after("row-activated", self.row_activated) self.board = LBoard() self.board.applyFen(FEN_START) self.columns_autosize() sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) sw.add(self) self.box.pack_start(sw, True, True, 0) # buttons toolbar = Gtk.Toolbar() firstButton = Gtk.ToolButton(stock_id=Gtk.STOCK_MEDIA_PREVIOUS) toolbar.insert(firstButton, -1) prevButton = Gtk.ToolButton(stock_id=Gtk.STOCK_MEDIA_REWIND) toolbar.insert(prevButton, -1) self.filterButton = Gtk.ToggleToolButton(Gtk.STOCK_FIND) self.filterButton.set_tooltip_text( _("Filter game list by opening moves")) toolbar.insert(self.filterButton, -1) firstButton.connect("clicked", self.on_first_clicked) prevButton.connect("clicked", self.on_prev_clicked) self.filterButton.connect("clicked", self.on_filter_clicked) tool_box = Gtk.Box() tool_box.pack_start(toolbar, False, False, 0) self.box.pack_start(tool_box, False, False, 0) self.box.show_all() def on_chessfile_imported(self, persp, chessfile): self.update_tree() def on_first_clicked(self, widget): while self.board.hist_move: self.board.popMove() self.update_tree() def on_prev_clicked(self, widget): if self.board.hist_move: self.board.popMove() self.update_tree() def on_filter_clicked(self, button): self.filtered = button.get_active() if not self.filtered: self.persp.filter_panel.filterButton.set_sensitive(True) self.filtered = True while self.board.hist_move: self.board.popMove() self.update_tree() self.filtered = False else: self.persp.filter_panel.filterButton.set_sensitive(False) self.update_tree() def column_clicked(self, col, data): self.set_search_column(data) def row_activated(self, widget, path, col): lmove = self.liststore[self.modelsort.convert_path_to_child_path(path) [0]][0] self.board.applyMove(lmove) self.update_tree() def update_tree(self, load_games=True): self.persp.gamelist.ply = self.board.plyCount if load_games and self.filtered: self.persp.chessfile.set_fen_filter(self.board.asFen()) self.persp.gamelist.load_games() result = self.persp.chessfile.get_book_moves(self.board.asFen()) self.clear_tree() for move, count, white_won, blackwon, draw in result: lmove = parseAN(self.board, move) perf = 0 if not count else round( (white_won * 100. + draw * 50.) / count) self.liststore.append( [lmove, toSAN(self.board, lmove), count, perf]) def clear_tree(self): selection = self.get_selection() if self.conid is not None and selection.handler_is_connected( self.conid): with GObject.signal_handler_block(selection, self.conid): self.liststore.clear() else: self.liststore.clear()
def download_game(self): # Check if self.id is None: return None # Download url = 'https://gameknot.com/analyze-board.pl?bd=%s' % self.id page = self.download(url, userAgent=True) if page is None: return None # Header game = {} structure = [('anbd_movelist', 's', '_moves'), ('anbd_result', 'i', 'Result'), ('anbd_player_w', 's', 'White'), ('anbd_player_b', 's', 'Black'), ('anbd_rating_w', 'i', 'WhiteElo'), ('anbd_rating_b', 'i', 'BlackElo'), ('anbd_title', 's', 'Event'), ('anbd_timestamp', 's', 'Date'), ('export_web_input_result_text', 's', '_reason')] for var, type, tag in structure: game[tag] = '' game['_url'] = url lines = page.split(';') for line in lines: for var, type, tag in structure: if var not in line.lower(): continue if type == 's': pos1 = line.find("'") pos2 = line.find("'", pos1 + 1) if pos2 > pos1: game[tag] = line[pos1 + 1:pos2] elif type == 'i': pos1 = line.find("=") if pos1 != -1: txt = line[pos1 + 1:].strip() if txt not in ['', '0']: game[tag] = txt else: return None if game['Result'] == '1': game['Result'] = '1-0' elif game['Result'] == '2': game['Result'] = '1/2-1/2' elif game['Result'] == '3': game['Result'] = '0-1' else: game['Result'] = '*' # Body board = LBoard() board.applyFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1') moves = game['_moves'].split('-') game['_moves'] = '' for move in moves: if move == '': break try: if self.use_an: kmove = parseAny(board, move) game['_moves'] += toSAN(board, kmove) + ' ' board.applyMove(kmove) else: game['_moves'] += move + ' ' except Exception: return None # Rebuild the PGN game return self.rebuild_pgn(game)
class PyChessCECP(PyChess): def __init__ (self): PyChess.__init__(self) self.board = LBoard(NORMALCHESS) self.board.applyFen(FEN_START) self.forced = False self.analyzing = False self.thread = None self.basetime = 0 self.features = { "ping": 1, "setboard": 1, "playother": 1, "san": 1, "usermove": 1, "time": 1, "draw": 1, "sigint": 0, "sigterm": 0, "reuse": 1, "analyze": 1, "myname": "PyChess %s" % pychess.VERSION, "variants": "normal,wildcastle,nocastle,fischerandom,crazyhouse,losers,suicide,atomic", "colors": 0, "ics": 0, "name": 0, "pause": 0, # Unimplemented "nps": 0, # Unimplemented "debug": 1, "memory": 1, "smp": 0, # Unimplemented # "egt": "gaviota", # TODO: re-enable "option": "skipPruneChance -slider 0 0 100" } def handle_sigterm(self, *args): self.__stopSearching() sys.exit(0) def makeReady(self): PyChess.makeReady(self) signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGTERM, self.handle_sigterm) def run (self): while True: try: line = raw_input() except EOFError: line = "quit" lines = line.split() if 1: #try: if not lines: continue ########## CECP commands ########## # See http://www.gnu.org/software/xboard/engine-intf.html#8 elif lines[0] == "xboard": pass elif lines[0] == "protover": stringPairs = ["=".join([k, '"%s"' % v if type(v) is str else str(v)]) for k,v in self.features.iteritems()] print "feature %s" % " ".join(stringPairs) print "feature done=1" elif lines[0] in ("accepted", "rejected"): # We only really care about one case: if tuple(lines) == ("rejected", "debug"): self.debug = False elif lines[0] == "new": self.__stopSearching() self.board = LBoard(NORMALCHESS) self.board.applyFen(FEN_START) self.forced = False self.playingAs = BLACK self.clock[:] = self.basetime, self.basetime self.searchtime = 0 self.sd = MAXPLY if self.analyzing: self.__analyze() elif lines[0] == "variant": if len(lines) > 1: if lines[1] == "fischerandom": self.board.variant = FISCHERRANDOMCHESS elif lines[1] == "crazyhouse": self.board.variant = CRAZYHOUSECHESS self.board.iniHouse() elif lines[1] == "wildcastle": self.board.variant = WILDCASTLESHUFFLECHESS elif lines[1] == "losers": self.board.variant = LOSERSCHESS elif lines[1] == "suicide": self.board.variant = SUICIDECHESS elif lines[1] == "atomic": self.board.variant = ATOMICCHESS self.board.iniAtomic() elif lines[0] == "quit": self.forced = True self.__stopSearching() sys.exit(0) elif lines[0] == "random": leval.random = True elif lines[0] == "force": if not self.forced and not self.analyzing: self.forced = True self.__stopSearching() elif lines[0] == "go": self.playingAs = self.board.color self.forced = False self.__go() elif lines[0] == "playother": self.playingAs = 1-self.board.color self.forced = False # TODO: start pondering, if possible elif lines[0] in ("black", "white"): newColor = lines[0] == "black" and BLACK or WHITE self.__stopSearching() self.playingAs = 1-newColor if self.board.color != newColor: self.board.setColor(newColor) self.board.setEnpassant(None) if self.analyzing: self.__analyze() elif lines[0] == "level": self.movestogo = int(lines[1]) inc = int(lines[3]) minutes = lines[2].split(":") # Per protocol spec, strip off any non-numeric suffixes. for i in xrange(len(minutes)): minutes[i] = re.match(r'\d*', minutes[i]).group() self.basetime = int(minutes[0])*60 if len(minutes) > 1 and minutes[1]: self.basetime += int(minutes[1]) self.clock[:] = self.basetime, self.basetime self.increment = inc, inc elif lines[0] == "st": self.searchtime = float(lines[1]) elif lines[0] == "sd": self.sd = int(lines[1]) # Unimplemented: nps elif lines[0] == "time": self.clock[self.playingAs] = float(lines[1])/100. elif lines[0] == "otim": self.clock[1-self.playingAs] = float(lines[1])/100. elif lines[0] == "usermove": self.__stopSearching() try: move = parseAny (self.board, lines[1]) except ParsingError, e: print "Error (unknown command):", lines[1] print self.board continue if not validateMove(self.board, move): print "Illegal move", lines[1] print self.board continue self.board.applyMove(move) self.playingAs = self.board.color if not self.forced and not self.analyzing: self.__go() if self.analyzing: self.__analyze() elif lines[0] == "?": if not self.forced and not self.analyzing: self.__stopSearching() elif lines[0] == "ping": print "pong", lines[1] elif lines[0] == "draw": if self.__willingToDraw(): print "offer draw" elif lines[0] == "result": # We don't really care what the result is at the moment. pass elif lines[0] == "setboard": self.__stopSearching() try: self.board = LBoard(self.board.variant) fen = " ".join(lines[1:]) self.board.applyFen(fen.replace("[", "/").replace("]", "")) except SyntaxError as e: print "tellusererror Illegal position:", str(e) if self.analyzing: self.__analyze() # "edit" is unimplemented. See docs. Exiting edit mode returns to analyze mode. elif lines[0] == "hint": pass # TODO: Respond "Hint: MOVE" if we have an expected reply elif lines[0] == "bk": entries = getOpenings(self.board) if entries: totalWeight = sum(entry[1] for entry in entries) for entry in entries: print "\t%s\t%02.2f%%" % (toSAN(self.board, entry[0]), entry[1] * 100.0 / totalWeight) elif lines[0] == "undo": self.__stopSearching() self.board.popMove() if self.analyzing: self.__analyze() elif lines[0] == "remove": self.__stopSearching() self.board.popMove() self.board.popMove() if self.analyzing: self.__analyze() elif lines[0] in ("hard", "easy"): self.ponder = (lines[0] == "hard") elif lines[0] in ("post", "nopost"): self.post = (lines[0] == "post") elif lines[0] == "analyze": self.analyzing = True self.__analyze() elif lines[0] in ("name", "rating", "ics", "computer"): pass # We don't care. # Unimplemented: pause, resume elif lines[0] == "memory": # FIXME: this is supposed to control the *total* memory use. if lsearch.searching: print "Error (already searching):", line else: limit = int(lines[1]) if limit < 1: print "Error (limit too low):", line else: pass # TODO implement #lsearch.setHashSize(limit) elif lines[0] == "cores": pass # We aren't SMP-capable. elif lines[0] == "egtpath": # TODO: Accept "egtpath TYPE PATH" commands, at least for Gaviota EGTBs pass elif lines[0] == "option" and len(lines) > 1: name, eq, value = lines[1].partition("=") if value: value = int(value) # CECP spec says option values are *always* numeric if name == "skipPruneChance": if 0 <= value <= 100: self.skipPruneChance = value / 100.0 else: print "Error (argument must be an integer 0..100):", line ########## CECP analyze mode commands ########## # See http://www.gnu.org/software/xboard/engine-intf.html#11 elif lines[0] == "exit": if self.analyzing: self.__stopSearching() self.analyzing = False # Periodic updates (".") are not implemented. ########## Custom commands ########## elif lines[0] == "egtb": enableEGTB() elif lines[0] == "benchmark": benchmark() elif lines[0] == "profile": if len(lines) > 1: import cProfile cProfile.runctx("benchmark()", locals(), globals(), lines[1]) else: print "Usage: profile outputfilename" elif len(lines) == 1: # A GUI without usermove support might try to send a move. try: move = parseAny (self.board, line) except: print "Error (unknown command):", line continue if not validateMove(self.board, move): print "Illegal move", lines[0] print self.board continue self.__stopSearching() self.board.applyMove(move) self.playingAs = self.board.color if not self.forced and not self.analyzing: self.__go() if self.analyzing: self.__analyze() else: print "Error (unknown command):", line
class PyChessCECP(PyChess): def __init__(self): PyChess.__init__(self) self.board = LBoard(NORMALCHESS) self.board.applyFen(FEN_START) self.forced = False self.analyzing = False self.thread = None self.basetime = 0 self.features = { "ping": 1, "setboard": 1, "playother": 1, "san": 1, "usermove": 1, "time": 1, "draw": 1, "sigint": 0, "sigterm": 0, "reuse": 1, "analyze": 1, "myname": "PyChess %s" % pychess.VERSION, "variants": "normal,wildcastle,nocastle,fischerandom,crazyhouse," + "losers,suicide,giveaway,horde,atomic," + "kingofthehill,3check,asean,cambodian,makruk,sittuyin", "colors": 0, "ics": 0, "name": 0, "pause": 0, # Unimplemented "nps": 0, # Unimplemented "debug": 1, "memory": 0, # Unimplemented "smp": 0, # Unimplemented "egt": "gaviota", "option": "skipPruneChance -slider 0 0 100" } python = sys.executable.split("/")[-1] python_version = "%s.%s.%s" % sys.version_info[0:3] self.print("# %s [%s %s]" % (self.features["myname"], python, python_version)) def handle_sigterm(self, *args): self.__stopSearching() sys.exit(0) def makeReady(self): signal.signal(signal.SIGINT, signal.SIG_IGN) signal.signal(signal.SIGTERM, self.handle_sigterm) def run(self): while True: try: line = get_input() except EOFError: line = "quit" lines = line.split() try: if not lines: continue log.debug(line, extra={"task": "xboard"}) # CECP commands # See http://home.hccnet.nl/h.g.muller/engine-intf.html if lines[0] == "xboard": pass elif lines[0] == "protover": stringPairs = ["=".join([k, '"%s"' % v if isinstance( v, str) else str(v)]) for k, v in self.features.items()] self.print("feature %s" % " ".join(stringPairs)) self.print("feature done=1") elif lines[0] in ("accepted", "rejected"): # We only really care about one case: if tuple(lines) == ("rejected", "debug"): self.debug = False elif lines[0] == "new": self.__stopSearching() self.board = LBoard(NORMALCHESS) self.board.applyFen(FEN_START) self.outOfBook = False self.forced = False self.playingAs = BLACK self.clock[:] = self.basetime, self.basetime self.searchtime = 0 self.sd = MAXPLY if self.analyzing: self.__analyze() elif lines[0] == "variant": if len(lines) > 1: if lines[1] == "fischerandom": self.board.variant = FISCHERRANDOMCHESS elif lines[1] == "crazyhouse": self.board.variant = CRAZYHOUSECHESS self.board.iniHouse() elif lines[1] == "wildcastle": self.board.variant = WILDCASTLESHUFFLECHESS elif lines[1] == "losers": self.board.variant = LOSERSCHESS elif lines[1] == "suicide": self.board.variant = SUICIDECHESS elif lines[1] == "giveaway": self.board.variant = GIVEAWAYCHESS elif lines[1] == "atomic": self.board.variant = ATOMICCHESS self.board.iniAtomic() elif lines[1] == "3check": self.board.variant = THREECHECKCHESS elif lines[1] == "kingofthehill": self.board.variant = KINGOFTHEHILLCHESS self.print("setup (PNBRQKpnbrqk) 8x8+0_fairy %s" % FEN_START) elif lines[1] == "horde": self.board = LBoard(HORDECHESS) self.board.applyFen(HORDESTART) elif lines[1] == "asean": self.board = LBoard(ASEANCHESS) self.board.applyFen(ASEANSTART) elif lines[1] == "makruk": self.board = LBoard(MAKRUKCHESS) self.board.applyFen(MAKRUKSTART) elif lines[1] == "cambodian": self.board = LBoard(CAMBODIANCHESS) self.board.applyFen(KAMBODIANSTART) self.print( "setup (PN.R.M....SKpn.r.m....sk) 8x8+0_makruk %s" % KAMBODIANSTART) self.print("piece K& KiN") self.print("piece M& FifD") elif lines[1] == "sittuyin": self.board = LBoard(SITTUYINCHESS) self.board.applyFen(SITTUYINSTART) self.print( "setup (PN.R.F....SKpn.r.f....sk) 8x8+6_bughouse %s" % SITTUYINSTART) self.print("piece N& Nj@3") self.print("piece S& FfWj@3") self.print("piece F& Fjb@3") self.print("piece R& R@1") self.print("piece K& Kj@3") self.print("piece P& fmWfcFj@3") elif lines[0] == "quit": self.forced = True self.__stopSearching() sys.exit(0) elif lines[0] == "random": leval.random = True elif lines[0] == "force": if not self.forced and not self.analyzing: self.forced = True self.__stopSearching() elif lines[0] == "go": self.playingAs = self.board.color self.forced = False self.__go() elif lines[0] == "playother": self.playingAs = 1 - self.board.color self.forced = False # TODO: start pondering, if possible elif lines[0] in ("black", "white"): newColor = lines[0] == "black" and BLACK or WHITE self.__stopSearching() self.playingAs = 1 - newColor if self.board.color != newColor: self.board.setColor(newColor) self.board.setEnpassant(None) if self.analyzing: self.__analyze() elif lines[0] == "level": self.movestogo = int(lines[1]) inc = int(lines[3]) minutes = lines[2].split(":") # Per protocol spec, strip off any non-numeric suffixes. for i in range(len(minutes)): minutes[i] = re.match(r'\d*', minutes[i]).group() self.basetime = int(minutes[0]) * 60 if len(minutes) > 1 and minutes[1]: self.basetime += int(minutes[1]) self.clock[:] = self.basetime, self.basetime self.increment = inc, inc elif lines[0] == "st": self.searchtime = float(lines[1]) elif lines[0] == "sd": self.sd = int(lines[1]) # Unimplemented: nps elif lines[0] == "time": self.clock[self.playingAs] = float(lines[1]) / 100. elif lines[0] == "otim": self.clock[1 - self.playingAs] = float(lines[1]) / 100. elif lines[0] == "usermove": self.__stopSearching() try: move = parseAny(self.board, lines[1]) except ParsingError as err: self.print("Error (unknown command): %s" % lines[1]) self.print(self.board.prepr(ascii=ASCII)) continue if not validateMove(self.board, move): self.print("Illegal move: %s" % lines[1]) self.print(self.board.prepr(ascii=ASCII)) continue self.board.applyMove(move) self.playingAs = self.board.color if not self.forced and not self.analyzing: self.__go() if self.analyzing: self.__analyze() elif lines[0] == "?": if not self.forced and not self.analyzing: self.__stopSearching() elif lines[0] == "ping": self.print("pong %s" % lines[1]) elif lines[0] == "draw": if self.__willingToDraw(): self.print("offer draw") elif lines[0] == "result": # We don't really care what the result is at the moment. pass elif lines[0] == "setboard": self.__stopSearching() try: self.board = LBoard(self.board.variant) fen = " ".join(lines[1:]) self.board.applyFen(fen.replace("[", "/").replace("]", "")) except SyntaxError as err: self.print("tellusererror Illegal position: %s" % str(err)) # "edit" is unimplemented. See docs. Exiting edit mode returns to analyze mode. elif lines[0] == "hint": pass # TODO: Respond "Hint: MOVE" if we have an expected reply elif lines[0] == "bk": entries = getOpenings(self.board) if entries: totalWeight = sum(entry[1] for entry in entries) for entry in entries: self.print("\t%s\t%02.2f%%" % (toSAN(self.board, entry[0]), entry[1] * 100.0 / totalWeight)) elif lines[0] == "undo": self.__stopSearching() self.board.popMove() if self.analyzing: self.__analyze() elif lines[0] == "remove": self.__stopSearching() self.board.popMove() self.board.popMove() if self.analyzing: self.__analyze() elif lines[0] in ("hard", "easy"): self.ponder = (lines[0] == "hard") elif lines[0] in ("post", "nopost"): self.post = (lines[0] == "post") elif lines[0] == "analyze": self.analyzing = True self.__analyze() elif lines[0] in ("name", "rating", "ics", "computer"): pass # We don't care. # Unimplemented: pause, resume elif lines[0] == "memory": # FIXME: this is supposed to control the *total* memory use. if lsearch.searching: self.print("Error (already searching):", line) else: limit = int(lines[1]) if limit < 1: self.print("Error (limit too low):", line) else: pass # TODO implement # lsearch.setHashSize(limit) elif lines[0] == "cores": pass # We aren't SMP-capable. elif lines[0] == "egtpath": if len(lines) >= 3 and lines[1] == "gaviota": conf.set("egtb_path", conf.get("egtb_path", lines[2])) from pychess.Utils.lutils.lsearch import enableEGTB enableEGTB() elif lines[0] == "option" and len(lines) > 1: name, eq, value = lines[1].partition("=") if value: value = int( value ) # CECP spec says option values are *always* numeric if name == "skipPruneChance": if 0 <= value <= 100: self.skipPruneChance = value / 100.0 else: self.print( "Error (argument must be an integer 0..100): %s" % line) # CECP analyze mode commands # See http://www.gnu.org/software/xboard/engine-intf.html#11 elif lines[0] == "exit": if self.analyzing: self.__stopSearching() self.analyzing = False # Periodic updates (".") are not implemented. # Custom commands elif lines[0] == "moves": self.print(self.board.prepr(ascii=ASCII)) self.print([toSAN(self.board, move) for move in genAllMoves(self.board)]) elif lines[0] == "captures": self.print(self.board.prepr(ascii=ASCII)) self.print([toSAN(self.board, move) for move in genCaptures(self.board)]) elif lines[0] == "evasions": self.print(self.board.prepr(ascii=ASCII)) self.print([toSAN(self.board, move) for move in genCheckEvasions(self.board)]) elif lines[0] == "benchmark": if len(lines) > 1: benchmark(int(lines[1])) else: benchmark() elif lines[0] == "profile": if len(lines) > 1: import cProfile cProfile.runctx("benchmark()", locals(), globals(), lines[1]) else: self.print("Usage: profile outputfilename") elif lines[0] == "perft": root = "0" if len(lines) < 3 else lines[2] depth = "1" if len(lines) == 1 else lines[1] if root.isdigit() and depth.isdigit(): perft(self.board, int(depth), int(root)) else: self.print("Error (arguments must be integer") elif lines[0] == "stop_unittest": break elif len(lines) == 1: # A GUI without usermove support might try to send a move. try: move = parseAny(self.board, line) except ParsingError: self.print("Error (unknown command): %s" % line) continue if not validateMove(self.board, move): self.print("Illegal move: %s" % lines[0]) self.print(self.board.prepr(ascii=ASCII)) continue self.__stopSearching() self.board.applyMove(move) self.playingAs = self.board.color if not self.forced and not self.analyzing: self.__go() if self.analyzing: self.__analyze() else: self.print("Error (unknown command): %s" % line) except IndexError: self.print("Error (missing argument): %s" % line) def __stopSearching(self): lsearch.searching = False if self.thread: self.thread.join() def __go(self): def ondone(result): if not self.forced: self.board.applyMove(parseSAN(self.board, result)) self.print("move %s" % result) # TODO: start pondering, if enabled self.thread = Thread(target=PyChess._PyChess__go, name=fident(PyChess._PyChess__go), args=(self, ondone)) self.thread.daemon = True self.thread.start() def __analyze(self): self.thread = Thread(target=PyChess._PyChess__analyze, name=fident(PyChess._PyChess__analyze), args=(self, )) self.thread.daemon = True self.thread.start() def __willingToDraw(self): return self.scr <= 0 # FIXME: this misbehaves in all but the simplest use cases
class OpeningTreePanel(Gtk.TreeView): def __init__(self, gamelist): GObject.GObject.__init__(self) self.gamelist = gamelist self.filtered = False self.persp = perspective_manager.get_perspective("database") self.persp.connect("chessfile_opened", self.on_chessfile_opened) self.persp.connect("chessfile_switched", self.on_chessfile_switched) self.persp.connect("chessfile_imported", self.on_chessfile_imported) self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.liststore = Gtk.ListStore(int, str, int, int, int) self.modelsort = Gtk.TreeModelSort(self.liststore) self.modelsort.set_sort_column_id(2, Gtk.SortType.DESCENDING) self.set_model(self.modelsort) self.set_headers_visible(True) column = Gtk.TreeViewColumn(_("Move"), Gtk.CellRendererText(), text=1) column.set_sort_column_id(1) column.connect("clicked", self.column_clicked, 1) self.append_column(column) column = Gtk.TreeViewColumn(_("Games"), Gtk.CellRendererText(), text=2) column.set_sort_column_id(2) column.connect("clicked", self.column_clicked, 2) self.append_column(column) column = Gtk.TreeViewColumn(_("Result"), Gtk.CellRendererProgress(), value=3) column.set_min_width(80) column.set_sort_column_id(3) column.connect("clicked", self.column_clicked, 3) self.append_column(column) column = Gtk.TreeViewColumn(_("Elo Avg"), Gtk.CellRendererText(), text=4) column.set_sort_column_id(4) column.connect("clicked", self.column_clicked, 4) self.append_column(column) self.conid = self.connect_after("row-activated", self.row_activated) self.board = LBoard() self.board.applyFen(FEN_START) self.update_tree() self.columns_autosize() sw = Gtk.ScrolledWindow() sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN) sw.add(self) self.box.pack_start(sw, True, True, 0) # buttons toolbar = Gtk.Toolbar() firstButton = Gtk.ToolButton(Gtk.STOCK_MEDIA_PREVIOUS) toolbar.insert(firstButton, -1) prevButton = Gtk.ToolButton(Gtk.STOCK_MEDIA_REWIND) toolbar.insert(prevButton, -1) filterButton = Gtk.ToggleToolButton(Gtk.STOCK_FIND) toolbar.insert(filterButton, -1) firstButton.connect("clicked", self.on_first_clicked) prevButton.connect("clicked", self.on_prev_clicked) filterButton.connect("clicked", self.on_filter_clicked) tool_box = Gtk.Box() tool_box.pack_start(toolbar, False, False, 0) self.box.pack_start(tool_box, False, False, 0) self.box.show_all() def on_chessfile_opened(self, persp, chessfile): self.update_tree(load_games=False) def on_chessfile_switched(self, switcher, chessfile): if isinstance(chessfile, Database): self.update_tree() else: self.clear_tree() def on_chessfile_imported(self, persp, chessfile): self.update_tree() def on_first_clicked(self, widget): while self.board.hist_move: self.board.popMove() self.update_tree() def on_prev_clicked(self, widget): if self.board.hist_move: self.board.popMove() self.update_tree() def on_filter_clicked(self, button): self.filtered = button.get_active() def column_clicked(self, col, data): self.set_search_column(data) def row_activated(self, widget, path, col): lmove = self.liststore[self.modelsort.convert_path_to_child_path(path) [0]][0] self.board.applyMove(lmove) self.update_tree() self.gamelist.update_counter(with_select=True) def update_tree(self, load_games=True): bb = self.board.friends[0] | self.board.friends[1] self.gamelist.ply = self.board.plyCount self.gamelist.chessfile.build_where_bitboards(self.board.plyCount, bb) self.gamelist.offset = 0 self.gamelist.chessfile.build_query() if load_games and self.filtered: self.gamelist.load_games() bb_candidates = {} for lmove in genAllMoves(self.board): self.board.applyMove(lmove) if self.board.opIsChecked(): self.board.popMove() continue bb_candidates[self.board.friends[0] | self.board.friends[1]] = lmove self.board.popMove() result = [] # print("get_bitboards() for %s bb_candidates" % len(bb_candidates)) bb_list = self.gamelist.chessfile.get_bitboards( self.board.plyCount + 1, bb_candidates) for bb, count, white_won, blackwon, draw, white_elo_avg, black_elo_avg in bb_list: try: result.append((bb_candidates[bb], count, white_won, blackwon, draw, white_elo_avg, black_elo_avg)) # print("OK ", bb, count, white_won, blackwon, draw, white_elo_avg, black_elo_avg) except KeyError: # print("KeyError", bb, count, white_won, blackwon, draw, white_elo_avg, black_elo_avg) pass self.clear_tree() for lmove, count, white_won, blackwon, draw, white_elo_avg, black_elo_avg in result: perf = 0 if not count else round( (white_won * 100. + draw * 50.) / count) elo_avg = white_elo_avg if self.board.color == WHITE else black_elo_avg self.liststore.append( [lmove, toSAN(self.board, lmove), count, perf, elo_avg]) def clear_tree(self): selection = self.get_selection() if self.conid is not None and selection.handler_is_connected( self.conid): with GObject.signal_handler_block(selection, self.conid): self.liststore.clear() else: self.liststore.clear()
class PyChessCECP(PyChess): def __init__ (self): PyChess.__init__(self) self.board = LBoard(NORMALCHESS) self.board.applyFen(FEN_START) self.forced = False self.analyzing = False self.worker = None self.features = { "setboard": 1, "analyze": 1, "san": 1, "usermove": 1, "reuse": 0, "draw": 1, "sigterm": 1, "colors": 1, "variants": "normal,nocastle,fischerandom", "myname": "PyChess %s" % pychess.VERSION } def makeReady(self): PyChess.makeReady(self) def run (self): while True: line = raw_input() if not line.strip(): continue lines = line.split() if lines[0] == "protover": stringPairs = ["=".join([k,repr(v)]) for k,v in self.features.iteritems()] print "feature %s done=1" % " ".join(stringPairs) elif lines[0] == "usermove": self.__stopSearching() move = parseAny (self.board, lines[1]) if not validateMove(self.board, move): print "Illegal move", lines[1] continue self.board.applyMove(move) self.playingAs = self.board.color if not self.forced and not self.analyzing: self.__go() if self.analyzing: self.__analyze() elif lines[0] == "sd": self.sd = int(lines[1]) self.skipPruneChance = max(0, (5-self.sd)*0.02) if self.sd >= 5: print "If the game has no timesettings, you probably don't want\n"+\ "to set a search depth much greater than 4" elif lines[0] == "egtb": # This is a crafty command interpreted a bit loose self.useegtb = True elif lines[0] == "level": moves = int(lines[1]) self.increment = int(lines[3]) minutes = lines[2].split(":") self.mytime = int(minutes[0])*60 if len(minutes) > 1: self.mytime += int(minutes[1]) print "Playing %d moves in %d seconds + %d increment" % \ (moves, self.mytime, self.increment) elif lines[0] == "time": self.mytime = int(lines[1])/100. #elif lines[0] == "otim": # self.optime = int(lines[1]) elif lines[0] == "quit": sys.exit() elif lines[0] == "result": # We don't really care what the result is at the moment. sys.exit() elif lines[0] == "force": if not self.forced and not self.analyzing: self.forced = True self.__stopSearching() elif lines[0] == "go": self.playingAs = self.board.color self.forced = False self.__go() elif lines[0] == "undo": self.__stopSearching() self.board.popMove() if self.analyzing: self.__analyze() elif lines[0] == "?": self.__stopSearching() elif lines[0] in ("black", "white"): newColor = lines[0] == "black" and BLACK or WHITE if self.playingAs != newColor: self.__stopSearching() self.playingAs = newColor # It is dangerous to use the same table, when we change color #lsearch.table.clear() self.board.setColor(newColor) # Concider the case: # * White moves a pawn and creates a enpassant cord # * Spy analyzer is not set back to white to analyze the # position. An attackable enpassant cord now no longer makes # sense. # * Notice though, that when the color is shifted back to black # to make the actual move - and it is an enpassant move - the # cord won't be set in the lboard. self.board.setEnpassant(None) if self.analyzing: self.__analyze() elif lines[0] == "analyze": self.playingAs = self.board.color self.analyzing = True self.__analyze() elif lines[0] == "draw": if self.scr <= 0: print "offer draw" elif lines[0] == "random": leval.random = True elif lines[0] == "variant": if lines[1] == "fischerandom": self.board.variant = FISCHERRANDOMCHESS elif lines[0] == "setboard": self.__stopSearching() self.board.applyFen(" ".join(lines[1:])) if self.analyzing: self.__analyze() elif lines[0] in ("xboard", "otim", "hard", "easy", "nopost", "post", "accepted", "rejected"): pass else: print "Warning (unknown command):", line def __stopSearching(self): lsearch.searching = False if self.worker: self.worker.cancel() glock.acquire() self.worker.get() glock.release() self.worker = None def __go (self): self.worker = GtkWorker(lambda worker: PyChess._PyChess__go(self, worker)) def process (worker, messages): print "\n".join(messages) self.worker.connect("published", process) def ondone (worker, result): if not result: return self.board.applyMove(parseSAN(self.board,result)) print "move %s" % result self.worker.connect("done", ondone) self.worker.execute() def __analyze (self): self.worker = GtkWorker(lambda worker: PyChess._PyChess__analyze(self, worker)) def process (worker, messages): print "\n".join(messages) self.worker.connect("published", process) self.worker.execute()