prologwordle-game

Looping until the result is false in Prolog


I am trying to execute a loop until the output of the predicate is false using Prolog. I am implementing the "Wordle" game and here are some of the relevant predicates I am using:

available_length(L):-
       word(W,_),          %Passing through every word W resulting from predicate word(W,C). 
       atom_chars(W,Y),    %Adding the letters of the word W to list Y.
       length1(Y,L).       %Finding the length of the list.
length1([],0).
length1([_|Tail],L):-
       length1(Tail,Prev),
       L is Prev + 1.  

The previous predicate basically takes as an input an integer and checks whether there exists a word in the predicate word(W, C) having a length equal to that integer(where W is the word and C is the category). Shown below is the Knowledge Base of this predicate:


word(horse,animals).
word(panda,animals).
word(hello,greetings).
word(banana,fruits).
word(bison,animals).
word(hoard,collections).

This is how the "available_length" predicate works:


available_length(L): succeeds if there are words in the KB with length L.
Examples:
?- available_length(5).
true.    %becuase there exists words with length 5 in the KB(horse,panda,hello,bison,hoard).

?- available_length(9).
false.    %becuase there don't exist words with the length 9 in the KB.

As the game starts, it will ask the user to choose a category as well as a length for a word of his choice. Then based on what he chooses, a random word from the category he entered is chosen and the game starts giving the user a number of guesses for the word equal to the (length of the word + 1). However, if the user enters a length of a word that doesn't exist in the category he chooses, he should be prompted with a message saying that the length he choose doesn't exist and is allowed to choose again until he enters a length that exists.

I implemented another helper predicate shown at the bottom that uses the above predicate "available_length" to check whether the user entered a proper length that exists or not and if he doesn't, he should be shown a prompt message as mentioned above.

And this is the relevant part of the of the predicate that executes the game:


play:-
    write('The available categories are: '),  %Not implemented yet.
    nl,
    write('Choose a category: '),
    nl,
    read(Category),
    nl,
    write('Choose a length: '),
    nl,
    read(WordLength),      %Takes the length as an input from the user.
    check_length,          %Executes the helper predicate"check_length"shown below.
    Guesses is WordLength + 1,   %The rest is executed only if "check_length" succeeds.
    write('Game started. You have '),
    write(Guesses),
    write(' guesses.'),

The question is how do I keep prompting the user with the message until the output of the "available_length" predicate becomes true (meaning that the user entered a correct length). I already tried the following helper predicate, but it didn't work, it prompts the message whether I enter a length that exists or not:


check_length:-
    read(WordLength),
    (available_length(WordLength) = true;   %Loops until the length entered does exist.
     (write('There are no words with this length.'),  %Otherwise,this prompt message appear. 
      nl,
      write('Choose a length: '),     %The user is allowed to choose a length again.
      check_length)
    ).

Does anybody have a clue about how it is done? Another question is how to execute a predicate that chooses a random word in the category that the user chooses from the predicates of the KB. For example, if he chooses the "animals" category, one of the three words: horse, panda, and bison should be chosen randomly). The categories are shown again below:


word(horse,animals).
word(panda,animals).
word(hello,greetings).
word(banana,fruits).
word(bison,animals).
word(hoard,collections).



Solution

  • To check the availability of a word of a given length, you need to know which category it belongs to. Also, to determine the length of an atom, you can use the ISO predicate atom_length/2:

    available_length(Category, Length) :-
        (   word(Word, Category),
            atom_length(Word, Length)
        ->  true ).
    

    To read a term entered by the user, you can use the predicate:

    input(Prompt, Term) :-
        write(Prompt),
        read(Term).
    

    To repeat the input until a valid word category is entered, use the predicate:

    input_category(Category) :-
        (   input('Choose a category:\n', Category),
            word(_, Category)
        ->  true
        ;   write('Invalid category!\n'),
            input_category(Category) ).
    

    Example:

    ?- input_category(C).
    Choose a category:
    |: planets.
    Invalid category!
    Choose a category:
    |: animals.
    
    C = animals.
    

    To repeat the input until a valid word length is entered, for a given category, use the predicate:

    input_length(Category, Length) :-
        (   input('Choose a length:\n', Length),
            available_length(Category, Length)
        ->  true
        ;   format('Category "~w" has no word with this length.\n', [Category]),
            input_length(Category, Length) ).
    

    Example:

    ?- input_length(animals, L).
    Choose a length:
    |: 9.
    Category "animals" has no word with this length.
    Choose a length:
    |: 6.
    Category "animals" has no word with this length.
    Choose a length:
    |: 5.
    
    L = 5.
    

    To choose a random word from a given category, you can use the predicate random_member/2:

    random_word(Category, Word) :-
        findall(Word, word(Word, Category), Words),
        random_member(Word, Words).
    

    Examples:

    ?- random_word(animals, W).
    W = panda.
    
    ?- random_word(animals, W).
    W = horse.
    
    ?- random_word(animals, W).
    W = bison.
    

    SWI-Prolog defines random_member/2 as follows:

    random_member(X, List) :-
        must_be(list, List),
        length(List, Len),
        Len > 0,
        N is random(Len),
        nth0(N, List, X).