Bulmaca oluşturmak için algoritma


123

Bir kelime listesi verildiğinde, bunları bir bulmaca tablosuna yerleştirmeye ne dersiniz?

Simetrik veya buna benzer "uygun" bir bulmaca gibi olmak zorunda değildir: temelde her kelime için bir başlangıç ​​konumu ve yönü verin.

Yanıtlar:


62

Muhtemelen en verimli olmayan bir çözüm buldum, ancak yeterince iyi çalışıyor. Temelde:

  1. Tüm kelimeleri azalan uzunluğa göre sıralayın.
  2. İlk kelimeyi alın ve tahtaya yerleştirin.
  3. Sonraki kelimeye geçin.
  4. Tahtada bulunan tüm kelimeleri arayın ve bu kelime ile herhangi bir olası kesişim (ortak harfler) olup olmadığına bakın.
  5. Bu kelime için olası bir yer varsa, tahtadaki tüm kelimeler arasında döngü yapın ve yeni kelimenin karışıp karışmadığını kontrol edin.
  6. Bu kelime tahtayı kırmazsa, onu oraya yerleştirin ve 3. adıma gidin, aksi halde bir yer aramaya devam edin (4. adım).
  7. Tüm sözcükler yerleştirilene veya yerleştirilemeyene kadar bu döngüye devam edin.

Bu, işe yarayan ama çoğu zaman oldukça zayıf bir bulmaca yapar. Daha iyi bir sonuç elde etmek için yukarıdaki temel tarifte yaptığım bir takım değişiklikler oldu.

  • Bir bulmaca oluşturmanın sonunda, kelimelerin kaç tanesinin yerleştirildiğine (ne kadar iyi), tahtanın ne kadar büyük olduğuna (ne kadar küçükse o kadar iyi) ve yükseklik ile genişlik arasındaki orana (ne kadar yakınsa 1'e kadar daha iyi). Bir dizi bulmaca oluşturun ve ardından puanlarını karşılaştırın ve en iyisini seçin.
    • Rasgele sayıda yineleme çalıştırmak yerine, keyfi bir süre içinde olabildiğince çok bulmaca oluşturmaya karar verdim. Yalnızca küçük bir kelime listeniz varsa, 5 saniye içinde düzinelerce olası bulmaca alırsınız. Daha büyük bir bulmaca, yalnızca 5-6 olasılık arasından seçilebilir.
  • Yeni bir kelime yerleştirirken, kabul edilebilir bir konum bulduktan hemen sonra yerleştirmek yerine, o kelime konumuna, ızgaranın boyutunu ne kadar artırdığına ve kaç kesişim bulunduğuna bağlı olarak bir puan verin (ideal olarak her kelimenin olmasını istersiniz 2-3 başka kelime ile geçti). Tüm pozisyonları ve puanlarını takip edin ve ardından en iyisini seçin.

7
Bu programı konuşurken ben de yazıyorum ve bu benim de seçtiğim aynı algoritma. Az sayıda kelime için (10 veya daha az), program tüm olası çözümleri milisaniye cinsinden hesaplarken hiçbir sorun yaşamaz. Algoritma üstel olsa da. İşin kolay kısmı, tüm olası kombinasyonlar boyunca kaba kuvvet uygulayan temel algoritmayı yazmaktır. Zor kısım, programın tüm çıkmaz çözümleri denemesini engellemek için ihtiyaç duyduğunuz düzinelerce 'kısa yol'.
user31586

2
"5. ... ve yeni kelimenin müdahale edip etmediğini kontrol edin" Yeni kelimenin mevcut bir kelimenin yanında yer aldığı ve bitişik karelerin olduğu yerlerde anlamsız sözler oluşturan durumları nasıl açıklarsınız? Örneğin: LEMON ERASE "LE", "ER" ve "MA" vb. Listenizdeki kelimeler değilse, bu yanlıştır. Öte yandan, bu tür bitişiklikleri doğrudan reddetmek, gerçekten iyi ızgaraları
ortadan kaldırabilir

4
@Kaffeine, evet, ne demek istediğini anlıyorum - Bu seçenekleri atmak zorunda kaldım çünkü gerçekten iyi ızgaralar oluştursalar bile, kontrol etmek çok zor (okuyun: Rahatsız edemedim) ve muhtemelen sadece bir müdahale .
nickf

4
JQuery / Javascript ile, yukarıdaki öneriler ve benim birkaç önerim kullanılarak oluşturuldu
MLewisCodeSolutions

@MLewisCodeSolutions Harika görünüyor. Bunu açık kaynak mı yapıyorsunuz?
GKS

23

Kısa süre önce Python'da kendim yazdım. Burada bulabilirsiniz: http://bryanhelmig.com/python-crossword-puzzle-generator/ . NYT tarzı yoğun bulmacaları yaratmaz, ancak bir çocuğun bulmaca kitabında bulabileceğiniz bulmaca stilini yaratır.

Birkaç algoritmanın önerdiği gibi kelimeleri yerleştirmek için rastgele bir kaba kuvvet yöntemini uygulayan birkaç algoritmanın aksine, kelime yerleştirmede biraz daha akıllı bir kaba kuvvet yaklaşımı uygulamaya çalıştım. İşte benim sürecim:

  1. Hangi boyutta olursa olsun bir ızgara ve bir kelime listesi oluşturun.
  2. Kelime listesini karıştırın ve ardından kelimeleri en uzundan en kısaya sıralayın.
  3. İlk ve en uzun kelimeyi en sol üst konuma 1,1 (dikey veya yatay) yerleştirin.
  4. Bir sonraki kelimeye geçin, kelimedeki her harfin ve ızgaradaki her bir hücrenin harf-harf eşleşmelerini arayın.
  5. Bir eşleşme bulunduğunda, o konumu o kelime için önerilen koordinat listesine eklemeniz yeterlidir.
  6. Önerilen koordinat listesi üzerinde döngü yapın ve kelime yerleşimini, geçtiği diğer kelimelerin sayısına göre "puanlayın". 0 puanları ya kötü yerleşimi (mevcut kelimelerin yanında) ya da hiçbir kelime çarpı işareti olmadığını gösterir.
  7. Kelime listesi bitene kadar 4. adıma geri dönün. İsteğe bağlı ikinci geçiş.
  8. Şimdi bir bulmacaya sahip olmalıyız, ancak bazı rastgele yerleşimler nedeniyle kalite vurulabilir veya gözden kaçabilir. Bu yüzden, bu bulmacayı ara belleğe alıp 2. adıma geri dönüyoruz. Bir sonraki bulmaca tahtaya yerleştirilmiş daha fazla kelime varsa, ara bellekteki bulmacanın yerini alır. Bu zaman sınırlıdır (en iyi bulmacayı x saniyede bulun).

Sonunda, hemen hemen aynı oldukları için düzgün bir bulmaca veya kelime arama bulmacanız var. Oldukça iyi çalışma eğilimindedir, ancak iyileştirme konusunda herhangi bir öneriniz varsa bana bildirin. Daha büyük ızgaralar katlanarak daha yavaş çalışır; doğrusal olarak daha büyük kelime listeleri. Daha büyük kelime listelerinin daha iyi kelime yerleştirme sayıları elde etme şansı çok daha yüksektir.


@Neil N: Muhtemelen diğer kelimeler için daha iyi bir harf eşleştirme imkanı. Belki de kelime başına bulunan farklı harflerin sayısına göre sıralama yapmak için bir yaklaşım olabilir, bu da çoğunlukla aynı sonuca yol açacaktır.
Karl Adler

@NeilN Python array.sort(key=f)kararlıdır, bu da (örneğin) alfabetik bir kelime listesini uzunluğa göre sıralamak, tüm 8 harfli kelimeleri alfabetik olarak sıralı halde tutacağı anlamına gelir.
Lynn

4
@Bryan, Web sitenizin bağlantısı benim için çalışmıyor ve birincil alan adı Twitter'a yönlendiriyor. Kodunuz için güncellenmiş bir bağlantınız var mı?
Michael

2
İşte Bryan'ın jeneratörünün bir klonu (görünüşe göre): github.com/jeremy886/crossword_helmig
lvictorino

20

Aslında on yıl kadar önce bir bulmaca oluşturma programı yazmıştım (şifreli idi ama aynı kurallar normal bulmacalar için de geçerliydi).

Bugüne kadar azalan kullanıma göre sıralanmış bir dosyada depolanan sözcüklerin (ve ilişkili ipuçlarının) bir listesi vardı (böylece daha az kullanılan sözcükler dosyanın en üstünde yer alıyordu). Temelde siyah ve serbest kareleri temsil eden bir bit maskesi olan bir şablon, müşteri tarafından sağlanan bir havuzdan rastgele seçildi.

Daha sonra, bulmacadaki tamamlanmamış her kelime için (temel olarak ilk boş kareyi bulun ve sağdaki (kelimenin karşısında) veya alttaki (aşağı-kelime) de boş olup olmadığına bakın), bir arama yapıldı o kelimede zaten bulunan harfleri dikkate alarak, uyan ilk kelimeyi arayan dosya. Sığacak bir kelime yoksa, tüm kelimeyi eksik olarak işaretlediniz ve devam ettiniz.

Sonunda, derleyicinin doldurması gereken (ve istenirse dosyaya kelimeyi ve ipucunu ekleyeceği) tamamlanmamış kelimeler olacaktır. Herhangi bir fikir bulamazlarsa, kısıtlamaları değiştirmek için bulmacayı manuel olarak düzenleyebilirler veya tamamen yeniden oluşturma isteyebilirler.

Kelime / ipucu dosyası belirli bir boyuta ulaştığında (ve bu müşteri için günde 50-100 ipucu ekliyordu), nadiren her bulmaca için yapılması gereken iki veya üçten fazla manuel düzeltme vakası vardı. .


Bu aslında benim durumumda bana yardımcı olmuyor, çünkü sadece 6-12 kelimelik bir listem var. Benimki kullanıcı için bir kelime bulmacasından çok bir öğrenme alıştırması gibidir. Yine de ilginç algoritma için +1!
nickf

1
Güzel açıklama. Bunu geçmişte birkaç kez düşündüm ama hiç denemedim. Şimdi sihirli soru: ne kadar iyi çalıştı? Sadece seyrek bulmacalar için mi, yoksa yoğun bulmacalar için mi (gazetede olduğu gibi)? Ve yoğun bulmacalar için kaç ipucuna ihtiyacınız var?
dmckee --- eski moderatör kedicik

1
@dmckee, bir süre önceydi ama hafızadan yoğun bulmacalar bile oldukça iyiydi. Birçoğu müdahale olmadan tamamlandı, ancak yine de bir veya iki ekstra kelime eklenmesini gerektiren beşte birini elde edersiniz. Ve dosyadaki binlerce kelimeden bahsediyoruz. Hiç şüphe yok ki geriye dönük izleme yardımcı olabilirdi, ancak danışanın (örneğin) 5 bitmemiş kelimeyle birini reddetmesi, fazladan ipucu bulmaya çalışmaktan endişelenmekten daha kolaydı. Beş, bitmemiş sözler için gördüğüm dış sınırla ilgiliydi.
paxdiablo

16

Bu algoritma 60 saniyede 50 yoğun 6x9 ok bulmacası oluşturur . Bir kelime veritabanı (kelime + ipuçları ile) ve bir pano veritabanı (önceden yapılandırılmış panolarla) kullanır.

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

Daha büyük bir kelime veritabanı, oluşturma süresini önemli ölçüde azaltır ve bazı tahtaların doldurulması daha zordur! Daha büyük tahtaların doğru şekilde doldurulması için daha fazla zaman gerekir!


Misal:

Önceden Yapılandırılmış 6x9 Anakart:

(#, bir hücrede bir ipucu anlamına gelir,%, bir hücrede iki ipucu anlamına gelir, oklar gösterilmez)

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

Oluşturulan 6x9 Anakart:

# 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 

İpuçları [satır, sütun]:

[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

Bu daha eski bir soru olmasına rağmen, yaptığım benzer çalışmaya dayanarak bir cevap vermeye çalışacağım.

Kısıtlama problemlerini çözmek için birçok yaklaşım vardır (bunlar genellikle NPC karmaşıklık sınıfındadır).

Bu, kombinatoryal optimizasyon ve kısıt programlama ile ilgilidir. Bu durumda kısıtlamalar, ızgaranın geometrisi ve kelimelerin benzersiz olması gerekliliğidir.

Randomizasyon / Tavlama yaklaşımları da işe yarayabilir (uygun ortamda olmasına rağmen).

Etkili basitlik, sadece nihai bilgelik olabilir!

Gereksinimler, az çok eksiksiz bir çapraz kelime derleyicisi ve (görsel WYSIWYG) oluşturucu içindi.

WYSIWYG oluşturucu kısmını bir kenara bırakırsak, derleyicinin ana hatları şuydu:

  1. Kullanılabilir kelime listelerini yükleyin (kelime uzunluğuna göre sıralanmış, yani 2,3, .., 20)

  2. Kullanıcı tarafından oluşturulmuş ızgarada kelime gruplarını (yani ızgara kelimeleri) bulun (örn. X, y uzunluğunda L, yatay veya dikey) (karmaşıklık O (N))

  3. Izgara kelimelerinin (doldurulması gereken) kesişen noktalarını hesaplayın (karmaşıklık O (N ^ 2))

  4. Kelime listelerindeki kelimelerin kullanılan alfabenin çeşitli harfleri ile kesişimlerini hesaplayın (bu, bir şablon kullanarak eşleşen kelimeleri aramaya izin verir, örneğin cwc tarafından kullanılan Sik Cambon tezi ) (karmaşıklık O (WL * AL))

.3 ve .4 adımları bu görevi gerçekleştirmeye izin verir:

a. Izgara kelimelerinin kendileriyle kesişimleri, bu ızgara kelimesi için mevcut kelimelerin ilişkili kelime listesindeki eşleşmeleri bulmaya çalışmak için bir "şablon" oluşturmayı sağlar (bu kelime ile kesişen diğer kelimelerin harflerini kullanarak, önceden belirli bir algoritmanın adımı)

b. Bir kelime listesindeki kelimelerin alfabe ile kesişimleri, belirli bir "şablon" ile eşleşen eşleşen (aday) kelimelerin bulunmasını sağlar (örn. 1. sırada 'A' ve 3. sırada 'B' vb.)

Bu veri yapıları ile uygulanan algoritma şu şekildeydi:

NOT: ızgara ve sözcük veritabanı sabitse, önceki adımlar yalnızca bir kez yapılabilir.

  1. Algoritmanın ilk adımı, rastgele boş bir kelime dilimi (ızgara kelimesi) seçmek ve onu ilişkili kelime listesinden bir aday kelime ile doldurmaktır (randomizasyon, algoritmanın ardışık uygulamalarında farklı çözümler üretmeyi sağlar) (karmaşıklık O (1) veya O ( N))

  2. Hala boş olan her kelime alanı için (önceden doldurulmuş kelime alanlarıyla kesişimleri olan), bir kısıtlama oranı hesaplayın (bu değişebilir, basit, o adımdaki mevcut çözümlerin sayısıdır) ve boş kelime gruplarını bu orana göre sıralayın (karmaşıklık O (NlogN ) veya O (N))

  3. Önceki adımda hesaplanan boş kelime gruplarında döngü yapın ve her biri için birkaç kancdidate çözümü deneyin ("yay tutarlılığının korunduğundan" emin olun, yani bu kelime kullanılırsa ızgaranın bu adımdan sonra bir çözümü vardır) ve bunları şuna göre sıralayın: sonraki adım için maksimum kullanılabilirlik (yani bir sonraki adım, bu kelime o yerde o anda kullanılırsa, maksimum olası çözümleri içerir, vb.) (karmaşıklık O (N * MaxCandidatesUsed))

  4. Bu kelimeyi doldurun (doldurulmuş olarak işaretleyin ve 2. adıma gidin)

  5. Adım .3 kriterlerini karşılayan bir kelime bulunamazsa, önceki adımın başka bir aday çözümüne geri dönmeyi deneyin (kriterler burada değişebilir) (karmaşıklık O (N))

  6. Geriye dönük izleme bulunursa, alternatifi kullanın ve isteğe bağlı olarak sıfırlanması gerekebilecek önceden doldurulmuş sözcükleri sıfırlayın (bunları yeniden doldurulmamış olarak işaretleyin) (karmaşıklık O (N))

  7. Geri dönüş bulunamazsa, çözüm bulunamaz (en azından bu yapılandırma, ilk tohum vb. İle)

  8. Aksi takdirde tüm kelime grupları doldurulduğunda tek bir çözümünüz olur

Bu algoritma, problemin çözüm ağacında rastgele ve tutarlı bir yürüyüş yapar. Bir noktada bir çıkmaz varsa, önceki bir düğüme geri dönüş yapar ve başka bir yolu izler. Bir çözüm bulunana veya çeşitli düğümler için aday sayısı tükenene kadar.

Tutarlılık kısmı, bulunan bir çözümün gerçekten bir çözüm olmasını sağlar ve rastgele kısım, farklı uygulamalarda farklı çözümler üretmeyi ve ayrıca ortalama olarak daha iyi performansa sahip olmasını sağlar.

PS. tüm bunlar (ve diğerleri) saf JavaScript (paralel işleme ve WYSIWYG ile) özelliğinde uygulandı

PS2. Algoritma, aynı anda birden fazla (farklı) çözüm üretmek için kolayca paralelleştirilebilir

Bu yardımcı olur umarım


1
Bu, yoğun düzenler (NY Times benzeri) veya seyrek düzenler oluşturmak için mi?
Jim

1
@ Jim, bu çoğunlukla yoğun düzenler içindir, ancak seyrek olarak da ayarlanabilir. Aradaki fark, yoğun düzenlerde (örneğin, klasik, İskandinav vb.) Bir kılavuza sahiptir ve sözcükleri arar, oysa serbest biçimli düzenler için (seyrek) üzerinde sözcükler bulunur ve bir ızgara arar.
Nikos M.

1
Yukarıdaki adımları uygulayan bir yerde örnek bir kaynağa sahip olur musunuz? Örneğin, çoğu için sizinle birlikteyim (ve çoğunu bağımsız olarak zaten uyguladım), ancak "bir sınırlama oranını hesaplamak ..." söz konusu olduğunda, beni kaybettiğinizi kabul etmeliyim. "STH Oranı" gibi şeyler için internette arama yapmak da bana pek yardımcı olmuyor. Sorun benim uygulanmasında kelimeler doldurmak için bulmaya çalışıyor çok verimsiz ve çok uzun bir yol alarak olduğunu.
Jim

1
@ Jim, zaten kullanıldığından beri var, ancak bu yaptığım bir iş için özel olarak yapıldı, daha fazla yardıma ihtiyacınız olursa, açık kaynak projelerimde hafif bir sürüm yayınlamam mümkün, benimle iletişime geçin (ps gerçekten de algoritma i yayınlanmıştır bazı durumlarda olabilir ) çok uzun sürebilir, ancak ortalama doesnt
Nikos M.

1
@Jim, bu bulmacalar sitesine bir göz atın (hala devam ediyor) istavrolexo.gr (Yunanca), benzer bir algoritma ( büyük bir İskandinav bulmacası ) tarafından oluşturulmuş çeşitli (yoğun) bulmacalarla (örneğin, İskandinav, klasik, sudoku )
Nikos M.

9

Neden başlamak için rastgele olasılıklı bir yaklaşım kullanmıyorsunuz? Bir kelime ile başlayın ve sonra tekrar tekrar rastgele bir kelime seçin ve boyut vb. Üzerindeki kısıtlamaları bozmadan bulmacanın mevcut durumuna sığdırmaya çalışın. Başarısız olursanız, her şeye yeniden başlayın.

Böyle bir Monte Carlo yaklaşımının ne kadar sıklıkla işe yaradığına şaşıracaksınız.


2
evet, seçtiğim yaklaşım bu. Denemene ve çok zeki olmana gerek yok. Kelimeleri en uzundan en kısaya sıralayın. Bir döngüde, rastgele bir hücre seçin (sütun ve satır koordinatı) ve sondan akıp gitmediğini veya başka bir kelimeyi engelleyip engellemediğini görmek için test tahtasına yerleştirin (kelimeyi ızgaraya yazmadan önce, her hücrenin ya boş veya bir harfi varsa, o harf yazmaya çalıştığınız harfle eşleşir). Sınırları ve diğer şeyleri kontrol etmek için başka bir mantık var. Kaba kuvvet, daha küçük ve daha küçük ızgaralar oluşturuyor, ardından en küçüğü kesişen kelimelere göre sıralıyorum.
Max Hodges

6

İşte nickf'in cevabına ve Bryan'ın Python koduna dayalı bazı JavaScript kodu. Sadece başka birinin js'ye ihtiyacı olması ihtimaline karşı yazıyorum.

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();
}

word nesne şeması eksik, lütfen wordArray sağlayın
ör.

Kelimenin tam anlamıyla sadece ['elma', 'turuncu', 'armut'] gibi bir dizi kelime
FascistDonut

Merhaba, FYI Düzenlemem çok fazla kodu değiştirmedi, sadece biçimlendirdi. 'Satır içi' görüntülerken çok dağınık göründüğünü biliyorum, ancak koddaki gerçek değişiklikleri görmek istiyorsanız , 'yan yana işaretleme'yi tıklayın. Şey ... Düzenleme açıklamasına "Biçimlendirilmiş kod" yazmalıydım, ama meh.
çift ​​bip sesi

Bu nasıl çalışıyor? Bu javascript'i içeren bir html dosyası sağlayabilir misiniz?
GKS

5

İki sayı üretirim: Uzunluk ve Scrabble puanı. Düşük bir Scrabble puanının, katılmanın daha kolay olduğu anlamına geldiğini varsayın (düşük puanlar = çok sayıda ortak harf). Listeyi azalan uzunluğa ve artan Scrabble skoruna göre sıralayın.

Ardından, listeye gidin. Sözcük, mevcut bir sözcükle çakışmazsa (sırasıyla her sözcüğün uzunluğuna ve Scrabble puanına göre kontrol edin), sonra sıraya koyun ve sonraki sözcüğü kontrol edin.

Durulayın ve tekrarlayın, bu bir bulmaca oluşturmalıdır.

Tabii ki, bunun O (n!) Olduğundan oldukça eminim ve bulmacayı sizin için tamamlayacağınızın garantisi yok, ama belki birileri onu geliştirebilir.


3

Bu problem hakkında düşünüyordum. Benim düşünceme göre, gerçekten yoğun bir bulmaca yaratmak için, sınırlı kelime listenizin yeterli olacağını umamazsınız. Bu nedenle, bir sözlüğü alıp bir "trie" veri yapısına yerleştirmek isteyebilirsiniz. Bu, soldaki boşlukları dolduran kelimeleri kolayca bulmanızı sağlayacaktır. Bir üçlüde, diyelim ki size "c? T" biçimindeki tüm kelimeleri veren bir geçiş uygulamak oldukça etkilidir.

Öyleyse, benim genel düşüncem şudur: Burada bazılarının düşük yoğunluklu bir haç oluşturmak için açıklandığı gibi bir tür nispeten kaba kuvvet yaklaşımı yaratın ve boşlukları sözlük kelimeleriyle doldurun.

Başka biri bu yaklaşımı benimsemişse, lütfen bana bildirin.


3

Bulmaca üretme motoru etrafında oynuyordum ve bunu en önemli buldum:

0.!/usr/bin/python

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

    b. Daha sonra rastgele seçimle yinelemek istemediğiniz sürece, kolay yönlendirme için matriste dolaşacak imleç gibi bir öğe / nesne yapın.

  2. birincisi, ilk çifti alın ve 0,0'dan aşağı ve aşağı yerleştirin; ilkini mevcut bulmaca 'liderimiz' olarak saklayın.

  3. imleci çapraz veya rasgele sırayla hareket ettirin ve çapraz olasılıkla bir sonraki boş hücreye geçin

  4. gibi kelimeleri yineleyin ve maksimum kelime uzunluğunu tanımlamak için boş alan uzunluğu kullanın: 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. kelimeyi kullandığım boş alanla karşılaştırmak için:

    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. Başarıyla kullanılan her kelimeden sonra yön değiştirin. Tüm hücreler doluyken döngü yapın VEYA kelimeleriniz biterse VEYA yineleme sınırı nedeniyle:

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

... ve yeni bulmacayı tekrar yineleyin.

  1. Puanlama sistemini doldurma kolaylığı ve bazı tahmin hesaplamaları ile yapın. Mevcut bulmaca için puan verin ve puanlama sisteminizden puan tatmin edildiyse, bulmacalar listesine ekleyerek sonraki seçimi daraltın.

  2. İlk yineleme oturumundan sonra, işi bitirmek için yapılan bulmacalar listesinden tekrar yineleyin.

Daha fazla parametre kullanarak hız, çok büyük bir faktörle iyileştirilebilir.


2

Olası çarpımları bilmek için her kelimenin kullandığı her harfin bir dizinini alırdım. O zaman en büyük kelimeyi seçer ve onu temel olarak kullanırdım. Bir sonraki büyük olanı seçin ve onu geçin. Durulayın ve tekrarlayın. Muhtemelen bir NP problemidir.

Başka bir fikir, güç ölçüsünün ızgaraya kaç kelime koyabileceğiniz olduğu genetik bir algoritma oluşturmaktır.

Bulduğum en zor kısım, belirli bir listenin ne zaman aşılamayacağını bilmek.


1
Ayrıca genetik bir algoritma düşünüyordum. Uygunluk işlevi, kelimelerin ızgaraya ne kadar sıkı yerleştirildiği olabilir.
Adrian McCarthy

2

Bu , Harvard'dan AI CS50 kursunda bir proje olarak görünüyor . Buradaki fikir, çapraz kelime oluşturma problemini bir kısıtlama tatmin problemi olarak formüle etmek ve arama alanını azaltmak için farklı sezgisel yöntemlerle geriye doğru izleme ile çözmektir.

Başlamak için birkaç girdi dosyasına ihtiyacımız var:

  1. Çapraz bulmacanın yapısı (aşağıdakine benzer, örneğin '#' doldurulmayacak karakterleri ve '_' doldurulacak karakterleri temsil eder)

'

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

'

  1. Aday kelimelerin seçileceği bir giriş sözlüğü (kelime listesi / sözlük) (aşağıda gösterilen gibi).

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

Şimdi CSP şu şekilde tanımlandı ve çözülecek:

  1. Değişkenler, girdi olarak sağlanan kelime listesinden (kelime dağarcığı) değerlere (yani alanlarına) sahip olacak şekilde tanımlanır.
  2. Her değişken 3 demet ile temsil edilir: (grid_coordinate, direction, length) koordinat karşılık gelen kelimenin başlangıcını temsil eder, yön yatay veya dikey olabilir ve uzunluk, değişkenin olacağı kelimenin uzunluğu olarak tanımlanır atandı.
  3. Kısıtlamalar, sağlanan yapı girdisi ile tanımlanır: örneğin, bir yatay ve bir dikey değişken ortak bir karaktere sahipse, bir örtüşme (yay) kısıtlaması olarak temsil edilecektir.
  4. Şimdi, düğüm tutarlılığı ve AC3 yay tutarlılığı algoritmaları, alanları azaltmak için kullanılabilir.
  5. Ardından, MRV (minimum kalan değer), derece vb. Sezgisel yöntemlerle CSP'ye bir çözüm elde etmek için geri izleme (varsa), bir sonraki atanmamış değişkeni seçmek için kullanılabilir ve alan adı için LCV (en az kısıtlama değeri) gibi buluşsal yöntemler kullanılabilir. arama algoritmasını daha hızlı hale getirmek için sıralama.

Aşağıda, CSP çözme algoritmasının bir uygulaması kullanılarak elde edilen çıktı gösterilmektedir:

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

'

Aşağıdaki animasyon, geri izleme adımlarını gösterir:

görüntü açıklamasını buraya girin

İşte Bangla (Bengalce) kelime listesi olan bir tane daha:

görüntü açıklamasını buraya girin


Gerçekten ilginç bir açıklama için +1. Bununla birlikte, burada çözmeye çalıştığım problemin bağlamı, hepsinin kullanılması gereken küçük bir kelime grubunun olmasıydı ve bir düzen ile başlayıp kelimeleri bulmak yerine bulmaca için en uygun düzeni bulmaya çalışıyordum. hangisi uygun.
nickf

1

jQuery Bulmaca Üreticisi ve Oyunu

Bu soruna bir JavaScript / jQuery çözümü kodladım:

Örnek Demo: http://www.earthfluent.com/crossword-puzzle-demo.html

Kaynak Kodu: https://github.com/HoldOffHunger/jquery-crossword-puzzle-generator

Kullandığım algoritmanın amacı:

  1. Izgaradaki kullanılamayan karelerin sayısını mümkün olduğunca en aza indirin.
  2. Mümkün olduğunca çok sayıda karışık kelimeye sahip olun.
  3. Son derece hızlı bir zamanda hesaplama yapın.

Oluşturulan bir çapraz bulmacanın gösterilmesi.

Kullandığım algoritmayı açıklayacağım:

  1. Kelimeleri ortak bir harfi paylaşanlara göre gruplayın.

  2. Bu gruplardan, yeni bir veri yapısı ("kelime blokları") oluşturun, bu bir birincil kelime (tüm diğer kelimeleri geçen) ve sonra diğer kelimeleri (birincil kelimeden geçen).

  3. Çapraz bulmacayı, bulmacanın en üst sol konumunda bu kelime bloklarının ilkiyle başlatın.

  4. Kelime bloklarının geri kalanı için, bulmacanın en sağ-alt konumundan başlayarak, doldurulacak boş alan kalmayıncaya kadar yukarı ve sola doğru hareket edin. Yukarıya doğru soldan daha fazla boş sütun varsa, yukarı doğru hareket edin ve bunun tersi de geçerlidir.


@holdoffhunger Bulmaca anahtarını göstermek için bir yönteminiz var mı? Harflerle dolu kutular?
Jon Glazer

@Jon Glazer: Tipik olarak, bulmaca anahtarlarını fonksiyonun kendisine gönderirsiniz, ancak bulmacayı hemen sonra 2d karakter dizisi olarak kaydedebilirsiniz var crosswords = generateCrosswordBlockSources(puzzlewords);. Sadece bu değeri günlüğe kaydedin. Unutmayın, oyunda hemen değeri almak için "Yanıtı Göster" e tıklayabileceğiniz bir "hile modu" vardır.
HoldOffHunger

Bu, bitişik "aşağı" kutuları olan yerlerde anlamsız kelimelerin "karşısında" olduğu bulmacalar üretir ve bunun tersi de geçerlidir. Standart çapraz bulmaca , yoğunluğu en üst düzeye çıkarmasına rağmen bu şekilde çalışmaz .
Beejor
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.