User2000
User2000

Reputation: 119

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).


Upvotes: 2

Views: 430

Answers (2)

TA_intern
TA_intern

Reputation: 2436

Your question is too long. Way too long. You have also made several questionable design choices already.

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).

With your database, an easy way to do this in Prolog is:

available_length(Length) :-
    \+ \+ ( word(W, _), atom_length(W, Length) ).

You look for a word that is Length long in the database, and if you find one, you succeed. No loops, no recursion, just logic. Look up what "negation as failure" means!

The question is how do I keep prompting the user with the message until the output of the "available_length" predicate becomes true

There are at least two ways to do that. One is the venerable "failure-driven loop", look up what that means. You do need repeat/0 for this. Skipping all the writes for the sake of the example:

?- repeat,
   (   read(N),
       available_length(N)
   ->  true,
       !
   ;   fail
   ).
|: 3.
|: 7.
|: 5.

N = 5.

This is also literally the title of your very long question.

The other way is to use a recursive definition as this answer showed you. You should prefer a recursive definition over a failure-driven loop if you need to keep a state.

There are too many ways to pick a word at random depending on your use case. But this is already a new question so I won't answer it here.

Upvotes: -2

slago
slago

Reputation: 5519

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).

Upvotes: 1

Related Questions