It is not possible to make the crossword check work for all symbol

83 views Asked by At

If I enter a single character, the message that everything is OK is not output (as it should be). But if I enter 2 correct characters, then the check says that everything is ok. Although it should only be used when all the characters are filled in and correct. I'm a beginner, I don't understand how to fix it. There is a lot of excess in the code, but it is only important for me that the check works. The entire program code is shown below.

    import random, re, time, string
    from copy import copy as duplicate
    
    class Crossword(object):
        def __init__(self, cols, rows, empty = '-', maxloops = 2000, available_words=[]):
            self.cols = cols
            self.rows = rows
            self.empty = empty
            self.maxloops = maxloops
            self.available_words = available_words
            self.randomize_word_list()
            self.current_word_list = []
            self.debug = 0
            self.clear_grid()
    
        def clear_grid(self):
            self.grid = []
            for i in range(self.rows):
                ea_row = []
                for j in range(self.cols):
                    ea_row.append(self.empty)
                self.grid.append(ea_row)
    
        def randomize_word_list(self):
            temp_list = []
            for word in self.available_words:
                if isinstance(word, Word):
                    temp_list.append(Word(word.word, word.clue))
                else:
                    temp_list.append(Word(word[0], word[1]))
            random.shuffle(temp_list)
            temp_list.sort(key=lambda i: len(i.word), reverse=True)
            self.available_words = temp_list
    
        def compute_crossword(self, time_permitted = 1.00, spins=2):
            time_permitted = float(time_permitted)
    
            count = 0
            copy = Crossword(self.cols, self.rows, self.empty, self.maxloops, self.available_words)
    
            start_full = float(time.time())
            while (float(time.time()) - start_full) < time_permitted or count == 0:
                self.debug += 1
                copy.current_word_list = []
                copy.clear_grid()
                copy.randomize_word_list()
                x = 0
                while x < spins:
                    for word in copy.available_words:
                        if word not in copy.current_word_list:
                            copy.fit_and_add(word)
                    x += 1
    
                if len(copy.current_word_list) > len(self.current_word_list):
                    self.current_word_list = copy.current_word_list
                    self.grid = copy.grid
                count += 1
            return
    
        def suggest_coord(self, word):
            count = 0
            coordlist = []
            glc = -1
            for given_letter in word.word:
                glc += 1
                rowc = 0
                for row in self.grid:
                    rowc += 1
                    colc = 0
                    for cell in row:
                        colc += 1
                        if given_letter == cell:
                            try:
                                if rowc - glc > 0:
                                    if ((rowc - glc) + word.length) <= self.rows:
                                        coordlist.append([colc, rowc - glc, 1, colc + (rowc - glc), 0])
                            except: pass
                            try:
                                if colc - glc > 0:
                                    if ((colc - glc) + word.length) <= self.cols:
                                        coordlist.append([colc - glc, rowc, 0, rowc + (colc - glc), 0])
                            except: pass
    
            new_coordlist = self.sort_coordlist(coordlist, word)
            return new_coordlist
    
        def sort_coordlist(self, coordlist, word):
            new_coordlist = []
            for coord in coordlist:
                col, row, vertical = coord[0], coord[1], coord[2]
                coord[4] = self.check_fit_score(col, row, vertical, word)
                if coord[4]:
                    new_coordlist.append(coord)
            random.shuffle(new_coordlist)
            new_coordlist.sort(key=lambda i: i[4], reverse=True)
            return new_coordlist
    
        def fit_and_add(self, word):
            fit = False
            count = 0
            coordlist = self.suggest_coord(word)
    
            while not fit and count < self.maxloops:
    
                if len(self.current_word_list) == 0:
                    vertical, col, row = random.randrange(0, 2), 1, 1
    
                    if self.check_fit_score(col, row, vertical, word):
                        fit = True
                        self.set_word(col, row, vertical, word, force=True)
                else:
                    try:
                        col, row, vertical = coordlist[count][0], coordlist[count][1], coordlist[count][2]
                    except IndexError: return
    
                    if coordlist[count][4]:
                        fit = True
                        self.set_word(col, row, vertical, word, force=True)
    
                count += 1
            return
    
        def check_fit_score(self, col, row, vertical, word):
    
            if col < 1 or row < 1:
                return 0
    
            count, score = 1, 1
            for letter in word.word:
                try:
                    active_cell = self.get_cell(col, row)
                except IndexError:
                    return 0
    
                if active_cell == self.empty or active_cell == letter:
                    pass
                else:
                    return 0
    
                if active_cell == letter:
                    score += 1
    
                if vertical:
                    if active_cell != letter:
                        if not self.check_if_cell_clear(col+1, row):
                            return 0
    
                        if not self.check_if_cell_clear(col-1, row):
                            return 0
    
                    if count == 1:
                        if not self.check_if_cell_clear(col, row-1):
                            return 0
    
                    if count == len(word.word):
                        if not self.check_if_cell_clear(col, row+1):
                            return 0
                else:
                    if active_cell != letter:
                        if not self.check_if_cell_clear(col, row-1):
                            return 0
    
                        if not self.check_if_cell_clear(col, row+1):
                            return 0
    
                    if count == 1:
                        if not self.check_if_cell_clear(col-1, row):
                            return 0
    
                    if count == len(word.word):
                        if not self.check_if_cell_clear(col+1, row):
                            return 0
    
                if vertical:
                    row += 1
                else:
                    col += 1
    
                count += 1
    
            return score
    
        def set_word(self, col, row, vertical, word, force=False):
            if force:
                word.col = col
                word.row = row
                word.vertical = vertical
                self.current_word_list.append(word)
    
                for letter in word.word:
                    self.set_cell(col, row, letter)
                    if vertical:
                        row += 1
                    else:
                        col += 1
            return
    
        def set_cell(self, col, row, value):
            self.grid[row-1][col-1] = value
    
        def get_cell(self, col, row):
            return self.grid[row-1][col-1]
    
        def check_if_cell_clear(self, col, row):
            try:
                cell = self.get_cell(col, row)
                if cell == self.empty:
                    return True
            except IndexError:
                pass
            return False
    
        def solution(self):
            outStr = ""
            for r in range(self.rows):
                for c in self.grid[r]:
                    outStr += '%s ' % c
                outStr += '\n'
            return outStr
    
        def word_find(self):
            outStr = ""
            for r in range(self.rows):
                for c in self.grid[r]:
                    if c == self.empty:
                        outStr += '%s ' % string.ascii_lowercase[random.randint(0,len(string.ascii_lowercase)-1)]
                    else:
                        outStr += '%s ' % c
                outStr += '\n'
            return outStr
    
        def order_number_words(self):
            self.current_word_list.sort(key=lambda i: (i.col + i.row))
            count, icount = 1, 1
            for word in self.current_word_list:
                word.number = count
                if icount < len(self.current_word_list):
                    if word.col == self.current_word_list[icount].col and word.row == self.current_word_list[icount].row:
                        pass
                    else:
                        count += 1
                icount += 1
    
        def display(self, order=True):
            outStr = ""
            if order:
                self.order_number_words()
    
            copy = self
    
            for word in self.current_word_list:
                copy.set_cell(word.col, word.row, word.number)
    
            for r in range(copy.rows):
                for c in copy.grid[r]:
                    outStr += '%s ' % c
                outStr += '\n'
    
            outStr = re.sub(r'[a-z]', ' ', outStr)
            return outStr
    
        def word_bank(self):
            outStr = ''
            temp_list = duplicate(self.current_word_list)
            random.shuffle(temp_list)
            for word in temp_list:
                outStr += '%s\n' % word.word
            return outStr
    
        def legend(self):
            outStr = ''
            for word in self.current_word_list:
                outStr += '%d. (%d,%d) %s: %s\n' % (word.number, word.col, word.row, word.down_across(), word.clue )
            return outStr
    
    class Word(object):
        def __init__(self, word=None, clue=None):
            self.word = re.sub(r'\s', '', word.lower())
            self.clue = clue
            self.length = len(self.word)
            self.row = None
            self.col = None
            self.vertical = None
            self.number = None
    
        def down_across(self):
            if self.vertical:
                return 'down'
            else:
                return 'across'
    
    word_list = ['saffron', 'The dried, orange yellow plant used to as dye and as a cooking spice.'], \
['pumpernickel', 'Dark, sour bread made from coarse ground rye.'], \
['leaven', 'An agent, such as yeast, that cause batter or dough to rise..'], \
['coda', 'Musical conclusion of a movement or composition.'], \
['paladin', 'A heroic champion or paragon of chivalry.'], \
['syncopation', 'Shifting the emphasis of a beat to the normally weak beat.'], \
['albatross', 'A large bird of the ocean having a hooked beek and long, narrow wings.'], \
['harp', 'Musical instrument with 46 or more open strings played by plucking.'], \
['piston', 'A solid cylinder or disk that fits snugly in a larger cylinder and moves under pressure as in an engine.'], \
['caramel', 'A smooth chery candy made from suger, butter, cream or milk with flavoring.'], \
['coral', 'A rock-like deposit of organism skeletons that make up reefs.'], \
['dawn', 'The time of each morning at which daylight begins.'], \
['pitch', 'A resin derived from the sap of various pine trees.'], \
['fjord', 'A long, narrow, deep inlet of the sea between steep slopes.'], \
['lip', 'Either of two fleshy folds surrounding the mouth.'], \
['lime', 'The egg-shaped citrus fruit having a green coloring and acidic juice.'], \
['mist', 'A mass of fine water droplets in the air near or in contact with the ground.'], \
['plague', 'A widespread affliction or calamity.'], \
['yarn', 'A strand of twisted threads or a long elaborate narrative.'], \
['snicker', 'A snide, slightly stifled laugh.']
    
    a = Crossword(17, 17, '-', 5000, word_list)
    a.compute_crossword(2)
    print (a.word_bank())
    print (a.solution())
    print (a.word_find())
    print (a.display())
    print (a.legend())
    print (len(a.current_word_list), 'из', len(word_list))
    print (a.debug)
    
    ###############################################################################
    
    from tkinter import *
    from tkinter.messagebox import showwarning, showinfo
    
    class CellEntry(Entry):
    
        def __init__(self, master, **kw):
            self._variable = StringVar()
            self._variable.trace("w", self._callback)
            super().__init__(master, textvariable=self._variable, **kw)
    
        def _callback(self, *args):
            value = self._variable.get()
            self._variable.set('' if not value else value[-1])
    
    class App:
        def __init__(self, crossword):
            self.root = Tk()
            self.root.title("Crossword")
            self._crossword = crossword
            self._grid = Frame(self.root)
            self._grid.pack(padx=30, pady=30)
            self._cells = {}
            for col in range(1, crossword.cols + 1):
                for row in range(crossword.rows):
                    c = crossword.get_cell(col, row)
                    if c != '-':
                        entry = CellEntry(self._grid, width=3, justify=CENTER)
                        entry.grid(row=row, column=col)
                        self._cells[(col, row)] = entry
    
            for w in crossword.current_word_list:
                row, col = w.row, w.col
                if w.down_across() == 'down':
                    row -= 1
                else:
                    col -= 1
                Label(self._grid, text=str(w.number)).grid(column=col, row=row)
            Button(self.root, text='Questions', command=self.questions).pack(pady=10)
            Button(self.root, text='Check', command=self.check).pack(pady=10)
    
        def check(self):
            for (col, row), entry in self._cells.items():
                if entry.get() == self._crossword.get_cell(col, row):
                    showinfo('OK!!!')
                    return
    
            '''for (col, row), entry in self._cells.items():
                    if entry.get() != self._crossword.get_cell(col, row):
                       showwarning('...', 'Something went wrong')
                       return
                    else:
                        showinfo('...', 'Ok')'''
    
    
    
    
        def questions(self):
            super().__init__()
            self.label = Label(text=a.legend(),justify=LEFT,font=("Arial Bold", 9))
            self.label.pack(padx=20, pady=20)
    
        def run(self):
            self.root.mainloop()
    
    App(a).run()
1

There are 1 answers

0
Henry Ecker On

There are likely many areas of your code that have unexpected behaviour, and may need refactoring in other places, but to address the specific issue you're citing:

If I enter a single character, the message that everything is OK is not output (as it should be). But if I enter 2 correct characters, then the check says that everything is ok.

Is not quite accurate. Actually, the testing any first letter of any word in your crossword would not trigger the event. The "p" in "p u m p e r n i c k e l" for example. Conversely, a single letter anywhere else in the word will fire your alert. The "m" in "p u m p e r n i c k e l" for example.

The issue lies in your Crossword.display function

def display(self, order=True):
    outStr = ""
    if order:
        self.order_number_words()

    copy = self

    for word in self.current_word_list:
        copy.set_cell(word.col, word.row, word.number)

    for r in range(copy.rows):
        for c in copy.grid[r]:
            outStr += '%s ' % c
        outStr += '\n'

    outStr = re.sub(r'[a-z]', ' ', outStr)
    return outStr

Specifically these lines:

copy = self

for word in self.current_word_list:
    copy.set_cell(word.col, word.row, word.number)

You attempt to make a copy (distinct object the is a duplicate of self) by running copy = self, however, what you've done instead is make an alias of self.

Essentially, copy and self are referring to the same object in memory. For this reason, modifications made to copy are also made to self.

When you call set_cell on copy, you overwrite the first letter of every word in the crossword with the number of its label in self.

For example "p u m p e r n i c k e l" becomes "1 u m p e r n i c k e l"

Due to this error introduced to your data, when you go through and do your comparison, you cannot correctly assess if the value is correct, as the correct value is not present for comparison. This is the reason why your alert does not appear in these cases.

You can use copy.deepcopy to create a distinct copy of self.

def display(self, order=True):
    outStr = ""
    if order:
        self.order_number_words()

    copy = deepcopy(self)

    for word in self.current_word_list:
        copy.set_cell(word.col, word.row, word.number)

    for r in range(copy.rows):
        for c in copy.grid[r]:
            outStr += '%s ' % c
        outStr += '\n'

    outStr = re.sub(r'[a-z]', ' ', outStr)
    return outStr

You will need to update your import statement to include deepcopy:

from copy import copy as duplicate, deepcopy

General note: There is a philosophy known as the Single Responsibility Principle, which essentially recommends that every function take care of a single task and that task alone. While there are many "correct" ways to design programs, it may be helpful to keep this principle in mind as you're working through this problem.