def display_path(cls, path, desc=None, nrow=8, ncol=8, width=400, height=400): """ Display board with path squares labeled :desc: text description :path: list of squares in order returns ChssBoardDisplay of displayed board """ if not hasattr(cls, 'wm'): cls.wm = Tk() wm_base = cls.wm wm = Toplevel(wm_base) wm_base.withdraw() wm.geometry("%dx%d" % (width, height)) frame = Frame(wm, width=width, height=height, bg="", colormap="new") frame.pack(fill=BOTH, expand=True) wm.title(desc) #creation of an instance cb = ChessBoard(nrow=nrow, ncol=ncol) cbd = ChessBoardDisplay(wm=wm, frame=frame, board=cb, path=path, desc=desc, width=width, height=height, nrow=nrow, ncol=ncol) if path is None: SlTrace.lg("No path") return for loc in path: loc = cb.loc2tuple(loc) cbd.label_square(loc) cbd.display() wd = 7 if len(path) == 0: return loc_start = path[0] sq1 = cbd.get_square(loc_start) cbd.draw_outline(sq1, color="green", width=wd) loc_end = path[-1] sq2 = cbd.get_square(loc_end) cbd.draw_outline(sq2, color="red", width=wd) if cb.is_neighbor(loc_end, loc_start): p1 = sq1.get_center() p2 = sq2.get_center() cbd.draw_line(p1,p2, color="blue", width=wd) prev_loc = None for loc in path: loc = cb.loc2tuple(loc) if prev_loc is not None: cbd.display_connected_moves(loc, prev_loc) prev_loc = loc wm.lift() cbd.update_display() return cbd
class ChessTourValidation: def __init__(self, locs=None, ncol=None, nrow=None, piece=None, closed_tours=True): """ Set up validation constraints on which validation is made :locs: squares for which possible tour default ncolxnrow :ncol: number of columns default: nrow if nrow is not None else 8 :nrow: number of rows default: ncol if ncol is not None else 8 :piece: Chess piece letter e.g. 'K' for black king default: 'N' - black knight :closed_tours: Check for closed tour (ending square is one piece move from start) default: True """ if ncol is None: ncol = 8 if nrow is None else nrow self.ncol = ncol if nrow is None: nrow = 8 if ncol is None else ncol self.nrow = nrow if locs is None: locs = [] for ic in range(ncol): for ir in range(nrow): locs.append((ic, ir)) self.locs = locs if piece is None: piece = 'N' self.piece = piece self.closed_tours = closed_tours self.cb = ChessBoard(ncol=self.ncol, nrow=self.nrow) # For acces to basic fns def find_path_duplicates(self, dpaths=None): """ Find any duplicate paths :path_list: list of displayed paths :returns: list of lists of matching dpaths """ if dpaths is None: raise SelectError("Missing REQUIRED dpaths") matches = [] dpaths_to_check = dpaths[:] # Copy so pop won't modify dpaths while len(dpaths_to_check) > 1: dpgrp = [] # latest group of matches dp1 = dpaths_to_check.pop(0) p1 = dp1.path dptc_next = [] # Next remaining group for dp2 in dpaths_to_check: p2 = dp2.path if self.match_path(p1, p2, bidirectional=True): if len(dpgrp) == 0: dpgrp.append(dp1) # Add first dpgrp.append(dp2) # Add in match else: dptc_next.append(dp2) # Add to remaining list if len(dpgrp) > 1: matches.append(dpgrp) # Add to group of matches dpaths_to_check = dptc_next # Set remaining list return matches def match_path(self, p1, p2, bidirectional=True): """ Match two paths each containing a list of unique locs :p1: path 1 list of locs :p2: path 2 list of locs :bidirectional: check for reversed default: True :returns: return True if exact match """ if len(p1) != len(p2): return False # Unequal lengths if len(p1) == 0: return True # Both empty loc1 = p1[0] if loc1 not in p2: return False # loc in p1 not in p2 iloc2 = p2.index(loc1) p2 = p2[iloc2:] + p2[:iloc2] # Wrap p2 matching first loc of p1,p2 if p1 == p2: return True if bidirectional: p2 = p2[::-1] if p1 == p2: return True def is_valid_moves(self, dpath, closed_tours=None, quiet=False, prefix=None): """ Check if locs path consists of a list of valid piece moves :dpath: display path (square denotations) tuple or algebraic notation :closed_tours: check for closed tour default:self.closed_tour else True :quiet: suppress diagnostic output default: False :prefix: optional prefix to diagnostic messages :returns: True if legitimate moves within validation squares """ cb = self.cb path = dpath.path if prefix is None: prefix = "" if closed_tours is None: closed_tours = True if self.closed_tours is None else self.closed_tours prev_loc = None for loc in path: loc = cb.loc2tuple(loc) if loc not in self.locs: if not quiet: SlTrace.lg( f"{prefix} move {cb.loc2desc(loc)} is not in squares:{cb.path_desc(self.locs)}" ) return False if prev_loc is not None: if not self.cb.is_neighbor(prev_loc, loc): if not quiet: SlTrace.lg( f"move {loc2desc(prev_loc)} to {loc2desc(loc)} is not legal" ) return False prev_loc = loc if closed_tours: prev_loc = path[-1] loc = path[0] if not self.cb.is_neighbor(prev_loc, loc): if not quiet: SlTrace.lg( f"path closing move {loc2desc(prev_loc)} to {loc2desc(loc)} is not legal" ) return False return True def is_covering_but_once(self, dpath, quiet=False, prefix=None): """ Check if path covers the board(self.locs) touching each square but once :dpath: display path of squares traversed :quiet: suppress diagnostics default: False """ cb = self.cb path = dpath.path if prefix is None: prefix = "" touches = {} for loc in path: loc = cb.loc2tuple(loc) if loc not in touches: touches[loc] = 1 else: if not quiet: SlTrace.lg( f"\n {prefix} Repeating square {cb.loc2desc(loc)}") return False for loc in self.locs: loc = cb.loc2tuple(loc) if loc not in touches: if not quiet: SlTrace.lg( f"\n {prefix} {cb.loc2desc(loc)} not in {cb.squares_list(path)}" ) return False return True def is_valid_tour(self, dpath, closed_tours=True, quiet=False, prefix=None): """ Test if valid tour :dpath: DisplayPath of tour :closed_tours: accept only closed tours default: True :quiet: suppress diagnostics default: False :prefix: optional prefix to fail diagnostic messages :returns: True iff path is a valid tour """ if prefix is None: prefix = "" if not self.is_covering_but_once(dpath, quiet=quiet, prefix=prefix): return False if not self.is_valid_moves( dpath, closed_tours=closed_tours, quiet=quiet, prefix=prefix): return False return True def is_valid_tours(self, dpaths, closed_tours=True, quiet=False, all_fails=True): """ Test if valid tour :dpaths: list of display paths(DisplayedPath) (each a sequence of locs) :closed_tours: accept only closed tours default: True :quiet: suppress diagnostics default: False :all_fails: test and report on all False=> report first fail and stop default:all :returns: True iff all paths are valid tours """ cb = self.cb res = True for dpath in dpaths: path = dpath.path prefix = f"{dpath.org_no}: {cb.loc2desc(path[0])}: {dpath.desc}\n " if not self.is_valid_tour( dpath, closed_tours=closed_tours, quiet=quiet, prefix=prefix): res = False if not all_fails: break return res def report_duplicate_paths(self, dpaths): cb = self.cb dup_paths = self.find_path_duplicates(dpaths) if len(dup_paths) > 0: SlTrace.lg(f"{len(dup_paths)} duplicate path groups") for igrp, dup_grp in enumerate(dup_paths): SlTrace.lg(f"{igrp+1}: {cb.loc2desc(dup_grp[0].path[0])}") for grp in dup_grp: SlTrace.lg( f"{cb.loc2desc(grp.path[0])}-{cb.loc2desc(grp.path[-1])}: {cb.path_desc(grp.path)}" )