Алгоритм для створення кросворду


123

З урахуванням списку слів, як би ви вирішили впорядкувати їх у сітці кросвордів?

Це не повинно бути схожим на "правильну" кросворд, яка є симетричною або що-небудь подібне: в основному просто виведіть початкове положення та напрямок для кожного слова.

Відповіді:


62

Я придумав рішення, яке, мабуть, не найефективніше, але воно працює досить добре. В основному:

  1. Сортуйте всі слова за довжиною, за спаданням.
  2. Візьміть перше слово і покладіть його на дошку.
  3. Візьміть наступне слово.
  4. Перегляньте всі слова, які вже є на дошці, і перевірте, чи є можливі перехрестя (будь-які загальні букви) з цим словом.
  5. Якщо можливе місце для цього слова, переведіть усі слова, які є на дошці, і перевірте, чи заважає нове слово.
  6. Якщо це слово не порушує дошку, помістіть її там і перейдіть до кроку 3, інакше продовжуйте пошук місця (крок 4).
  7. Продовжуйте цю петлю, доки всі слова не будуть розміщені або неможливо розмістити.

Це робить робочий, але часто досить поганий кросворд. Я вніс декілька змін, які я вніс до базового рецепту вище, щоб досягти кращого результату.

  • В кінці генерування кросворду дайте йому бал залежно від того, скільки слів було розміщено (чим більше, тим краще), наскільки велика дошка (чим менша, тим краще) та співвідношення між висотою та шириною (чим ближче до 1, тим краще). Створіть кілька кросвордів, а потім порівняйте їхні оцінки та виберіть найкращий.
    • Замість того, щоб виконувати довільну кількість ітерацій, я вирішив створити якомога більше кросвордів за довільну кількість часу. Якщо у вас є лише невеликий список слів, то ви отримаєте десятки можливих кросвордів за 5 секунд. Великий кросворд можна вибрати лише з 5-6 можливостей.
  • Розміщуючи нове слово, замість того, щоб розміщувати його відразу, знайшовши прийнятне місце, дайте розташуванню цього слова базуючись на те, наскільки він збільшує розмір сітки та кількість перетинів (в ідеалі ви хочете, щоб кожне слово було перекреслені 2-3 іншими словами). Слідкуйте за всіма позиціями та їх балами, а потім виберіть найкращу.

7
Я випадково пишу цю програму, коли ми говоримо, і це той самий альгоротм, який я також вибрав. Для невеликої кількості слів (10 або менше) програма не без проблем обчислює всі можливі рішення в мілісекундах. Але алгоритм є експоненціальним. Найпростіша частина - це написання основного алгоритму, який грубо діє через усі можливі комбінації. Важкою частиною є десяток або близько таких "коротких скорочень", які вам потрібні, щоб програма не могла спробувати всі тупикові рішення.
user31586

2
"5. ... і перевірте, чи заважає нове слово" Як ви враховуєте ситуації, коли нове слово розташоване поряд із наявним словом, яке генерує хитрість у місцях, де воно має сусідні квадрати? Напр .: ЛИМОНОВЕ ПОЛУЧЕННЯ Якщо "LE", "ER" та "MA" тощо не є словами у вашому списку, це неправильно. З іншого боку, прямо відхилення таких суміжностей може викинути справді хороші сітки, як-от: W LEMON ERASE NEXUS TT
George Armhold

4
@Kaffeine, так, я знаю, що ти маєш на увазі - мені довелося викинути ці варіанти, тому що, хоча вони можуть створити справді хороші сітки, це важко перевірити (читайте: я не міг заважати ) , і, швидше за все, це просто втручання .
nickf

4
Створено за допомогою jQuery / Javascript з використанням вищезазначених рекомендацій та декількох моїх власних ... mlewiscs.com/crossword
MLewisCodeSolutions

@MLewisCodeSolutions Виглядає приголомшливо. Ви це відкрите?
GKS

23

Я нещодавно написав своє в Python. Ви можете знайти його тут: http://bryanhelmig.com/python-crossword-puzzle-generator/ . Це не створює щільних кросвордів у стилі Нью-Йорк, але стиль кросвордів, які ви можете знайти у дитячій головоломці.

На відміну від декількох алгоритмів, які я там знайшов, що реалізував випадковий брутальний метод розміщення слів, як кілька запропонованих, я спробував реалізувати трохи розумніший підхід із грубою силою при розміщенні слова. Ось мій процес:

  1. Створіть сітку будь-якого розміру та список слів.
  2. Перемішайте список слів, а потім сортуйте слова за найдовшим та найкоротшим часом.
  3. Помістіть перше і найдовше слово у верхньому лівому верхньому місці, 1,1 (вертикальне або горизонтальне).
  4. Перейдіть до наступного слова, переведіть петлю на кожну букву слова та кожну клітинку в сітці, шукаючи відповідність букви на букву.
  5. Коли відповідність знайдена, просто додайте цю позицію до запропонованого списку координат для цього слова.
  6. Проведіть петлю над запропонованим списком координат і "оцініть" розміщення слів залежно від кількості інших слів, які воно перетинає. Оцінки 0 вказують або на неправильне розміщення (поруч із існуючими словами), або на те, що не було перекреслених слів.
  7. Поверніться до кроку №4, поки список слів не вичерпається. Необов’язковий другий прохід.
  8. Зараз ми повинні мати кросворд, але якість може бути вражена або пропущена через деякі випадкові місця розташування. Отже, ми зберігаємо цей кросворд і повертаємось до кроку №2. Якщо наступний кросворд містить більше слів, розміщених на дошці, він замінює кросворд у буфері. Цей час обмежений (знайти кращу кросворд за x секунд).

Зрештою, у вас є пристойний кросворд або пошук слова, оскільки вони приблизно однакові. Це, як правило, працює досить добре, але повідомте мене, якщо у вас є якісь пропозиції щодо вдосконалення. Більші сітки працюють експоненціально повільніше; більший перелік слів лінійно. Більші списки слів також мають набагато більший шанс на кращу кількість слов.


@Neil N: Мабуть, краща можливість відповідності букв для інших слів. Можливо, також буде підхід до сортування за кількістю різних літер, що містяться в одному слові, що в основному призведе до того ж результату.
Карл Адлер

@NeilN Python's array.sort(key=f)стабільний, що означає (наприклад), що просто сортування алфавітного списку слів за довжиною зберігало б усі 8-літерні слова в алфавітному порядку.
Лінн

4
@Bryan, посилання на ваш веб-сайт для мене не працює, і основний домен просто перенаправляє на Twitter. Чи є у вас оновлене посилання на ваш код?
Майкл A

2
Ось (мабуть) клон генератора Брайана: github.com/jeremy886/crossword_helmig
lvictorino

20

Я фактично написав програму генерування кросвордів близько десяти років тому (це було виразно, але ті ж правила застосовуватимуться і до звичайних кросвордів).

У ньому був список слів (та пов’язаних із ними підказок), що зберігаються у файлі, відсортованому за убуванням до цього часу (так, що слова, які використовуються менше, були у верхній частині файлу). Шаблон, в основному, бітова маска, що представляє чорні та вільні квадрати, вибирався випадковим чином із пулу, який надав клієнт.

Потім для кожного неповного слова в головоломці (в основному знайдіть перший порожній квадрат і подивіться, чи є той, що праворуч (через слово) або той, що знаходиться внизу (вниз), також порожній), було проведено пошук файл шукає перше слово, яке підходить, з урахуванням букв, які вже є в цьому слові. Якщо не було слова, яке могло б підійти, ви просто позначили це слово як неповне та продовжували рух.

Зрештою, буде кілька незавершених слів, які компілятор повинен був би заповнити (та додати слово та підказку до файлу за бажанням). Якщо вони не змогли придумати жодних ідей, вони могли б відредагувати кросворд вручну, щоб змінити обмеження або просто попросити загальну регенерацію.

Після того, як файл слова / підказки набув певного розміру (і він додавав 50-100 підказок на день для цього клієнта), рідко траплялося більше двох-трьох ручних виправлень, які потрібно було зробити для кожної кросворду .


Це насправді не допомагає мені в моїй ситуації, оскільки у мене буде список з приблизно 6-12 слів. Моє скоріше нагадує навчальну вправу для користувача, ніж словесну головоломку. +1 все одно за цікавий алгоритм!
nickf

1
Гарний опис. Я думав про це кілька разів у минулому, але ніколи не пробував цього. Тепер для магічного запитання: наскільки добре це спрацювало? Просто для рідких головоломок, а також для щільних (як у папері)? А скільки доказів вам потрібно для щільних пазлів?
dmckee --- кошеня колишнього модератора

1
@dmckee, це було деякий час тому, але з пам’яті навіть щільні головоломки були досить гарними. Багато було завершено без втручання, але ви все одно отримаєте, можливо, п’яту, що вимагає додавання ще одного або двох слів. І ми говоримо про тисячі слів у файлі. Безперечно, зворотний трек міг би допомогти, але клієнту було легше відхилити одне (напр.) 5 незакінчених слів, ніж хвилюватися, намагаючись придумати зайві підказки. П'ять про зовнішню межу, яку я бачив за незакінчені слова.
paxdiablo

16

Цей алгоритм створює 50 щільних кросвордів 6х9 стрілок за 60 секунд. Він використовує базу даних слів (із словом + підказки) та базу даних плати (із попередньо налаштованими дошками).

1) Search for all starting cells (the ones with an arrow), store their size and directions
2) Loop through all starting cells
2.1) Search a word
2.1.1) Check if it was not already used
2.1.2) Check if it fits
2.2) Add the word to the board
3) Check if all cells were filled

Більша база даних слів значно скорочує час генерації, а деякі дошки важче заповнити! Більш великі дошки потребують більше часу, щоб правильно заповнити!


Приклад:

Попередньо налаштована плата 6x9:

(# означає одна підказка в одній клітинці,% означає дві підказки в одній клітині, стрілки не показані)

# - # # - % # - # 
- - - - - - - - - 
# - - - - - # - - 
% - - # - # - - - 
% - - - - - % - - 
- - - - - - - - - 

Створена рада 6x9:

# C # # P % # O # 
S A T E L L I T E 
# N I N E S # T A 
% A B # A # G A S 
% D E N S E % W E 
C A T H E D R A L 

Поради [рядок, стовпець]:

[1,0] SATELLITE: Used for weather forecast
[5,0] CATHEDRAL: The principal church of a city
[0,1] CANADA: Country on USA's northern border
[0,4] PLEASE: A polite way to ask things
[0,7] OTTAWA: Canada's capital
[1,2] TIBET: Dalai Lama's region
[1,8] EASEL: A tripod used to put a painting
[2,1] NINES: Dressed up to (?)
[4,1] DENSE: Thick; impenetrable
[3,6] GAS: Type of fuel
[1,5] LS: Lori Singer, american actress
[2,7] TA: Teaching assistant (abbr.)
[3,1] AB: A blood type
[4,3] NH: New Hampshire (abbr.)
[4,5] ED: (?) Harris, american actor
[4,7] WE: The first person of plural (Grammar)

11

Хоча це питання старіше, спробую відповісти на основі аналогічної роботи, яку я зробив.

Існує багато підходів до вирішення обмежувальних задач (які взагалі належать до класу складності NPC).

Це пов'язано з комбінаторною оптимізацією та програмуванням обмежень. У цьому випадку обмеженнями є геометрія сітки та вимога, щоб слова були унікальними тощо.

Підходи до рандомізації / відпалу також можуть працювати (хоча і в належних умовах).

Ефективна простота може бути просто найвищою мудрістю!

Вимоги були до більш-менш повного компілятора кросвордів та (візуального WYSIWYG).

Залишаючи осторонь частину будівельника WYSIWYG, конфігуратор компілятора був таким:

  1. Завантажте доступні списки слів (відсортовано за довжиною слова, тобто 2,3, .., 20)

  2. Знайдіть наборі слів (тобто сітки) на створеній користувачем сітці (наприклад, слово в x, y довжиною L, горизонтальне або вертикальне) (складність O (N))

  3. Обчисліть точки перетину слів сітки (які потрібно заповнити) (складність O (N ^ 2))

  4. Обчисліть перетини слів у списках слів за допомогою різних букв алфавіту, що використовується (це дозволяє шукати відповідні слова за допомогою шаблону, наприклад. Теза Сік-Камбона як використовується cwc ) (складність O (WL * AL))

Кроки .3 та .4 дозволяють виконати це завдання:

а. Перетини срібних слів дозволяють створити "шаблон" для спроби пошуку збігів у пов'язаному списку слів доступних слів для цього сіткового слова (за допомогою букв інших слів, що перетинаються із цим словом, які вже заповнені певним крок алгоритму)

б. Перетин слів у списку слів із алфавітом дозволяє знайти відповідні (кандидати) слова, які відповідають заданому "шаблону" (наприклад, "A" на 1-му місці та "B" на 3-му місці тощо).

Тож із цими структурами даних реалізований алгоритм виглядав так:

ПРИМІТКА: якщо сітка та база даних слів є постійними, попередні кроки можна зробити лише один раз.

  1. Першим кроком алгоритму є вибір порожнього словника (слово з сітки) навмання та заповнення його кандидатурним словом із його асоційованого списку слів (рандомізація дає змогу створювати різні солютони в послідовних виконання алгоритму) (складність O (1) або O ( N))

  2. Для кожного ще порожнього слота слова (який має перехрестя з уже заповненими слотами), обчисліть коефіцієнт обмеження (це може змінюватись, що просто на кількість доступних рішень на цьому кроці) та сортуйте порожні слова, за цим співвідношенням (складність O (складність NlogN ) або O (N))

  3. Проведіть цикл порожніх наборів слів, обчислених на попередньому кроці, і для кожного спробуйте декілька рішень канкдидату (переконайтесь, що "дуга-консистенція збережена", тобто сітка має рішення після цього кроку, якщо це слово використовується) та сортуйте їх відповідно до максимальна доступність для наступного кроку (тобто наступний крок має максимально можливі рішення, якщо це слово в цей час використовується в цьому місці тощо) (складність O (N * MaxCandidatesUsed))

  4. Заповніть це слово (позначте його як заповнене та перейдіть до кроку 2)

  5. Якщо не знайдено жодного слова, яке б відповідало критеріям кроку .3, спробуйте повернутись до іншого рішення кандидата якогось попереднього кроку (критерії можуть змінюватись тут) (складність O (N))

  6. Якщо зворотний трек знайдений, використовуйте альтернативну функцію та необов’язково скиньте всі вже заповнені слова, які можуть знадобитися скинути (позначте їх як знову незаповнені) (складність O (N))

  7. Якщо зворотний шлях не знайдено, рішення не може бути знайдено (принаймні, з цією конфігурацією, початковим насінням тощо)

  8. Інакше, коли всі слова слова заповнені, у вас є одне рішення

Цей алгоритм робить випадковим послідовним кроком дерево рішення проблеми. Якщо в якийсь момент є глухий кут, він робить зворотній шлях до попереднього вузла і слідує іншому маршруту. Доки не знайдене рішення або кількість кандидатів у різних вузлах не вичерпано.

Частина узгодженості гарантує, що знайдене рішення справді є рішенням, а випадкова частина дає змогу виробляти різні рішення в різних виконаннях, а також в середньому мати кращі показники.

PS. все це (та інші) були реалізовані у чистому JavaScript (з паралельною обробкою та WYSIWYG)

PS2. Алгоритм можна легко паралелізувати, щоб отримати одночасно більше (різних) рішення

Сподіваюся, це допомагає


1
Це для створення щільних макетів (наприклад, NY Times) або розріджених макетів?
Джим

1
@Jim, це в основному для щільних макетів, але їх можна налаштувати і для рідких. Різниця полягає у щільних макетах (наприклад, класичний, скандинавік тощо). Є одна сітка та пошук слів, тоді як для макетів вільної форми (рідкісних) є слова та шукає сітку.
Нікос М.

1
Чи трапляється вам, щоб десь було доступне джерело зразка, яке реалізує дії, описані вище. Наприклад, я з вами більшу частину цього (і вже реалізував більшу частину самостійно), але коли мова йде про "обчислення коефіцієнта обмеження ...", я мушу визнати, що ви мене втратили. Пошук у Інтернеті за такими речами, як "STH Ratio", теж не допомагає мені. Проблема з моєю реалізацією намагається знайти слова, які потрібно заповнити, дуже неефективна і забирає занадто довго.
Джим

1
@ Jim, у мене, як це вже використовується, але це було зроблено специфічно для роботи, яку я мав, можливо, я опублікую легку версію на своїх проектах з відкритим кодом, якщо вам потрібна додаткова допомога, зв’яжіться зі мною (ps дійсно на у деяких випадках алгоритм, який я розміщував, може зайняти занадто багато часу, але в середньому це не так)
Нікос М.

1
@ Jim, подивіться на цей сайт кросвордів (ще триває) istavrolexo.gr (грецькою мовою) з різними (щільними) кросвордами (тобто скандинавіком, класикою, судоку), які були створені за аналогічним алгоритмом ( великий скандинавічний кросворд )
Нікос М.

9

Чому б просто не використати випадковий імовірнісний підхід для початку. Почніть зі слова, а потім кілька разів вибирайте випадкове слово і намагайтеся вписати його у поточний стан головоломки, не порушуючи обмежень щодо розміру тощо. Якщо ви не вдається, просто починайте все спочатку.

Ви здивуєтеся, як часто такий підхід Монте-Карло працює.


2
так, саме такий підхід я обрав. Не потрібно намагатися бути дуже розумним. Упорядкуйте слова від найдовшого до найкоротшого. У циклі виберіть випадкову клітинку (координату стовпців та рядків) і поставте слово на тестування на дошці, щоб побачити, чи воно закінчується чи втручається в інше слово (перед тим, як записати слово в сітку, перевірте, чи кожна клітина є або порожній або якщо у нього є лист, цей лист відповідає тій букві, яку ви намагаєтеся написати). Існує якась інша логіка, щоб перевірити межі та інше. Я грубою силою генерую купу все менших та менших сіток, а потім розміщую найменший на основі слів, що перетинаються.
Макс Ходжес

6

Ось код JavaScript на основі відповіді nickf та коду Брайана Python. Просто опублікуйте його, якщо хтось інший потребує його в js.

function board(cols, rows) { //instantiator object for making gameboards
this.cols = cols;
this.rows = rows;
var activeWordList = []; //keeps array of words actually placed in board
var acrossCount = 0;
var downCount = 0;

var grid = new Array(cols); //create 2 dimensional array for letter grid
for (var i = 0; i < rows; i++) {
    grid[i] = new Array(rows);
}

for (var x = 0; x < cols; x++) {
    for (var y = 0; y < rows; y++) {
        grid[x][y] = {};
        grid[x][y].targetChar = EMPTYCHAR; //target character, hidden
        grid[x][y].indexDisplay = ''; //used to display index number of word start
        grid[x][y].value = '-'; //actual current letter shown on board
    }
}

function suggestCoords(word) { //search for potential cross placement locations
    var c = '';
    coordCount = [];
    coordCount = 0;
    for (i = 0; i < word.length; i++) { //cycle through each character of the word
        for (x = 0; x < GRID_HEIGHT; x++) {
            for (y = 0; y < GRID_WIDTH; y++) {
                c = word[i];
                if (grid[x][y].targetChar == c) { //check for letter match in cell
                    if (x - i + 1> 0 && x - i + word.length-1 < GRID_HEIGHT) { //would fit vertically?
                        coordList[coordCount] = {};
                        coordList[coordCount].x = x - i;
                        coordList[coordCount].y = y;
                        coordList[coordCount].score = 0;
                        coordList[coordCount].vertical = true;
                        coordCount++;
                    }

                    if (y - i + 1 > 0 && y - i + word.length-1 < GRID_WIDTH) { //would fit horizontally?
                        coordList[coordCount] = {};
                        coordList[coordCount].x = x;
                        coordList[coordCount].y = y - i;
                        coordList[coordCount].score = 0;
                        coordList[coordCount].vertical = false;
                        coordCount++;
                    }
                }
            }
        }
    }
}

function checkFitScore(word, x, y, vertical) {
    var fitScore = 1; //default is 1, 2+ has crosses, 0 is invalid due to collision

    if (vertical) { //vertical checking
        for (i = 0; i < word.length; i++) {
            if (i == 0 && x > 0) { //check for empty space preceeding first character of word if not on edge
                if (grid[x - 1][y].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            } else if (i == word.length && x < GRID_HEIGHT) { //check for empty space after last character of word if not on edge
                 if (grid[x+i+1][y].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            }
            if (x + i < GRID_HEIGHT) {
                if (grid[x + i][y].targetChar == word[i]) { //letter match - aka cross point
                    fitScore += 1;
                } else if (grid[x + i][y].targetChar != EMPTYCHAR) { //letter doesn't match and it isn't empty so there is a collision
                    fitScore = 0;
                    break;
                } else { //verify that there aren't letters on either side of placement if it isn't a crosspoint
                    if (y < GRID_WIDTH - 1) { //check right side if it isn't on the edge
                        if (grid[x + i][y + 1].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                    if (y > 0) { //check left side if it isn't on the edge
                        if (grid[x + i][y - 1].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                }
            }

        }

    } else { //horizontal checking
        for (i = 0; i < word.length; i++) {
            if (i == 0 && y > 0) { //check for empty space preceeding first character of word if not on edge
                if (grid[x][y-1].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            } else if (i == word.length - 1 && y + i < GRID_WIDTH -1) { //check for empty space after last character of word if not on edge
                if (grid[x][y + i + 1].targetChar != EMPTYCHAR) { //adjacent letter collision
                    fitScore = 0;
                    break;
                }
            }
            if (y + i < GRID_WIDTH) {
                if (grid[x][y + i].targetChar == word[i]) { //letter match - aka cross point
                    fitScore += 1;
                } else if (grid[x][y + i].targetChar != EMPTYCHAR) { //letter doesn't match and it isn't empty so there is a collision
                    fitScore = 0;
                    break;
                } else { //verify that there aren't letters on either side of placement if it isn't a crosspoint
                    if (x < GRID_HEIGHT) { //check top side if it isn't on the edge
                        if (grid[x + 1][y + i].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                    if (x > 0) { //check bottom side if it isn't on the edge
                        if (grid[x - 1][y + i].targetChar != EMPTYCHAR) { //adjacent letter collision
                            fitScore = 0;
                            break;
                        }
                    }
                }
            }

        }
    }

    return fitScore;
}

function placeWord(word, clue, x, y, vertical) { //places a new active word on the board

    var wordPlaced = false;

    if (vertical) {
        if (word.length + x < GRID_HEIGHT) {
            for (i = 0; i < word.length; i++) {
                grid[x + i][y].targetChar = word[i];
            }
            wordPlaced = true;
        }
    } else {
        if (word.length + y < GRID_WIDTH) {
            for (i = 0; i < word.length; i++) {
                grid[x][y + i].targetChar = word[i];
            }
            wordPlaced = true;
        }
    }

    if (wordPlaced) {
        var currentIndex = activeWordList.length;
        activeWordList[currentIndex] = {};
        activeWordList[currentIndex].word = word;
        activeWordList[currentIndex].clue = clue;
        activeWordList[currentIndex].x = x;
        activeWordList[currentIndex].y = y;
        activeWordList[currentIndex].vertical = vertical;

        if (activeWordList[currentIndex].vertical) {
            downCount++;
            activeWordList[currentIndex].number = downCount;
        } else {
            acrossCount++;
            activeWordList[currentIndex].number = acrossCount;
        }
    }

}

function isActiveWord(word) {
    if (activeWordList.length > 0) {
        for (var w = 0; w < activeWordList.length; w++) {
            if (word == activeWordList[w].word) {
                //console.log(word + ' in activeWordList');
                return true;
            }
        }
    }
    return false;
}

this.displayGrid = function displayGrid() {

    var rowStr = "";
    for (var x = 0; x < cols; x++) {

        for (var y = 0; y < rows; y++) {
            rowStr += "<td>" + grid[x][y].targetChar + "</td>";
        }
        $('#tempTable').append("<tr>" + rowStr + "</tr>");
        rowStr = "";

    }
    console.log('across ' + acrossCount);
    console.log('down ' + downCount);
}

//for each word in the source array we test where it can fit on the board and then test those locations for validity against other already placed words
this.generateBoard = function generateBoard(seed = 0) {

    var bestScoreIndex = 0;
    var top = 0;
    var fitScore = 0;
    var startTime;

    //manually place the longest word horizontally at 0,0, try others if the generated board is too weak
    placeWord(wordArray[seed].word, wordArray[seed].displayWord, wordArray[seed].clue, 0, 0, false);

    //attempt to fill the rest of the board 
    for (var iy = 0; iy < FIT_ATTEMPTS; iy++) { //usually 2 times is enough for max fill potential
        for (var ix = 1; ix < wordArray.length; ix++) {
            if (!isActiveWord(wordArray[ix].word)) { //only add if not already in the active word list
                topScore = 0;
                bestScoreIndex = 0;

                suggestCoords(wordArray[ix].word); //fills coordList and coordCount
                coordList = shuffleArray(coordList); //adds some randomization

                if (coordList[0]) {
                    for (c = 0; c < coordList.length; c++) { //get the best fit score from the list of possible valid coordinates
                        fitScore = checkFitScore(wordArray[ix].word, coordList[c].x, coordList[c].y, coordList[c].vertical);
                        if (fitScore > topScore) {
                            topScore = fitScore;
                            bestScoreIndex = c;
                        }
                    }
                }

                if (topScore > 1) { //only place a word if it has a fitscore of 2 or higher

                    placeWord(wordArray[ix].word, wordArray[ix].clue, coordList[bestScoreIndex].x, coordList[bestScoreIndex].y, coordList[bestScoreIndex].vertical);
                }
            }

        }
    }
    if(activeWordList.length < wordArray.length/2) { //regenerate board if if less than half the words were placed
        seed++;
        generateBoard(seed);
    }
}
}
function seedBoard() {
    gameboard = new board(GRID_WIDTH, GRID_HEIGHT);
    gameboard.generateBoard();
    gameboard.displayGrid();
}

Схема об’єкта слова відсутня, введіть wordArray
тобто

Буквально просто масив слів, таких як ['яблуко', 'апельсин', 'груша']
FascistDonut

Привіт, FYI моя редакція не змінила багато коду, вона просто відформатувала. Я знаю, що це виглядає так безладно, коли ви переглядаєте його "inline", але якщо ви хочете побачити реальні зміни в коді, натисніть "side-by-side-markdown". Ну ... я повинен був написати "Форматований код" в описі редагування, але мех.
подвійний звуковий сигнал

Як це працює? Чи можете ви надати HTML-файл, що містить цей javascript?
GKS

5

Я б генерував два числа: Оцінка довжини та скребла. Припустимо, що низький рахунок Scrabble означає, що приєднатися до нього простіше (низькі бали = багато загальних букв). Сортуйте список за довжиною за зменшенням та оцінкою Scrabble за зростанням.

Далі просто перейдіть за списком. Якщо слово не перетинається з наявним словом (порівняйте кожне слово за його довжиною та оцінкою Scrabble відповідно), тоді поставте його у чергу та перевірте наступне слово.

Промийте та повторіть, і це повинно створити кросворд.

Звичайно, я майже впевнений, що це O (n!), І кросворд для вас не гарантовано, але, можливо, хтось може його вдосконалити.


3

Я думав над цією проблемою. Я розумію, що для створення по-справжньому щільного кросворду ви не можете сподіватися, що вашого обмеженого списку слів буде достатньо. Тому ви можете взяти словник і помістити його в структуру даних "трие". Це дозволить вам легко знайти слова, які заповнюють ліві місця пробілами. У трійці досить ефективно здійснити обхід, який, скажімо, дає всі слова форми "c? T".

Отже, моє загальне мислення таке: створити якийсь підхід відносно грубої сили, як описаний тут, щоб створити хрест із низькою щільністю та заповнити пробіли словниковими словами.

Якщо хтось застосував такий підхід, будь ласка, дайте мені знати.


3

Я грав навколо двигуна генератора кросвордів, і я вважав це найважливішим:

0.!/usr/bin/python

  1. а. allwords.sort(key=len, reverse=True)

    б. зробіть якийсь предмет / об’єкт, наприклад курсор, який буде ходити навколо матриці для легкої орієнтації, якщо згодом ви не хочете повторити випадковим вибором.

  2. першу, підберіть першу пару і поставте їх поперек і вниз від 0,0; збережіть перший як наш поточний кросворд «лідер».

  3. перемістити курсор за порядком діагоналі або випадково з більшою ймовірністю діагоналі до наступної порожньої комірки

  4. повторіть слова типу і використовуйте довжину вільного простору для визначення максимальної довжини слова: temp=[] for w_size in range( len( w_space ), 2, -1 ) : # t for w in [ word for word in allwords if len(word) == w_size ] : # if w not in temp and putTheWord( w, w_space ) : # temp.append( w )

  5. для порівняння слова з вільним простором, який я використав, тобто:

    w_space=['c','.','a','.','.','.'] # whereas dots are blank cells
    
    # CONVERT MULTIPLE '.' INTO '.*' FOR REGEX
    
    pattern = r''.join( [ x.letter for x in w_space ] )
    pattern = pattern.strip('.') +'.*' if pattern[-1] == '.' else pattern
    
    prog = re.compile( pattern, re.U | re.I )
    
    if prog.match( w ) :
        #
        if prog.match( w ).group() == w :
            #
            return True
    
  6. після кожного успішно вживаного слова змінюйте напрямок. Цикл, поки всі клітинки заповнені АБО у вас не вистачає слів АБО обмеженням ітерацій:

# CHANGE ALL WORDS LIST inexOf1stWord = allwords.index( leading_w ) allwords = allwords[:inexOf1stWord+1][:] + allwords[inexOf1stWord+1:][:]

... і повторіть знову новий кросворд.

  1. Створіть систему балів за легкістю заповнення та деякими калькуляціями. Дайте бал за поточний кросворд і звужте пізній вибір, додавши його до списку зроблених кросвордів, якщо бал буде задоволений вашою системою балів.

  2. Після першого ітераційного сеансу повторіть перелік зі списку зроблених кросвордів, щоб закінчити завдання.

Використовуючи більше параметрів, швидкість може бути покращена величезним фактором.


2

Я отримав би індекс кожної літери, що використовується кожним словом, щоб знати можливі хрести. Тоді я б вибрав найбільше слово і використав би його як базу. Виберіть наступний великий і перекресліть його. Промийте і повторіть. Це, мабуть, проблема НП.

Ще одна ідея - створення генетичного алгоритму, де показник сили - це скільки слів, які ви можете помістити в сітку.

Найважча частина, яку я знаходжу, - це коли знати певний список, можливо, не можна перекреслити.


1
Я також думав про генетичний алгоритм. Фітнес-функція може бути настільки щільно, як слова упаковані в сітку.
Адріан Маккарті

2

Цей з'являється як проект у курсі AI CS50 з Гарварду. Ідея полягає в тому, щоб сформулювати проблему створення кросвордів як проблему задоволення обмежень і вирішити її за допомогою зворотного відстеження з різною евристикою, щоб зменшити простір пошуку.

Для початку нам потрібно пара вхідних файлів:

  1. Структура кросворду (виглядає як наступна, наприклад, де "#" представляє символи, які не повинні бути заповнені, а "_" - символи, які слід заповнити)

`

###_####_#
____####_#
_##_#_____
_##_#_##_#
______####
#_###_####
#_##______
#_###_##_#
_____###_#
#_######_#
##_______#    

`

  1. Вхідний словник (список слів / словник), з якого будуть обрані слова-кандидати (як показано нижче).

    a abandon ability able abortion about above abroad absence absolute absolutely ...

Тепер ДСП визначається і вирішується так:

  1. Змінні визначаються як такі, що мають значення (тобто їхні домени) зі списку слів (словникового запасу), поданих у якості вхідних даних.
  2. Кожна змінна представлена ​​трьома кортежами: (grid_coordinate, напрям, довжина), де координата являє собою початок відповідного слова, напрямок може бути або горизонтальним, або вертикальним, а довжина визначається як довжина слова. присвоєно.
  3. Обмеження визначаються введеною структурою: наприклад, якщо горизонтальна та вертикальна змінна має загальний характер, вона буде представлена ​​як обмеження перекриття (дуги).
  4. Тепер алгоритми консистенції дуги та алгоритми послідовності AC3 можна використовувати для зменшення доменів.
  5. Потім зворотний трекінг для отримання рішення (якщо такий існує) на CSP з MRV (мінімальне залишкове значення), ступінь і т.д. впорядкування, щоб зробити алгоритм пошуку швидшим.

Далі показано результат, отриманий за допомогою реалізації алгоритму вирішення CSP:

`
███S████D█
MUCH████E█
E██A█AGENT
S██R█N██Y█
SUPPLY████
█N███O████
█I██INSIDE
█Q███E██A█
SUGAR███N█
█E██████C█
██OFFENSE█

`

Наступна анімація показує етапи зворотного відстеження:

введіть тут опис зображення

Ось ще один зі списком слів на мові Bangla (бенгальська):

введіть тут опис зображення


+1 для дійсно цікавого пояснення. Однак контекст проблеми, яку я намагався вирішити тут, полягав у тому, що був невеликий набір слів, який усі повинні використовувати, і я намагався знайти оптимальний макет для кросворду, а не починати з макету та знаходити слова які підходять.
nickf

1

Генератор головоломки та ігри jQuery

Я зашифрував рішення цієї проблеми в JavaScript / jQuery:

Приклад демонстрації: http://www.earthfluent.com/crossword-puzzle-demo.html

Вихідний код: https://github.com/HoldOffHunger/jquery-crossword-puzzle-generator

Завдання алгоритму, який я використав:

  1. Максимально зменшіть кількість непридатних квадратів у сітці.
  2. Майте якомога більше змішаних слів.
  3. Обчислити в надзвичайно швидкий час.

Демонстрація створеної кросворду.

Я опишу використаний алгоритм:

  1. Згрупуйте слова разом за тими, що мають спільну букву.

  2. З цих груп побудуйте набори нової структури даних ("слова слова"), що є первинним словом (яке проходить через усі інші слова), а потім іншими словами (які проходять через первинне слово).

  3. Починайте кросворд із самого першого з цих блоків слів у самому верхньому лівому місці кросворду.

  4. Для решти блоків слів, починаючи з правої нижньої частини більшості кросвордів, рухайтесь вгору та вліво, поки не буде доступних проміжків для заповнення. Якщо вгору більше порожніх стовпців, ніж ліворуч, рухайтеся вгору і навпаки.


@holdoffhunger У вас є спосіб показати ключ кросворду? Коробки з заповненими літерами?
Джон Глейзер

@Jon Glazer: Зазвичай ви надсилаєте кросворди до самої функції, але ви можете записувати кросворд у вигляді 2d-масиву символів відразу після var crosswords = generateCrosswordBlockSources(puzzlewords);. Просто консоліруйте це значення. Не забувайте, що в грі є "режим чіт", де ви можете просто натиснути "Розкрити відповідь", щоб отримати значення негайно.
HoldOffHunger

Це створює головоломки з химерними словами "впоперек" у місцях із сусідніми полями "вниз" і навпаки. Стандартні кросворди не працюють так, хоча це робить максимальну щільність.
Beejor
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.