Exemple #1
0
 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)}"
                    )