Почему этот генератор кроссвордов не вставляет слова в сетку? - PullRequest
0 голосов
/ 10 ноября 2019

Я пишу программу для создания редких кроссвордов / анаграмм, которые должны выглядеть следующим образом:

-------------T--
-----------OGRE-
---------T---O--
--------FORGET--
---------R------
---------T------

В данном случае это слова FORGET, OGRE, TORTTROT. Правила для дизайна сетки примерно такие, как вы ожидаете;слова должны пересекаться на одной и той же букве, слова не могут проходить рядом друг с другом, и все поставляемые слова должны разделять подмножество некоторого набора букв. Это анаграммы.

Приведенный ниже код реализует сетку и метод для "сканирования" сетки и вставки каждого слова в первую действительную позицию. Первое слово вставлено в приблизительный центр головоломки.

К сожалению, код фактически не вставляет ничего, кроме первого слова, и я не могу понять, что не так с моей жизньюмои проверки в функции word_fits.

import enum
import io
import itertools
import math
import random


@enum.unique
class Direction(enum.Enum):
    ACROSS, DOWN = enum.auto(), enum.auto()

    def __str__(self):
        return self.name

    def get_deltas(self):
        return int(self == Direction.DOWN),  int(self == Direction.ACROSS)

    @staticmethod
    def random():
        return random.choice(list(Direction))


class Grid:
    def __init__(self, height = 16, width = 16):
        self.width = width
        self.height = height
        self.grid = {r: {c: [] for c in range(width)} for r in range(height)}
        self.num_words = 0

    def get_letter(self, r, c):
        if not self.grid[r][c]:
            return []
        letters = {word[offset] for (word, offset, _) in self.grid[r][c]}
        assert(len(letters) == 1)
        return letters.pop()

    def get_words(self, r, c):
        if not self.grid[r][c]:
            return []
        return [word for word, _, _ in self.grid[r][c]]

    def approximate_center(self):
        return math.floor(self.height/ 2), math.floor(self.width/ 2)

    def word_fits(self, word, r, c, direction):
        # Make sure we aren't inserting the word outside the grid
        if ((direction == Direction.DOWN and r + len(word) >= self.height) or (direction == Direction.ACROSS and c + len(word) >= self.width)):
            return False

        # Otherwise we get a KeyError (for being out of bounds) when we check
        # the adjacent cells later in this function
        if r == 0 or c == 0 or r == self.height-1 or c == self.width-1:
            return False

        delta_r, delta_c = direction.get_deltas()

        # Check that the word doesn't overlap any letters incorrectly
        intersects = False
        for offset, letter in enumerate(word):
            rr = r + offset*delta_r
            cc = c + offset*delta_c
            other_letter = self.get_letter(rr, cc)
            if other_letter:
                if letter != other_letter: return False
                else: intersects = True

            # Check adjacent cells
            for delta in [-1, 1]:
                rr = r + offset*delta_r + delta*delta_c
                cc = c + offset*delta_c + delta*delta_r
                if self.grid[rr][cc]:
                    if any(direction == d for _, _, d in self.grid[rr][cc]):
                        return False
#                    if set(self.get_words(r+offset*delta_r, c+offset*delta_c)) & set(self.get_words(rr, cc)):
#                        return False

                if offset == 0:
                    # delta == -1
                    # point directly to the left (above) a word placed across (down)
                    #
                    # delta == 1
                    # point directly to the right (below) a word placed across (down)

                    if delta == -1:
                        rr = r + delta*delta_r
                        cc = c + delta*delta_c
                    elif delta == 1:
                        rr = r + delta*len(word)*delta_r
                        cc = c + delta*len(word)*delta_c

                    if self.grid[rr][cc]:
                        if any(direction == d for _, _, d in self.grid[rr][cc]):
                            return False

        return True and intersects

    def insert_word(self, word, r, c, direction):
        assert(isinstance(direction, Direction))
        delta_r, delta_c = direction.get_deltas()
        for offset, _ in enumerate(word):
           self.grid[r + offset*delta_r][c + offset*delta_c].append((word.upper(), offset, direction))
        self.num_words += 1


    def scan_and_insert_word(self, word):
        if self.num_words == 0:
            self.insert_word(word, *self.approximate_center(), Direction.random())
            return
        for d, r, c in itertools.product(list(Direction), range(self.height), range(self.width)):
            if self.word_fits(word, r, c, d):
                self.insert_word(word, r, c, d)
                break
        raise ValueError(f""""{word}" could not be inserted.""")

    def __str__(self):
        output = io.StringIO()
        for r in range(self.height):
            for c in range(self.width):
                if self.grid[r][c]:
                    # Checks elsewhere ensure that there are no inconsistencies
                    # in the letters specified by each (word, offset, direction)
                    # triplet, so we can just grab the first one
                    word, offset, _ = self.grid[r][c][0]
                    letter = word[offset]
                else:
                    letter = "-"
                output.write(letter)
            output.write("\n")
        contents = output.getvalue()
        output.close()
        return contents


random.seed(1)
word_list = ["FORGET", "TROT", "OGRE", "TORT"]
g = Grid()
for word in word_list:
    g.scan_and_insert_word(word.upper())
    print(g)

Есть много проверок, которые все еще должны быть реализованы в этом коде, например, проверка того, что все слова имеют одно и то же слово из букв N,для некоторых N, но я пытаюсь выяснить эту ошибку, прежде чем двигаться дальше.

1 Ответ

1 голос
/ 10 ноября 2019

Проблема в вашей функции scan_and_insert_word:

Первое слово вставлено без проблем, потому что если self.num_words == 0, вы вставляете слово в центр (приблизительно) и завершаете функцию (ХОРОШО).

Если это не так, вы пытаетесь найти место в сетке, где слово может соответствовать. Как только подходящая позиция найдена, вы вставляете слово и break (вместо return, что приведет к преждевременному завершению функции). Поскольку вы break используете вместо return, все, что вы делаете, - это выходите из цикла, а затем повышаете исключение ValueError, даже если вы нашли совершенно правильное место для нового слова.

Исправление:

Измените break на return. Или сделать это:

def scan_and_insert_word(self, word):
    if self.num_words == 0:
        self.insert_word(word, *self.approximate_center(), Direction.random())
        return
    for d, r, c in itertools.product(list(Direction), range(self.height), range(self.width)):
        if self.word_fits(word, r, c, d):
            self.insert_word(word, r, c, d)
            break
    else:
        raise ValueError(f""""{word}" could not be inserted.""")
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...