/* Wordle solver using CP in Picat. This is a Wordle solver using constraint modelling. It's a little strange to use CP here since it's not really a CP problem. Compare with the faster wordle4.pi which use the same logic in accept_word, but without the #=, #> constraints. Compare with my other Wordle solvers: Here are some times for one of the problems: - wordle.pi: The original model, foreach loops, quite fast solver. - wordle2.pi: Prolog style. The fastest solver. - wordle3.pi: using cl_facts and word/1. The slowest solver. - wordle4.pi: Using forloops but more compact than wordle.pi. Quite fast solver - wordle_cp.pi: (This model) Slower solver - wordle_dcg.pi: Using DCGs, including generating the words with a DCG. Slowest in total. Solver Solve time Total system time ----------------------------------------- wordle.pi 0.001..0.002s 0.047..0.52s wordle2.pi 0.0..0.001s(*) 0.035..0.038s(*) wordle3.pi 0.016..0.025s 0.055..0.060s wordle4.pi 0.001..0.002s 0.039..0.041s wordle_cp.pi 0.014..0.017s 0.55s..0.54s wordle_dcg.pi 0.0..0.001s 0.170..0.185s (*) Marks the best times. This model was created by Hakan Kjellerstrand, hakank@gmail.com See also my Picat page: http://www.hakank.org/picat/ */ import cp. main => go. go ?=> wordle("...n.",["","","","",""],"slat"), % -> [crone,brine,crony,briny,prone,corny,borne,prune,drone,phone,brink,phony,bound,pound,grind,frond,found,bring,drink,being,whine,fiend,chunk,whiny,prong,mound,horny,urine,round,drunk,irony,doing,wound,hound,opine,wring,rhino,downy,wrong,wrung,ebony,ovine,dying,young,owing,eying,vying,eking,penne,penny,bunny,funny,going,ninny,ozone,icing] % wordle(".r.n.",["","","","",""],"slatcoe"), % -> [briny,brink,grind,bring,drink,drunk,wring,wrung] % wordle("...st",["s","","","",""],"flancre"), % -> [moist,hoist,ghost,midst,joist,joust,boost,twist] % wordle(".r.n.",["","","","",""],"slatcoe"), % -> [briny,brink,grind,bring,drink,drunk,wring,wrung] % wordle(".r.n.",["","","","",""],"slatcoebiy"), % -> [drunk,wrung] % wordle(".run.",["","","","",""],"slatcoebiydk"), % -> [wrung] % wordle(".l...",["","","a","","t"],"sn"), % -> [alter,ultra,altar] % wordle(".....",["","","","",""],""), % -> All words... nl. go => true. % % wordle(Words,CorrectPos,CorrectChar,NotInWord) % - Words: the wordlist/candidates % - CorrectPos: correct character in correct position, % Example: n is in position 4 and t is in position 5: "...nt": % - CorrectChar: correct character but in wrong position. % Example: a is not in position 1, l is not in position 2: ["a","l","","",""] % - NotInWord: characters not in word. % Example: s, a, and t are not in the word: "sat" % wordle(CorrectPos1, CorrectChar1, NotInWord1) => N = 5, println($wordle(CorrectPos1, CorrectChar1, NotInWord1)), CorrectPos = [V : C in CorrectPos1, V = cond(C != '.', convert(C), 0)], CorrectChar = [convert_list(Cs) : Cs in CorrectChar1], NotInWord = convert_list(NotInWord1), println(correctPos=CorrectPos), println(correctChar=CorrectChar), println(notIntWord=NotInWord), % File = "unixdict.txt", % 3161 5 letter words. Is this the smallest word list? File = "wordle_small.txt", % 2314 words % File = "wordle_large.txt", % 12971 words % File = "eng_dict.txt", % 9336 5 letter words % File = "words_lower.txt", % 21830 English 5 letter words WordsAlpha = [W : W in read_file_lines(File), W.len == N], Words = convert_words(WordsAlpha), % Words = [W : W in read_file_lines(File), length(W) == N], NumWords = Words.len, println(numWordsInDict=NumWords), Candidates = [], foreach(I in 1..NumWords) if accept_word(Words[I], CorrectPos, CorrectChar, NotInWord) then Candidates := Candidates ++ [WordsAlpha[I]] end end, Candidates := sort_candidates(Candidates), println(candidates=Candidates), println(len=Candidates.len), (Candidates != [] -> println(suggestion=Candidates.first()) ; true), nl. % % The main engine: % Loop through all words and check if they are in the scope. % accept_word(Word, CorrectPos, CorrectChar, NotInWord) => N = Word.len, foreach(I in 1..N) if CorrectPos[I] > 0 then CorrectPos[I] #= Word[I] end, if CorrectChar[I] != [] then foreach(C in CorrectChar[I]) Word[I] #!= C, sum([Word[J] #= C : J in 1..N, J != I]) #> 0 end end end, foreach(C in NotInWord) sum([Word[I] #= C : I in 1..N]) #= 0 end. % solve(Word). % % Sort the candidates. % sort_candidates(Candidates) = Sorted => % The probability order for each position in the word. % Reversed and with missing chars. % See wordle.pi (go2/0) for the method to get this. Alphas = ["zyjkquinovhewlrmdgfaptbcsx", "zqfkgxvbsdymcwptnhulieroaj", "qjhzkxfwyvcbpmgdstlnrueoia", "xyzbwhfvpkmdguotcrilasnejq", "uzxbiwfcsgmpoakdnhlrtyejqv" ], score_words(Candidates,Alphas,[],Scores), Sorted = [W : [_S=W] in Scores.sort_down]. % % Score all words % score_words([],_Alpha,Scores,Scores). score_words([Word|Words],Alphas,Scores0,[[Score=Word]|Scores]) :- % We prefer words with distinct characters Score1 = cond(Word.len==Word.remove_dups.len,100,0), score_word(Word,Alphas,Score1,Score), score_words(Words,Alphas,Scores0,Scores). % Score each character in a word score_word([],_Alpha,Score,Score). score_word([C|Cs],[Alpha|Alphas],Score0,Score) :- nth(N,Alpha,C), % find the position of this character S = N / 2, Score1 = Score0 + S, score_word(Cs,Alphas,Score1,Score). % Print an empty board empty() => println("wordle(\".....\",[\"\",\"\",\"\",\"\",\"\"],\"\")"). % Convert words to integers convert_words(Words) = [ convert_list(Word) : Word in Words]. convert_list(W) = [convert(C) : C in W]. convert(C) = ord(C)-96.