def walk(model, node, path): if node.prev is None: # initial game board board = model.variant(setup=node.asFen(), lboard=node) else: move = Move(node.lastMove) try: board = node.prev.pieceBoard.move(move, lboard=node) except Exception: raise LoadingError( _("Invalid move."), "%s%s" % (move_count(node, black_periods=True), move)) if node.next is None: model.variations.append(path + [board]) else: walk(model, node.next, path + [board]) for child in node.children: if isinstance(child, list): if len(child) > 1: # non empty variation, go walk walk(model, child[1], list(path)) else: if not self.has_emt: self.has_emt = child.find("%emt") >= 0 if not self.has_eval: self.has_eval = child.find("%eval") >= 0
def _create_board(model, node): if node.prev is None: # initial game board board = model.variant(setup=node.asFen(), lboard=node) else: move = Move(node.lastMove) try: board = node.prev.pieceBoard.move(move, lboard=node) except Exception: raise LoadingError( _("Invalid move."), "%s%s" % (move_count(node, black_periods=True), move)) return board
def parse_movetext(self, string, board, position, variation=False): """Recursive parses a movelist part of one game. Arguments: srting - str (movelist) board - lboard (initial position) position - int (maximum ply to parse) variation- boolean (True if the string is a variation)""" boards = [] boards_append = boards.append last_board = board if variation: # this board used only to hold initial variation comments boards_append(LBoard(board.variant)) else: # initial game board boards_append(board) # status = None parenthesis = 0 v_string = "" v_last_board = None for m in re.finditer(pattern, string): group, text = m.lastindex, m.group(m.lastindex) if parenthesis > 0: v_string += ' ' + text if group == VARIATION_END: parenthesis -= 1 if parenthesis == 0: if last_board.prev is None: errstr1 = _("Error parsing %(mstr)s") % { "mstr": string } self.error = LoadingError(errstr1, "") return boards # , status v_last_board.children.append( self.parse_movetext(v_string[:-1], last_board.prev, position, variation=True)) v_string = "" continue elif group == VARIATION_START: parenthesis += 1 if parenthesis == 1: v_last_board = last_board if parenthesis == 0: if group == FULL_MOVE: if not variation: if position != -1 and last_board.plyCount >= position: break mstr = m.group(MOVE) try: lmove = parseAny(last_board, mstr) except ParsingError as err: # TODO: save the rest as comment # last_board.children.append(string[m.start():]) notation, reason, boardfen = err.args ply = last_board.plyCount if ply % 2 == 0: moveno = "%d." % (ply // 2 + 1) else: moveno = "%d..." % (ply // 2 + 1) errstr1 = _( "The game can't be read to end, because of an error parsing move %(moveno)s '%(notation)s'." ) % { 'moveno': moveno, 'notation': notation } errstr2 = _("The move failed because %s.") % reason self.error = LoadingError(errstr1, errstr2) break except Exception: ply = last_board.plyCount if ply % 2 == 0: moveno = "%d." % (ply // 2 + 1) else: moveno = "%d..." % (ply // 2 + 1) errstr1 = _( "Error parsing move %(moveno)s %(mstr)s") % { "moveno": moveno, "mstr": mstr } self.error = LoadingError(errstr1, "") break new_board = last_board.clone() new_board.applyMove(lmove) if m.group(MOVE_COMMENT): new_board.nags.append(symbol2nag( m.group(MOVE_COMMENT))) new_board.prev = last_board # set last_board next, except starting a new variation if variation and last_board == board: boards[0].next = new_board else: last_board.next = new_board boards_append(new_board) last_board = new_board elif group == COMMENT_REST: last_board.children.append(text[1:]) elif group == COMMENT_BRACE: comm = text.replace('{\r\n', '{').replace('\r\n}', '}') # Preserve new lines of lichess study comments if self.path is not None and "lichess_study_" in self.path: comment = comm[1:-1] else: comm = comm[1:-1].splitlines() comment = ' '.join([line.strip() for line in comm]) if variation and last_board == board: # initial variation comment boards[0].children.append(comment) else: last_board.children.append(comment) elif group == COMMENT_NAG: last_board.nags.append(text) # TODO elif group == RESULT: # if text == "1/2": # status = reprResult.index("1/2-1/2") # else: # status = reprResult.index(text) break else: print("Unknown:", text) return boards # , status
def loadToModel(self, rec, position=-1, model=None): """ Parse game text and load game record header tags to a GameModel object """ if model is None: model = GameModel() if self.pgn_is_string: rec = self.games[0] # Load mandatory tags for tag in mandatory_tags: model.tags[tag] = rec[tag] # Load other tags for tag in ('WhiteElo', 'BlackElo', 'ECO', 'TimeControl', 'Annotator'): model.tags[tag] = rec[tag] if self.pgn_is_string: for tag in rec: if isinstance(rec[tag], str) and rec[tag]: model.tags[tag] = rec[tag] else: model.info = self.tag_database.get_info(rec) extra_tags = self.tag_database.get_exta_tags(rec) for et in extra_tags: model.tags[et['tag_name']] = et['tag_value'] if self.pgn_is_string: variant = rec["Variant"].capitalize() else: variant = self.get_variant(rec) if model.tags['TimeControl']: tc = parseTimeControlTag(model.tags['TimeControl']) if tc is not None: secs, gain, moves = tc model.timed = True model.timemodel.secs = secs model.timemodel.gain = gain model.timemodel.minutes = secs / 60 model.timemodel.moves = moves for tag, color in (('WhiteClock', WHITE), ('BlackClock', BLACK)): if tag in model.tags: try: millisec = parseClockTimeTag(model.tags[tag]) # We need to fix when FICS reports negative clock time like this # [TimeControl "180+0"] # [WhiteClock "0:00:15.867"] # [BlackClock "23:59:58.820"] start_sec = ( millisec - 24 * 60 * 60 * 1000 ) / 1000. if millisec > 23 * 60 * 60 * 1000 else millisec / 1000. model.timemodel.intervals[color][0] = start_sec except ValueError: raise LoadingError("Error parsing '%s'" % tag) fenstr = rec["FEN"] if variant: if variant not in name2variant: raise LoadingError("Unknown variant %s" % variant) model.tags["Variant"] = variant # Fixes for some non statndard Chess960 .pgn if (fenstr is not None) and variant == "Fischerandom": parts = fenstr.split() parts[0] = parts[0].replace(".", "/").replace("0", "") if len(parts) == 1: parts.append("w") parts.append("-") parts.append("-") fenstr = " ".join(parts) model.variant = name2variant[variant] board = LBoard(model.variant.variant) else: model.variant = NormalBoard board = LBoard() if fenstr: try: board.applyFen(fenstr) model.tags["FEN"] = fenstr except SyntaxError as err: board.applyFen(FEN_EMPTY) raise LoadingError( _("The game can't be loaded, because of an error parsing FEN" ), err.args[0]) else: board.applyFen(FEN_START) boards = [board] del model.moves[:] del model.variations[:] self.error = None movetext = self.get_movetext(rec) boards = self.parse_movetext(movetext, boards[0], position) # The parser built a tree of lboard objects, now we have to # create the high level Board and Move lists... for board in boards: if board.lastMove is not None: model.moves.append(Move(board.lastMove)) self.has_emt = False self.has_eval = False def _create_board(model, node): if node.prev is None: # initial game board board = model.variant(setup=node.asFen(), lboard=node) else: move = Move(node.lastMove) try: board = node.prev.pieceBoard.move(move, lboard=node) except Exception: raise LoadingError( _("Invalid move."), "%s%s" % (move_count(node, black_periods=True), move)) return board def walk(model, node, path): boards = path stack = [] current = node while current is not None: board = _create_board(model, current) boards.append(board) stack.append(current) current = current.next else: model.variations.append(list(boards)) while stack: current = stack.pop() boards.pop() for child in current.children: if isinstance(child, list): if len(child) > 1: # non empty variation, go walk walk(model, child[1], list(boards)) else: if not self.has_emt: self.has_emt = child.find("%emt") >= 0 if not self.has_eval: self.has_eval = child.find("%eval") >= 0 # Collect all variation paths into a list of board lists # where the first one will be the boards of mainline game. # model.boards will allways point to the current shown variation # which will be model.variations[0] when we are in the mainline. walk(model, boards[0], []) model.boards = model.variations[0] self.has_emt = self.has_emt and model.timed if self.has_emt or self.has_eval: if self.has_emt: blacks = len(model.moves) // 2 whites = len(model.moves) - blacks model.timemodel.intervals = [ [model.timemodel.intervals[0][0]] * (whites + 1), [model.timemodel.intervals[1][0]] * (blacks + 1), ] model.timemodel.intervals[0][0] = secs model.timemodel.intervals[1][0] = secs for ply, board in enumerate(boards): for child in board.children: if isinstance(child, str): if self.has_emt: match = move_time_re.search(child) if match: movecount, color = divmod(ply + 1, 2) hour, minute, sec, msec = match.groups() prev = model.timemodel.intervals[color][ movecount - 1] hour = 0 if hour is None else int(hour[:-1]) minute = 0 if minute is None else int( minute[:-1]) msec = 0 if msec is None else int(msec) msec += int(sec) * 1000 + int( minute) * 60 * 1000 + int( hour) * 60 * 60 * 1000 model.timemodel.intervals[color][ movecount] = prev - msec / 1000. + gain if self.has_eval: match = move_eval_re.search(child) if match: sign, num, fraction, depth = match.groups() sign = 1 if sign is None or sign == "+" else -1 num = int(num) fraction = 0 if fraction is None else int( fraction) value = sign * (num * 100 + fraction) depth = "" if depth is None else depth if board.color == BLACK: value = -value model.scores[ply] = ("", value, depth) log.debug("pgn.loadToModel: intervals %s" % model.timemodel.intervals) # Find the physical status of the game model.status, model.reason = getStatus(model.boards[-1]) # Apply result from .pgn if the last position was loaded if position == -1 or len(model.moves) == position - model.lowply: if self.pgn_is_string: result = rec["Result"] if result in pgn2Const: status = pgn2Const[result] else: status = RUNNING else: status = rec["Result"] if status in (WHITEWON, BLACKWON) and status != model.status: model.status = status model.reason = WON_RESIGN elif status == DRAW and status != model.status: model.status = DRAW model.reason = DRAW_AGREE if model.timed: model.timemodel.movingColor = model.boards[-1].color # If parsing gave an error we throw it now, to enlarge our possibility # of being able to continue the game from where it failed. if self.error: raise self.error return model
def simple_parse_movetext(self, string, board, movelist, bitboards): """Parses a movelist part of one game. If find anything not being a move immediately returns False It fills list of lmoves parsed with parseSAN() and list of integers representing a board with occupied fields bitboard Arguments: srting - str (movelist) board - lboard (FEN_START) movelist - an empty array("H") to fill bitboards - an empty list to fill Return: True if parser find moves only.""" movelist_append = movelist.append for m in re.finditer(pattern, string): group, text = m.lastindex, m.group(m.lastindex) if group in (COMMENT_BRACE, COMMENT_NAG, VARIATION_END, VARIATION_START, COMMENT_REST): return False elif group == FULL_MOVE: if m.group(MOVE_COMMENT): return False mstr = m.group(MOVE) try: lmove = parseSAN(board, mstr, full=False) except ParsingError as err: notation, reason, boardfen = err.args ply = board.plyCount if ply % 2 == 0: moveno = "%d." % (ply // 2 + 1) else: moveno = "%d..." % (ply // 2 + 1) errstr1 = _( "The game can't be read to end, because of an error parsing move %(moveno)s '%(notation)s'." ) % { 'moveno': moveno, 'notation': notation } errstr2 = _("The move failed because %s.") % reason self.error = LoadingError(errstr1, errstr2) break except: ply = board.plyCount if ply % 2 == 0: moveno = "%d." % (ply // 2 + 1) else: moveno = "%d..." % (ply // 2 + 1) errstr1 = _("Error parsing move %(moveno)s %(mstr)s") % { "moveno": moveno, "mstr": mstr } self.error = LoadingError(errstr1, "") break bitboards.append(board.friends[0] | board.friends[1]) board.applyMove(lmove, full=False) movelist_append(lmove) elif group == RESULT: pass else: print("Unknown:", text) return True