2

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


User2000
  • 119
  • 2
  • 8
  • Can use -> as in e.g. https://stackoverflow.com/questions/2849045/if-in-prolog – brebs Apr 19 '22 at 17:22
  • @brebs Thank you for your response, but my issue is not about how to execute an if condition, it is rather about how to check if the output of the predicate "available_length" is true or false as mentioned in the question. – User2000 Apr 19 '22 at 17:33
  • @User2000 "my issue is not about how to execute an if condition, it is rather about how to check IF" ... sounds like a place to use IF, doesn't it? – TessellatingHeckler Apr 19 '22 at 20:04
  • @TessellatingHeckler Please check the line "available_length(WordLength) = true " in the predicate "available_length" above. I am actually trying to check if the this predicate returns true or false because if returns true, this means the user entered a length that exists, so the loop should stop. Otherwise, he should be prompted with a write message and allowed to re enter the length. – User2000 Apr 19 '22 at 20:20
  • @TessellatingHeckler The thing is this line of code doesn't work, Prolog always assumes the entered length doesn't exist even if it does, so I am asking how can I check the output that's all. Also, I would really appreciate it if you can help me write a predicate that chooses a random word from a certain category based on the category the user chooses. – User2000 Apr 19 '22 at 20:21

2 Answers2

1

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).
slago
  • 5,025
  • 2
  • 10
  • 23
  • The way you close your parens is giving me headache. Surely there must be a better way. – TA_intern Apr 20 '22 at 06:31
  • @slago I can't thank you enough man, you've been actually very helpful to me! I just have one last question, does Prolog have nested loops? I mean an if-else inside another if-else? And then again I really appreciate your help and effort! – User2000 Apr 20 '22 at 14:23
  • @User2000 Yes, it is possible to nest conditional constructs. Please, see https://www.swi-prolog.org/pldoc/man?predicate=-%3E/2. – slago Apr 20 '22 at 17:21
  • Thanks brother!@slago, – User2000 Apr 20 '22 at 18:41
  • @TA_intern The *best style* is just a matter of personal opinion. "*C style formatting looks silly in Lisp*" [https://ccrma.stanford.edu/CCRMA/Courses/AlgoComp/cm/doc/contrib/lispstyle.html] and **I** think this applies to Prolog as well. – slago Apr 21 '22 at 13:26
  • Style is a matter of convention, not personal opinions, when we talk about anything but art. "Convention" in this context means "majority opinion". But of course you might consider yourself a Prolog artist; then just ignore my opinion. – TA_intern Apr 22 '22 at 05:30
  • @TA_intern Perhaps I was influenced too much by books like "The Art of Computer Programming" by Knuth and "The Art of Prolog" by Sterling & Shapiro (which I highly recommend reading, by the way). Lol. – slago Apr 22 '22 at 09:58
  • @slago you are making assumptions about what books I have read, my lolling friend :-D – TA_intern Apr 22 '22 at 13:25
  • @TA_intern Indeed! :)) – slago Apr 22 '22 at 15:41
-2

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.

TA_intern
  • 2,222
  • 4
  • 12
  • Speaking of personal opinion, I think `?- once((repeat, read(N), available_length(N))).` looks better than the pattern you used in your answer (no need for conditional construct, `true`, `fail` or explicit cut) . – slago Apr 21 '22 at 13:42
  • @slago Your opinions again :-D this is a "failure driven loop" and it is very important that Prolog practitioners know it and recognize it. It is (in my personal opinion) usually less useful than the tail-recursive solution that you showed in your own answer. But it is useful to have seen it and understood it. – TA_intern Apr 22 '22 at 05:32
  • Have you noticed that the pattern `once((repeat, Action, Condition))` is a failure-driven loop too? – slago Apr 22 '22 at 10:56
  • @slago There are reasons why it isn't written like this, usually. I suggest that you write a question if you can't figure out on your own what those reasons might be. – TA_intern Apr 22 '22 at 13:26
  • Indeed! --- :)) – slago Apr 22 '22 at 15:43