class Game: MOVES_TO_PGN = re.compile('\(\d+?:\d+?\)') PLAYER_NAME = re.compile('\w+') def __init__(self, initial_state=None, game_info=None, board=None): # world self.board = None self.waiting_for_board = False # game self._history = GameHistory() self.selected = [] self.piece_flying = False self.piece_clicked = False self.move_sent = False # properties self.turn = True self.halfmove = -1 # ONESHOT PROPS self.number = 0 self.rating = ['',''] self.rated = '' self.player = ['',''] self.player_names = ['',''] self.opponent = self.me = '' self._kind = 0 self.kind = 0 self.side = True self.itime = self.iinc = '' self.name = '' self.interruptus = False self.altline = False self.last_style12 = None self.result = None if game_info: self.set_gameinfo(game_info) if initial_state: self.set_state(initial_state) if board: board.set_game(self) self.board = board def set_board(self, board): self.board = board def set_interruptus(self, result=None): if not self.result: self.result = result if result: self.name = self.name + ' - ' + result self.interruptus = True if self.board: self.board.reset(True) def set_altline(self): self.altline = not isinstance(self._history[-1], Style12) def set_gameinfo(self, info): self.number = int(info.split()[1]) self.rating = ['('+x+')' for x in info.split()[9].split('=')[1].split(',')[::-1]] self.rated = ('rated' if info.split()[4].split('=')[1] == '1' else 'unrated') def set_state(self, new_state): if isinstance(new_state, GameState): state = new_state else: state = Style12(new_state) self.interruptus = False self.last_style12 = state if not self._history.update(state) and self.opponent != '': self.fill_history(state) if state in self._history or self.kind & KIND_EXAMINING: r = self._history.set_mainline(state) if self.board and self.board.movetree: if r: self.board.movetree.set_mainline(*r) else: self.board.movetree.repopulate() elif self.board and self.board.movetree: self.board.movetree.update_node(state) self.move_sent = False if state is self._history[-1]: self.turn = state.turn #flush premove if (self.kind & KIND_PLAYING and len(self.selected) == 2 and self.side == self.turn and self.halfmove != state.halfmove): config.cli.send_cmd((postopos(self.selected[0])+ postopos(self.selected[1])), save_history=False) # move was ilegal? preserve selected piece elif (len(self.selected) == 2 and self.halfmove == state.halfmove): self.selected.pop() elif (self.kind & KIND_PLAYING and len(self.selected) == 1 and self.side == self.turn): pass elif state is not self._history[-1]: pass else: self.selected.clear() self.halfmove = state.halfmove if (not self.player_names == state.names or (abs(self._kind) == 2 and self._kind != state.relation)): self.number = state.game_number self.player = [state.names[0]+self.rating[0], state.names[1]+self.rating[1]] self.player_names = state.names self._kind = state.relation self.kind = (KIND_PLAYING if abs(self._kind) == 1 else KIND_OBSERVING if self._kind == 0 else KIND_EXAMINING if self._kind == 2 else KIND_ISOLATED if self._kind == -3 else KIND_OBSERVING | KIND_EXAMINING) self.side = ((self.turn and (self._kind == 1)) or ((self._kind == -1) and not self.turn) or (self.kind ^ KIND_PLAYING and (self.player_names[0] != config.fics_user or self.player_names[1] == config.fics_user))) self.opponent = self.player_names[not self.side] self.me = self.player_names[self.side] self.itime = state.itime self.iinc = state.iinc self.name = ('Game {}: '.format(state.game_number) + ('Observing ' if self.kind & KIND_OBSERVING else 'Examining ' if self.kind & KIND_EXAMINING else '')+ ' v/s '.join(self.player[::-1])+' - '+ '/'.join([self.itime, self.iinc])+ # (' - '+self.rated if self.rated else '' )+ (' - ' + self.result if self.result else '') ).strip() if self.kind & KIND_OBSERVING: self.get_old_moves() if self.kind & (KIND_EXAMINING | KIND_PLAYING): config.gui.seek_graph_destroy() if self.board: self.board.reset(True) for hdl in self.player_names: config.update_handle(hdl) else: if self.board: self.board.reset(False) def get_moves(self): moves_txt = list() if config.cli.send_cmd('moves {}'.format(self.number), wait_for=WAIT_FOR_MOVELIST, ans_buff=moves_txt, save_history=False): moves_txt = ''.join(moves_txt) if ' 1. ' in moves_txt: moves_txt = moves_txt[moves_txt.index(' 1. ')::] moves_txt = self.MOVES_TO_PGN.sub('', moves_txt) return Pgn(txt=moves_txt).main_line return list() def fill_history(self, state): l = self.get_moves() m = next((x for x in self._history._directory.back_sorted() if x in l and x != state), None) if m and m in l and state in l: l = l[l.index(m)+1:l.index(state)] if l: l[0].prev = m m.next.append(l[0]) l[-1].next.clear() if m in self._history: self._history.extend(l) else: for x in l: self._history.update_reg(x) self._history.update(state) def get_old_moves(self): l = self.get_moves() n, i = next(((x,l.index(x)) for x in l if x == self._history[0]), (None, 0)) if i: for x in l[i::]: l.remove(x) l._directory[x.halfmove].clear() for x in self._history: l.update_reg(x) l[-1].next = list([self._history[0]]) self._history[0].prev = l[-1] l.extend(self._history) self._history = l def backward(self, n): for i in range(n): if len(self._history)>1: x = self._history.pop() if x and self.board and self.board.movetree: self.board.movetree.recolor(x) else: break self.turn = self._history[-1].turn if not (self.kind & (KIND_OBSERVING | KIND_PLAYING)): config.cli.send_cmd('backward {}'.format(n), save_history=False) elif not len(self._history): config.cli.print("You're at the beginning of the game.") if isinstance(self._history[-1], Style12): self.altline = False def forward(self, n): if self.kind & (KIND_OBSERVING | KIND_PLAYING): for i in range(n): if len(self._history[-1].next): x = self._history[-1].next[0] self._history.append(x) if x and self.board and self.board.movetree: self.board.movetree.recolor(x) else: break self.turn = self._history[-1].turn elif not (self.kind & (KIND_OBSERVING | KIND_PLAYING)): config.cli.send_cmd('forward {}'.format(n), save_history=False) if (self.kind & KIND_OBSERVING and not isinstance(self._history[-1], Style12)): self.altline = True def piece_in(self, pos): return self._history[-1].piece_in(pos) def click(self, pos): x = self.piece_in(pos) # Another piece is selected if (x and (self.side if self.kind & KIND_PLAYING else self.turn) == x.isupper()): self.piece_clicked = x self.selected.clear() self.selected.append(pos) return None else: self.piece_clicked = False # MOVE if len(self.selected): if len(self.selected) > 1 : self.selected.pop() self.selected.append(pos) if self.kind & KIND_OBSERVING: ns = self._history[-1].make(self.selected) if ns: self.set_state(ns) self.altline = True else: self.selected.pop() elif (self.kind ^ KIND_PLAYING) or (self.side == self.turn) : self.move_sent = True return (postopos(self.selected[0]) +postopos(self.selected[1])) return None def release(self, pos): if self.piece_flying: self.piece_flying = False x = self.piece_in(pos) if (x and (self.side if self.kind & KIND_PLAYING else self.turn) == x.isupper()): self.piece_clicked = x self.selected.clear() self.selected.append(pos) return None # MOVE if len(self.selected): if len(self.selected) > 1: self.selected.pop() self.selected.append(pos) if self.kind & KIND_OBSERVING: ns = self._history[-1].make(self.selected) if ns: self.set_state(ns) self.altline = True else: self.selected.pop() elif (self.kind ^ KIND_PLAYING) or (self.side == self.turn) : self.move_sent = True return (postopos(self.selected[0]) +postopos(self.selected[1])) return None def figures(self): if len(self._history): fl = [ x for x in self._history[-1].figures if x[0] not in self.selected ] if not self.piece_flying and len(self.selected) > 1: if self.piece_in(self.selected[-2]): fl.append((self.selected[-1], self.piece_in(self.selected[-2]))) return fl elif not self.piece_flying and len(self.selected): if self.piece_in(self.selected[-1]): fl.append((self.selected[-1], self.piece_in(self.selected[-1]))) return fl else: return fl else: return [] def is_being_played(self): return ((not (self.kind & (KIND_EXAMINING | KIND_ISOLATED))) and not self.interruptus) def pgn(self, fd, save_timestamps=False): root = rewind(self._history[0]) if root.next: start_time = root.next[0].time else: start_time = time() date = strftime('%Y.%m.%d', localtime(start_time)) print('[Date "{}"]'.format(date), file=fd) if self.result: print('[Result "{}"]'.format(self.result), file=fd) print('[White "{}"]'.format(self.player_names[1]), file=fd) print('[Black "{}"]'.format(self.player_names[0]), file=fd) print('[FEN "{}"]\n'.format(root.fen()), file=fd) L = list() l = list() x = root Lpoped = False split = False while x: if x and x.halfmove>=0 and not Lpoped: if x.halfmove%2: print(x.move, end=' ', file=fd) else: print(str(x.halfmove//2+1)+'. '+x.move, end=' ', file=fd) if save_timestamps: times = (x.btime if x.halfmove%2 else x.wtime) print('{{ ({}:{}) }}'.format(times//60, times%60), end=' ', file=fd) if x.comment: print('{{ {} }}'.format(x.comment), end=' ', file=fd) if not x.next and L and not split: print(')', end=' ', file=fd) Lpoped = False if x.next and not split: if len(x.next) > 1: L.append(x.next[0]) l.append(x.next[1::][::-1]) x = x.next[0] split = True else: x = x.next[0] else: if l: if l[-1]: x = l[-1].pop() print('(', end=' ', file=fd) if x.halfmove%2: print(str(x.halfmove//2+1)+'. ... ', end=' ', file=fd) else: l.pop() if L: x = L.pop() Lpoped = True else: x = None else: if L: x = L.pop() Lpoped = True print(')', end=' ', file=fd) else: x = None split = False print('\n', file=fd) def setup_from_pgn(self, pgn): self._history = pgn.main_line for x in self._history[1::]: self._history.remove(x) root = self._history[-1] x = next((x[1] for x in pgn.header if x[0].lower() == 'fen'), None) if x: fen = x.split() config.cli.send_cmd('bsetup fen '+fen[0], save_history=False) config.cli.send_cmd('tomove '+('white' if fen[1]=='w' else 'black'), save_history=False) config.cli.send_cmd('bsetup done', save_history=False) x = next((x[1] for x in pgn.header if x[0].lower() == 'result'), None) if x: self.result = x for c, n in zip(['black', 'white'], ['bname ', 'wname ']): x = next((x[1] for x in pgn.header if x[0].lower() == c), None) if x: m = self.PLAYER_NAME.match(x) if m: x = m.group() config.cli.send_cmd(n+x, save_history=False) if self.board: self.board.reset(True) if self.board.movetree: self.board.movetree.repopulate() @property def marked(self): return [ pos2sq(x) for x in self._history.marked() ] @property def material(self): if len(self._history): d = (self._history[-1].strength[1]-self._history[-1].strength[0])*( 1 if self.side else -1) return '=' if not d else '{:+d}'.format(d) else: return '=' @property def time(self): if len(self._history): if (self.is_being_played() and self.halfmove > 1 and (not self.interruptus) and self.last_style12 is self._history[-1]): Dt = time()-self._history[-1].time else: Dt = 0 if self._history[-1].turn: return [int(self._history[-1].btime), int(self._history[-1].wtime-Dt)] else: return [int(self._history[-1].btime-Dt), int(self._history[-1].wtime)] else: return [0, 0]
class Pgn(): def __new__(cls, txt=None, path=None, hdr=None, it=None, ic=None): self = object.__new__(cls) if ic: last_move = GameState(ic) else: last_move = GameState() self.main_line = GameHistory([last_move]) self.header = list() if hdr: self.header.append(hdr) hdr_done = False var_stem = list() if path: with open(path, 'r') as fd: it = _pgn_re.finditer(fd.read()) elif txt: config.log('Pgn got txt="{}"'.format(txt)) it = _pgn_re.finditer(txt) for m in it: if m.group('hdr_name'): if hdr_done: self = [self] tail = Pgn(hdr=(m.group('hdr_name'), m.group('hdr')), it=it) if isinstance(tail, list): self.extend(tail) else: self.append(tail) break else: self.header.append((m.group('hdr_name'),m.group('hdr'))) if m.group('hdr_name').lower() == 'fen': last_move = GameState(m.group('hdr')) self.main_line = GameHistory([last_move]) elif m.group('vstart'): hdr_done = True var_stem.append(last_move) last_move = last_move.prev elif m.group('vend'): hdr_done = True last_move = var_stem.pop() elif m.group('move'): hdr_done = True s = last_move.make(San(m.group('move'))) # print(m.group('move'), end=' -> ') # print(s) if not s: config.cli.print('There was an error processing: {}'.format(m.group('move'))) config.log('There was an error processing: {}'.format(m.group('move'))) w = next((y[1] for y in self.header if y[0].lower() == 'white'), '') b = next((y[1] for y in self.header if y[0].lower() == 'black'), '') config.cli.print('{} v/s {}'.format(w, b)) break # last_move.next.append(s) last_move = s if not len(var_stem): self.main_line.append(s) else: self.main_line.update_reg(s) elif m.group('comment'): if last_move.comment: last_move.comment = last_move.comment + ' ' + m.group('comment') else: last_move.comment = m.group('comment') return self