Thuật toán tạo ô chữ


123

Với một danh sách các từ, bạn sẽ sắp xếp chúng vào một lưới ô chữ như thế nào?

Nó sẽ không giống như một trò chơi ô chữ "thích hợp" đối xứng hoặc bất cứ điều gì tương tự: về cơ bản chỉ cần xuất ra vị trí và hướng bắt đầu cho mỗi từ.

Câu trả lời:


62

Tôi đã nghĩ ra một giải pháp có lẽ không phải là hiệu quả nhất, nhưng nó hoạt động đủ tốt. Về cơ bản:

  1. Sắp xếp tất cả các từ theo độ dài, giảm dần.
  2. Lấy từ đầu tiên và đặt nó trên bảng.
  3. Hãy nói từ tiếp theo.
  4. Tìm kiếm qua tất cả các từ đã có trên bảng và xem liệu có bất kỳ giao điểm nào có thể có (bất kỳ chữ cái phổ biến nào) với từ này không.
  5. Nếu có một vị trí khả dĩ cho từ này, hãy lặp lại tất cả các từ trên bảng và kiểm tra xem liệu từ mới có xen vào không.
  6. Nếu từ này không phá vỡ bảng, thì hãy đặt nó ở đó và chuyển sang bước 3, nếu không, hãy tiếp tục tìm kiếm địa điểm (bước 4).
  7. Tiếp tục vòng lặp này cho đến khi tất cả các từ được đặt hoặc không thể đặt được.

Điều này làm cho một ô chữ hoạt động, nhưng thường khá kém. Tôi đã thực hiện một số thay đổi đối với công thức cơ bản ở trên để đưa ra kết quả tốt hơn.

  • Khi kết thúc việc tạo ô chữ, hãy cho nó điểm dựa trên số lượng từ đã được đặt (càng nhiều càng tốt), kích thước của bảng (càng nhỏ càng tốt) và tỷ lệ giữa chiều cao và chiều rộng (càng gần lên 1 càng tốt). Tạo một số trò chơi ô chữ và sau đó so sánh điểm của chúng và chọn cái tốt nhất.
    • Thay vì chạy một số lần lặp lại tùy ý, tôi đã quyết định tạo càng nhiều ô chữ càng tốt trong một khoảng thời gian tùy ý. Nếu bạn chỉ có một danh sách từ nhỏ, thì bạn sẽ nhận được hàng tá ô chữ khả thi trong 5 giây. Một ô chữ lớn hơn chỉ có thể được chọn từ 5-6 khả năng.
  • Khi đặt một từ mới, thay vì đặt nó ngay lập tức khi tìm thấy một vị trí có thể chấp nhận được, hãy cho điểm vị trí của từ đó dựa trên mức độ tăng kích thước của lưới và có bao nhiêu giao điểm ở đó (lý tưởng là bạn muốn mỗi từ là gạch chéo bởi 2-3 từ khác). Theo dõi tất cả các vị trí và điểm số của họ, sau đó chọn vị trí tốt nhất.

7
Tôi tình cờ viết chương trình này khi chúng ta nói chuyện, và đây cũng là thuật toán giống hệt tôi đã chọn. Đối với số lượng từ nhỏ (10 hoặc ít hơn), chương trình không gặp khó khăn khi tính toán tất cả các giải pháp có thể tính bằng mili giây. Tuy nhiên, algoritm là cấp số nhân. Phần dễ dàng là viết thuật toán cơ bản mà brute-force thông qua tất cả các kết hợp có thể có. Phần khó khăn là hàng tá 'đường tắt' mà bạn cần để ngăn chương trình thử tất cả các giải pháp cuối cùng.
user31586,

2
"5. ... và kiểm tra xem từ mới có gây trở ngại hay không" Làm cách nào để giải thích các tình huống mà từ mới được đặt bên cạnh một từ hiện có, điều này tạo ra từ vô nghĩa ở những nơi có các ô vuông liền kề? Vd: LEMON ERASE Nếu "LE", "ER" và "MA", v.v. không phải là các từ trong danh sách của bạn, điều này là sai. Mặt khác, ngay từ chối adjacencies như vậy có thể vứt bỏ lưới thực sự tốt, như: W LEMON ERASE NEXUS TT
George Armhold

4
@Kaffeine, vâng, tôi hiểu ý bạn - Tôi đã phải loại bỏ các tùy chọn này vì mặc dù chúng có thể tạo ra các lưới thực sự tốt, nhưng quá khó để kiểm tra (đọc: Tôi không thể bị làm phiền) và rất có thể đó chỉ là sự can thiệp .
nickf

4
Được tạo bằng jQuery / Javascript bằng cách sử dụng các đề xuất ở trên và một số ... mlewiscs.com/crossword
MLewisCodeSolutions

@MLewisCodeSolutions Trông thật tuyệt. Bạn có nguồn mở này không?
GKS

23

Tôi vừa mới viết của riêng mình bằng Python. Bạn có thể tìm thấy nó ở đây: http://bryanhelmig.com/python-crossword-puzzle-generator/ . Nó không tạo ra các trò chơi ô chữ kiểu NYT dày đặc, mà là kiểu ô chữ mà bạn có thể tìm thấy trong sách giải đố của một đứa trẻ.

Không giống như một số thuật toán mà tôi tìm thấy ở đó đã triển khai phương pháp đặt từ ngẫu nhiên như một số thuật toán đã đề xuất, tôi đã cố gắng triển khai phương pháp brute-force thông minh hơn một chút khi đặt từ. Đây là quy trình của tôi:

  1. Tạo một lưới có kích thước bất kỳ và một danh sách các từ.
  2. Xáo trộn danh sách từ, sau đó sắp xếp các từ dài nhất đến ngắn nhất.
  3. Đặt từ đầu tiên và từ dài nhất ở phía trên bên trái vị trí nhất, 1,1 (dọc hoặc ngang).
  4. Chuyển sang từ tiếp theo, lặp lại từng chữ cái trong từ và từng ô trong lưới để tìm kiếm các chữ cái phù hợp với chữ cái.
  5. Khi tìm thấy khớp, chỉ cần thêm vị trí đó vào danh sách tọa độ được đề xuất cho từ đó.
  6. Lặp lại danh sách tọa độ được đề xuất và "chấm điểm" vị trí của từ dựa trên số lượng từ khác mà nó vượt qua. Điểm 0 cho biết vị trí không tốt (liền kề với các từ hiện có) hoặc không có chữ nào bị gạch chéo.
  7. Quay lại bước # 4 cho đến khi hết danh sách từ. Vượt qua thứ hai tùy chọn.
  8. Bây giờ chúng ta sẽ có một ô chữ, nhưng chất lượng có thể bị ảnh hưởng hoặc bị thiếu do một số vị trí ngẫu nhiên. Vì vậy, chúng tôi đệm ô chữ này và quay lại bước # 2. Nếu ô chữ tiếp theo có nhiều từ hơn được đặt trên bảng, ô chữ đó sẽ thay thế ô chữ trong bộ đệm. Đây là thời gian giới hạn (tìm ô chữ tốt nhất trong x giây).

Cuối cùng, bạn đã có một câu đố ô chữ hoặc câu đố tìm kiếm từ tốt, vì chúng giống nhau. Nó có xu hướng chạy khá tốt, nhưng hãy cho tôi biết nếu bạn có bất kỳ đề xuất cải thiện nào. Lưới lớn hơn chạy chậm hơn theo cấp số nhân; danh sách từ lớn hơn một cách tuyến tính. Danh sách từ lớn hơn cũng có cơ hội cao hơn nhiều để có số vị trí từ tốt hơn.


@Neil N: Có lẽ là khả năng khớp chữ cái tốt hơn cho các từ khác. Có thể cũng sẽ là một cách tiếp cận để sắp xếp theo số lượng các chữ cái riêng biệt có trong mỗi từ, hầu hết sẽ dẫn đến cùng một kết quả.
Karl Adler,

@NeilN Python's array.sort(key=f)ổn định, có nghĩa là (ví dụ) chỉ cần sắp xếp danh sách từ theo thứ tự bảng chữ cái theo độ dài sẽ giữ tất cả các từ 8 chữ cái được sắp xếp theo thứ tự bảng chữ cái.
Lynn

4
@Bryan, Liên kết trang web của bạn không phù hợp với tôi và miền chính chỉ chuyển hướng đến Twitter. Bạn có liên kết cập nhật đến mã của mình không?
Michael A

2
Đây (dường như) một bản sao của trình tạo của Bryan: github.com/jeremy886/crossword_helmig
lvictorino

20

Tôi thực sự đã viết một chương trình tạo ô chữ khoảng mười năm trước (nó khó hiểu nhưng các quy tắc tương tự sẽ áp dụng cho ô chữ bình thường).

Nó có một danh sách các từ (và các manh mối liên quan) được lưu trữ trong một tệp được sắp xếp theo mức độ sử dụng giảm dần cho đến ngày nay (vì vậy các từ ít được sử dụng hơn nằm ở đầu tệp). Một mẫu, về cơ bản là một mặt nạ bit đại diện cho các hình vuông màu đen và tự do, được chọn ngẫu nhiên từ một nhóm do khách hàng cung cấp.

Sau đó, đối với mỗi từ không hoàn chỉnh trong câu đố (về cơ bản tìm ô trống đầu tiên và xem từ bên phải (từ ngang) hay từ bên dưới (từ dưới) cũng trống), một tìm kiếm đã được thực hiện tệp tìm kiếm từ đầu tiên phù hợp, có tính đến các chữ cái đã có trong từ đó. Nếu không có từ nào có thể phù hợp, bạn chỉ cần đánh dấu toàn bộ từ đó là chưa hoàn thành và tiếp tục.

Ở cuối sẽ là một số từ chưa hoàn thành mà trình biên dịch sẽ phải điền vào (và thêm từ đó và một đầu mối vào tệp nếu muốn). Nếu họ không thể đưa ra bất kỳ ý tưởng nào , họ có thể chỉnh sửa ô chữ theo cách thủ công để thay đổi các ràng buộc hoặc chỉ yêu cầu tạo lại toàn bộ.

Khi tệp từ / đầu mối đạt đến kích thước nhất định (và nó đã thêm 50-100 manh mối mỗi ngày cho ứng dụng khách này), hiếm khi có trường hợp phải thực hiện nhiều hơn hai hoặc ba lần sửa lỗi thủ công cho mỗi ô chữ .


Điều này không thực sự giúp ích cho tôi trong tình huống của tôi, vì tôi sẽ có một danh sách chỉ khoảng 6-12 từ. Của tôi giống như một bài tập học tập cho người dùng hơn là một câu đố chữ. +1 cho thuật toán thú vị dù sao!
nickf

1
Mô tả đẹp. Tôi đã nghĩ về điều này một vài lần trong quá khứ, nhưng chưa bao giờ thử nó. Bây giờ cho câu hỏi kỳ diệu: nó hoạt động tốt như thế nào? Chỉ dành cho những câu đố thưa thớt, hay những câu đố dày đặc (như trong bài báo)? Và bạn cần bao nhiêu manh mối cho những câu đố dày đặc?
dmckee --- mèo con người điều hành cũ.

1
@dmckee, nó đã được một thời gian trước đây nhưng từ trí nhớ, ngay cả những câu đố dày đặc cũng khá tốt. Nhiều câu đã được hoàn thành mà không cần can thiệp nhưng bạn vẫn có thể nhận được phần năm yêu cầu thêm một hoặc hai từ. Và chúng ta đang nói về hàng nghìn từ trong tệp. Không nghi ngờ gì việc bẻ khóa ngược có thể hữu ích nhưng chỉ cần khách hàng từ chối một câu có (ví dụ) 5 từ chưa hoàn thành sẽ dễ dàng hơn là lo lắng về việc cố gắng tìm ra manh mối bổ sung. Năm là giới hạn bên ngoài mà tôi nhìn thấy cho những từ chưa hoàn thành.
paxdiablo

16

Thuật toán này tạo ra 50 ô chữ mũi tên 6x9 dày đặc trong 60 giây. Nó sử dụng cơ sở dữ liệu từ (với từ + mẹo) và cơ sở dữ liệu bảng (với các bảng được cấu hình sẵn).

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

Một cơ sở dữ liệu từ lớn hơn sẽ giảm đáng kể thời gian tạo và một số loại bảng khó điền hơn! Các bảng lớn hơn đòi hỏi nhiều thời gian hơn để được điền chính xác!


Thí dụ:

Bảng 6x9 được cấu hình trước:

(# có nghĩa là một mẹo trong một ô,% có nghĩa là hai mẹo trong một ô, các mũi tên không được hiển thị)

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

Bảng 6x9 được tạo:

# 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 

Mẹo [dòng, cột]:

[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

Mặc dù đây là một câu hỏi cũ hơn, nhưng sẽ cố gắng trả lời dựa trên công việc tương tự mà tôi đã làm.

Có nhiều cách tiếp cận để giải quyết các vấn đề về ràng buộc (thường nằm trong lớp phức tạp NPC).

Điều này liên quan đến tối ưu hóa tổ hợp và lập trình ràng buộc. Trong trường hợp này, các ràng buộc là hình dạng của lưới và yêu cầu các từ là duy nhất, v.v.

Cách tiếp cận ngẫu nhiên hóa / ủ cũng có thể hoạt động (mặc dù trong cài đặt thích hợp).

Sự đơn giản hiệu quả có thể chỉ là sự khôn ngoan cuối cùng!

Các yêu cầu dành cho trình biên dịch ô chữ hoàn chỉnh hơn hoặc ít hơn và trình tạo (WYSIWYG trực quan).

Bỏ qua phần xây dựng WYSIWYG, phác thảo trình biên dịch là:

  1. Tải danh sách từ có sẵn (được sắp xếp theo độ dài từ, tức là 2,3, .., 20)

  2. Tìm các ô từ (tức là các từ lưới) trên lưới do người dùng tạo (ví dụ: từ x, y với độ dài L, ngang hoặc dọc) (độ phức tạp O (N))

  3. Tính các điểm giao nhau của các từ lưới (cần được điền) (độ phức tạp O (N ^ 2))

  4. Tính toán các giao điểm của các từ trong danh sách từ với các chữ cái khác nhau của bảng chữ cái được sử dụng (điều này cho phép tìm kiếm các từ phù hợp bằng cách sử dụng một mẫu ví dụ: Luận án Sik Cambon được cwc sử dụng ) (độ phức tạp O (WL * AL))

Các bước .3 và .4 cho phép thực hiện tác vụ này:

a. Các giao điểm của các từ lưới với chính nó cho phép tạo "mẫu" để cố gắng tìm các kết quả phù hợp trong danh sách từ liên quan gồm các từ có sẵn cho từ lưới này (bằng cách sử dụng các chữ cái của các từ giao nhau với từ này đã được điền vào một số bước của thuật toán)

b. Các giao điểm của các từ trong danh sách từ với bảng chữ cái cho phép tìm các từ phù hợp (ứng cử viên) phù hợp với một "mẫu" nhất định (ví dụ: 'A' ở vị trí đầu tiên và 'B' ở vị trí thứ 3, v.v.)

Vì vậy, với các cấu trúc dữ liệu được triển khai, thuật toán được sử dụng là như thế này:

LƯU Ý: nếu lưới và cơ sở dữ liệu từ không đổi, các bước trước đó chỉ có thể được thực hiện một lần.

  1. Bước đầu tiên của thuật toán là chọn ngẫu nhiên một ô từ trống (từ lưới) và điền nó bằng một từ ứng viên từ danh sách từ liên quan của nó (ngẫu nhiên hóa cho phép tạo ra các chất giải khác nhau trong các lần thực hiện liên tiếp của thuật toán) (độ phức tạp O (1) hoặc O ( N))

  2. Đối với mỗi ô từ còn trống (có giao điểm với ô từ đã được điền), hãy tính tỷ lệ ràng buộc (tỷ lệ này có thể khác nhau, đơn giản thứ là số lượng giải pháp có sẵn ở bước đó) và sắp xếp các ô từ trống theo tỷ lệ này (độ phức tạp O (NlogN ) hoặc O (N))

  3. Lặp lại các ô từ trống đã tính ở bước trước và với mỗi ô, hãy thử một số giải pháp cancdidate (đảm bảo rằng "tính nhất quán cung được giữ lại", tức là lưới có một giải pháp sau bước này nếu từ này được sử dụng) và sắp xếp chúng theo tính khả dụng tối đa cho bước tiếp theo (tức là bước tiếp theo có giải pháp tối đa có thể nếu từ này được sử dụng tại thời điểm đó ở nơi đó, v.v.) (độ phức tạp O (N * MaxCandidatesUsed))

  4. Điền từ đó (đánh dấu là đã điền và chuyển sang bước 2)

  5. Nếu không tìm thấy từ nào đáp ứng các tiêu chí của bước .3, hãy thử quay lại một giải pháp ứng viên khác của một số bước trước đó (tiêu chí có thể thay đổi ở đây) (độ phức tạp O (N))

  6. Nếu tìm thấy backtrack, hãy sử dụng tùy chọn thay thế và tùy chọn đặt lại bất kỳ từ nào đã được điền mà có thể cần đặt lại (đánh dấu chúng là chưa điền lại) (độ phức tạp O (N))

  7. Nếu không tìm thấy backtrack, thì không thể tìm thấy giải pháp nào (ít nhất là với cấu hình này, hạt giống ban đầu, v.v.)

  8. Khác khi tất cả các ô từ được lấp đầy, bạn có một giải pháp

Thuật toán này thực hiện một bước đi nhất quán ngẫu nhiên của cây giải pháp của bài toán. Nếu tại một thời điểm nào đó có một điểm cuối, nó sẽ quay trở lại một nút trước đó và đi theo một lộ trình khác. Chưa tìm thấy giải pháp nào hoặc số lượng ứng viên cho các nút khác nhau đã cạn kiệt.

Phần nhất quán đảm bảo rằng một giải pháp được tìm thấy thực sự là một giải pháp và phần ngẫu nhiên cho phép tạo ra các giải pháp khác nhau trong các lần thực thi khác nhau và ở mức trung bình cũng có hiệu suất tốt hơn.

Tái bút. tất cả điều này (và những thứ khác) được triển khai bằng JavaScript thuần túy (với khả năng xử lý song song và WYSIWYG)

PS2. Thuật toán có thể dễ dàng song song hóa để tạo ra nhiều giải pháp (khác nhau) cùng một lúc

Hi vọng điêu nay co ich


1
Đây là cách tạo bố cục dày đặc (như NY Times) hay bố cục thưa thớt?
Jim

1
@Jim, điều này chủ yếu dành cho bố cục dày đặc nhưng cũng có thể được điều chỉnh cho bố cục thưa thớt. Sự khác biệt là trong các bố cục dày đặc (ví dụ: cổ điển, scandinavik, v.v.), người ta có lưới và tìm kiếm các từ, trong khi bố cục dạng tự do (thưa thớt) có các từ và tìm kiếm lưới.
Nikos M.

1
Bạn có tình cờ có một số nguồn mẫu có sẵn ở đâu đó để thực hiện các bước trên không. Ví dụ, tôi ở bên bạn hầu hết nó (và đã thực hiện hầu hết nó một cách độc lập), nhưng khi nói đến "tính toán tỷ lệ ràng buộc ...", tôi phải thừa nhận rằng bạn đã thua tôi. Thực hiện tìm kiếm trên web cho những thứ như "STH Ratio" cũng không giúp ích được nhiều cho tôi. Vấn đề với việc triển khai của tôi là cố gắng tìm các từ để điền vào rất không hiệu quả và mất quá nhiều thời gian.
Jim

1
@Jim, tôi có cái này vì cái này đã được sử dụng rồi, nhưng cái này đã được thực hiện cụ thể cho công việc mà tôi đã có, có thể tôi sẽ đăng một phiên bản nhẹ lên các dự án mã nguồn mở của mình, nếu bạn cần thêm trợ giúp, hãy liên hệ với tôi (thực sự tại một số trường hợp các thuật toán tôi đăng có thể mất nhiều thời gian, nhưng trung bình nó không)
Nikos M.

1
@Jim, hãy xem trang web trò chơi ô chữ này (vẫn đang được tiến hành) istavrolexo.gr (bằng tiếng Hy Lạp) với nhiều trò chơi ô chữ (dày đặc) khác nhau (tức là scandinavik, cổ điển, sudoku) được tạo ra bởi một thuật toán tương tự ( một ô chữ scandinavik lớn )
Nikos M.

9

Tại sao không chỉ sử dụng phương pháp xác suất ngẫu nhiên để bắt đầu. Bắt đầu với một từ, sau đó liên tục chọn một từ ngẫu nhiên và cố gắng lắp nó vào trạng thái hiện tại của câu đố mà không phá vỡ các ràng buộc về kích thước, v.v. Nếu bạn thất bại, chỉ cần bắt đầu lại từ đầu.

Bạn sẽ ngạc nhiên về mức độ thường xuyên của một cách tiếp cận Monte Carlo như thế này.


2
vâng, đây là cách tiếp cận tôi đã chọn. Bạn không cần phải cố gắng và rất thông minh. Thứ tự các từ dài nhất đến ngắn nhất. Trong một vòng lặp, hãy chọn một ô ngẫu nhiên (tọa độ cột và hàng) và đặt từ đó lên bảng kiểm tra để xem liệu nó có chạy ra cuối hoặc xen vào từ khác (trước khi bạn viết từ vào lưới, hãy kiểm tra xem mỗi ô có trống hoặc nếu nó có một chữ cái, chữ cái đó khớp với chữ cái bạn đang cố gắng viết). Có một số logic khác để kiểm tra giới hạn và nội dung. Tôi brute force tạo ra một loạt các lưới nhỏ hơn và nhỏ hơn, sau đó xếp hạng nhỏ nhất dựa trên các từ giao nhau.
Max Hodges

6

Đây là một số mã JavaScript dựa trên câu trả lời của nickf và mã Python của Bryan. Chỉ đăng nó trong trường hợp người khác cần nó trong 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();
}

lược đồ đối tượng từ bị thiếu, vui lòng cung cấp wordArray
tức là

Theo nghĩa đen chỉ là một mảng của những từ như [ 'táo', 'da cam', 'lê']
FascistDonut

Xin chào, FYI bản chỉnh sửa của tôi không thay đổi nhiều mã, nó chỉ định dạng nó. Tôi biết nó trông rất lộn xộn khi xem nó 'nội tuyến', nhưng nếu bạn muốn xem những thay đổi thực sự trong mã, hãy nhấp vào 'side-by-side-markdown'. Chà ... lẽ ra tôi phải viết "Mã định dạng" trong mô tả chỉnh sửa, nhưng meh.
tiếng bíp kép

Cái này hoạt động ra sao? Bạn có thể cung cấp tệp html kết hợp javascript này không?
GKS

5

Tôi sẽ tạo ra hai con số: Độ dài và Điểm số Scrabble. Giả sử rằng điểm Scrabble thấp có nghĩa là bạn sẽ dễ dàng tham gia hơn (điểm thấp = nhiều chữ cái phổ biến). Sắp xếp danh sách theo độ dài giảm dần và điểm Scrabble tăng dần.

Tiếp theo, chỉ cần đi xuống danh sách. Nếu từ đó không giao nhau với một từ hiện có (kiểm tra từng từ theo độ dài của chúng và điểm Scrabble tương ứng), thì hãy đưa nó vào hàng đợi và kiểm tra từ tiếp theo.

Rửa sạch và lặp lại, và điều này sẽ tạo ra một ô chữ.

Tất nhiên, tôi khá chắc rằng đây là O (n!) Và không đảm bảo sẽ hoàn thành ô chữ cho bạn, nhưng có lẽ ai đó có thể cải thiện nó.


3

Tôi đã suy nghĩ về vấn đề này. Cảm nhận của tôi là để tạo ra một ô chữ thực sự dày đặc, bạn không thể hy vọng rằng danh sách từ giới hạn của bạn là đủ. Do đó, bạn có thể muốn lấy một từ điển và đặt nó vào cấu trúc dữ liệu "trie". Điều này sẽ cho phép bạn dễ dàng tìm thấy các từ điền vào khoảng trống còn lại. Trong một trie, khá hiệu quả để triển khai một truyền tải, chẳng hạn như, cung cấp cho bạn tất cả các từ có dạng "c? T".

Vì vậy, suy nghĩ chung của tôi là: tạo ra một số cách tiếp cận tương đối thô bạo như một số mô tả ở đây để tạo ra một chữ thập mật độ thấp và điền vào chỗ trống bằng các từ điển.

Nếu ai khác đã thực hiện phương pháp này, vui lòng cho tôi biết.


3

Tôi đã chơi xung quanh công cụ tạo ô chữ và tôi thấy điều này là quan trọng nhất:

0.!/usr/bin/python

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

    b. tạo một số mục / đối tượng giống như con trỏ sẽ đi quanh ma trận để dễ định hướng trừ khi bạn muốn lặp lại theo lựa chọn ngẫu nhiên sau này.

  2. đầu tiên, chọn cặp đầu tiên và đặt chúng ngang và xuống từ 0,0; lưu trữ cái đầu tiên làm 'thủ lĩnh' ô chữ hiện tại của chúng tôi.

  3. di chuyển con trỏ theo thứ tự đường chéo hoặc ngẫu nhiên với xác suất đường chéo lớn hơn đến ô trống tiếp theo

  4. lặp lại các từ như và sử dụng độ dài không gian trống để xác định độ dài từ tối đa: 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. để so sánh từ với không gian trống, tôi đã sử dụng tức là:

    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. sau mỗi từ được sử dụng thành công, hãy đổi hướng. Lặp lại trong khi tất cả các ô được điền HOẶC bạn hết từ HOẶC giới hạn số lần lặp thì:

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

... và lặp lại một lần nữa ô chữ mới.

  1. Tạo hệ thống tính điểm bằng cách dễ dàng điền và một số thước đo ước tính. Cho điểm cho ô chữ hiện tại và thu hẹp lựa chọn sau này bằng cách thêm nó vào danh sách ô chữ đã thực hiện nếu hệ thống tính điểm của bạn hài lòng với điểm số.

  2. Sau phiên lặp lại đầu tiên, hãy lặp lại một lần nữa từ danh sách các ô chữ đã tạo để hoàn thành công việc.

Bằng cách sử dụng nhiều tham số hơn, tốc độ có thể được cải thiện bởi một yếu tố rất lớn.


2

Tôi sẽ nhận được một chỉ mục của mỗi chữ cái được sử dụng bởi mỗi từ để biết các dấu gạch chéo có thể. Sau đó, tôi sẽ chọn từ lớn nhất và sử dụng nó làm cơ sở. Chọn cái lớn tiếp theo và vượt qua nó. Rửa sạch và lặp lại. Nó có thể là một vấn đề NP.

Một ý tưởng khác là tạo ra một thuật toán di truyền trong đó chỉ số độ mạnh là số lượng từ bạn có thể đưa vào lưới.

Phần khó tôi thấy là khi nào thì không thể bỏ qua một danh sách nào đó.


1
Tôi cũng đang nghĩ đến một thuật toán di truyền. Chức năng thể dục có thể là cách các từ được đóng gói chặt chẽ vào lưới.
Adrian McCarthy

2

Cái này xuất hiện như một dự án trong khóa học AI CS50 của Harvard. Ý tưởng là hình thành vấn đề tạo ô chữ như một vấn đề thỏa mãn hạn chế và giải quyết nó bằng cách quay ngược với các phương pháp heuristics khác nhau để giảm không gian tìm kiếm.

Để bắt đầu, chúng ta cần một vài tệp đầu vào:

  1. Cấu trúc của trò chơi ô chữ (trông giống như sau, ví dụ: trong đó '#' đại diện cho các ký tự không được điền và '_' đại diện cho các ký tự sẽ được điền)

`

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

`

  1. Một từ vựng đầu vào (danh sách từ / từ điển) từ đó các từ ứng viên sẽ được chọn (như hình sau).

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

Bây giờ CSP được xác định và giải quyết như sau:

  1. Các biến được định nghĩa là có các giá trị (tức là miền của chúng) từ danh sách các từ (từ vựng) được cung cấp làm đầu vào.
  2. Mỗi biến được đại diện bởi một bộ 3: (lưới_mục, hướng, độ dài) trong đó tọa độ thể hiện sự bắt đầu của từ tương ứng, hướng có thể là ngang hoặc dọc và độ dài được xác định là độ dài của từ mà biến sẽ là phân công.
  3. Các ràng buộc được xác định bởi đầu vào cấu trúc được cung cấp: ví dụ: nếu một biến ngang và một biến dọc có một ký tự chung, nó sẽ được biểu diễn dưới dạng ràng buộc chồng chéo (cung).
  4. Bây giờ, các thuật toán nhất quán của nút và cung AC3 có thể được sử dụng để giảm các miền.
  5. Sau đó, backtracking để có được giải pháp (nếu tồn tại) cho CSP với MRV (giá trị còn lại tối thiểu), độ, v.v ... có thể sử dụng heuristics để chọn biến chưa được gán tiếp theo và heuristics như LCV (giá trị ràng buộc ít nhất) có thể được sử dụng cho miền- sắp xếp, để làm cho thuật toán tìm kiếm nhanh hơn.

Phần sau cho thấy kết quả thu được bằng cách triển khai thuật toán giải 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█

`

Hình ảnh động sau đây cho thấy các bước quay lại:

nhập mô tả hình ảnh ở đây

Đây là một từ khác có danh sách từ tiếng Bangla (tiếng Bengali):

nhập mô tả hình ảnh ở đây


+1 cho một lời giải thích thực sự thú vị. Tuy nhiên, bối cảnh cho vấn đề mà tôi đang cố gắng giải quyết ở đây là có một nhóm từ nhỏ mà tất cả đều phải được sử dụng và tôi đang cố gắng tìm một bố cục tối ưu cho ô chữ, thay vì bắt đầu với một bố cục và tìm các từ. phù hợp.
nickf

1

jQuery Crossword Puzzle Generator and Game

Tôi đã viết mã giải pháp JavaScript / jQuery cho vấn đề này:

Demo mẫu: http://www.earthfluent.com/crossword-puzzle-demo.html

Mã nguồn: https://github.com/HoldOffHunger/jquery-crossword-puzzle-generator

Mục đích của thuật toán tôi đã sử dụng:

  1. Giảm thiểu số lượng ô vuông không sử dụng được trong lưới càng nhiều càng tốt.
  2. Có càng nhiều từ trộn lẫn với nhau càng tốt.
  3. Tính toán trong thời gian cực kỳ nhanh chóng.

Trình diễn trò chơi ô chữ đã tạo.

Tôi sẽ mô tả thuật toán tôi đã sử dụng:

  1. Nhóm các từ lại với nhau theo những từ có chung một chữ cái.

  2. Từ các nhóm này, xây dựng các tập hợp cấu trúc dữ liệu mới ("khối từ"), là một từ chính (chạy qua tất cả các từ khác) và sau đó là các từ khác (chạy qua từ chính).

  3. Bắt đầu trò chơi ô chữ với khối chữ đầu tiên ở vị trí trên cùng bên trái của trò chơi ô chữ.

  4. Đối với phần còn lại của các khối từ, bắt đầu từ vị trí dưới cùng bên phải của trò chơi ô chữ, di chuyển lên trên và sang trái cho đến khi không còn chỗ trống nào để điền. Nếu có nhiều cột trống hướng lên trên hơn bên trái, hãy di chuyển lên trên và ngược lại.


@holdoffhunger Bạn có phương pháp nào để hiển thị từ khóa không? Hộp có điền chữ cái?
Jon Glazer

@Jon Glazer: Thông thường, bạn gửi các phím ô chữ đến chính hàm, nhưng bạn có thể ghi lại ô chữ dưới dạng một dãy ký tự 2d, ngay sau đó var crosswords = generateCrosswordBlockSources(puzzlewords);. Chỉ cần bảng điều khiển ghi giá trị này. Đừng quên, có một "chế độ ăn gian" trong trò chơi, nơi bạn chỉ cần nhấp vào "Tiết lộ câu trả lời", để nhận giá trị ngay lập tức.
HoldOffHunger

Điều này tạo ra các câu đố với các từ "ngang qua" vô nghĩa ở những nơi có ô "xuống" liền kề và ngược lại. Câu đố ô chữ tiêu chuẩn không làm việc như thế này, mặc dù nó không phát huy tối đa mật độ.
Beejor
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.