Hangman Game in SWI Prolog - algorithm

I'm trying to make a simple hangman game in SWI Prolog.
Since we made this program run can you help me enchance the program with the following:
1) By keeping up with the letters that have been guessed so far. If the user guesses a letter that has already been guessed, the program should say 'You guessed that!' and just continue the game.
2) Lastly, add a counter that counts the number of incorrect guesses and quits the game when a certain number is reached. The program should tell the user that they lose, display what the phrase really was, and terminate. Duplicate guesses should not be counted as wrong.
I would like to thank everyone who helped me so far. This means a lot to me.
I provide you with the code and comments.
% This top-level predicate runs the game. It prints a
% welcome message, picks a phrase, and calls getGuess.
% Ans = Answer
% AnsList = AnswerList
hangman:-
getPhrase(Ans),
!,
write('Welcome to hangman.'),
nl,
name(Ans,AnsList),
makeBlanks(AnsList, BlankList),
getGuess(AnsList,BlankList).
% Randomly returns a phrase from the list of possibilities.
getPhrase(Ans):-
phrases(L),
length(L, X),
R is random(X),
N is R+1,
getNth(L, N, Ans).
% Possible phrases to guess.
phrases(['a_picture_is_worth_a_thousand_words','one_for_the_money','dead_or_alive','computer_science']).
% Asks the user for a letter guess. Starts by writing the
% current "display phrase" with blanks, then asks for a guess and
% calls process on the guess.
getGuess(AnsList, BlankList):-
name(BlankName, BlankList),
write(BlankName),
nl,
write('Enter your guess, followed by a period and return.'),
nl,
read(Guess),
!,
name(Guess, [GuessName]),
processGuess(AnsList,BlankList,GuessName).
% Process guess takes a list of codes representing the answer, a list of codes representing the current
% "display phrase" with blanks in it, and the code of the letter that was just guessed. If the guess
% was right, call substitute to put the letter in the display phrase and check for a win. Otherwise, just
% get another guess from the user.
processGuess(AnsList,BlankList,GuessName):-
member(GuessName,AnsList),
!,
write('Correct!'),
nl,
substitute(AnsList, BlankList, GuessName, NewBlanks),
checkWin(AnsList,NewBlanks).
processGuess(AnsList, BlankList,_):-
write('Nope!'),
nl,
getGuess(AnsList, BlankList).
% Check to see if the phrase is guessed. If so, write 'You win' and if not, go back and get another guess.
checkWin(AnsList, BlankList):-
name(Ans, AnsList),
name(BlankName, BlankList),
BlankName = Ans,
!,
write('You win!').
checkWin(AnsList, BlankList):-
!,
getGuess(AnsList, BlankList).
% getNth(L,N,E) should be true when E is the Nth element of the list L. N will always
% be at least 1.
getNth([H|T],1,H).
getNth([H|T],N,E):-
N1 is N-1,
getNth(T,N1,E1),
E=E1.
% makeBlanks(AnsList, BlankList) should take an answer phrase, which is a list
% of character codes that represent the answer phrase, and return a list
% where all codes but the '_' turn into the code for '*'. The underscores
% need to remain to show where the words start and end. Please note that
% both input and output lists for this predicate are lists of character codes.
% You can test your code with a query like this:
% testMakeBlanks:- name('csc_is_awesome', List), makeBlanks(List, BlankList), name(Towrite, BlankList), write(Towrite).
makeBlanks(AnsCodes, BlankCodes) :-
maplist(answer_blank, AnsCodes, BlankCodes).
answer_blank(Ans, Blank) :-
Ans == 0'_ -> Blank = Ans ; Blank = 0'* .
% substitute(AnsList, BlankList, GuessName, NewBlanks) Takes character code lists AnsList and BlankList,
% and GuessName, which is the character code for the guessed letter. The NewBlanks should again be a
% character code list, which puts all the guesses into the display word and keeps the *'s and _'s otherwise.
% For example, if the answer is 'csc_is_awesome' and the display is 'c*c_**_*******' and the guess is 's', the
% new display should be 'csc_*s_***s***'.
% You can test your predicate with a query like this:
% testSubstitute:- name('csc_is_awesome', AnsList), name('c*c_**_*******', BlankList), name('s',[GuessName]), substitute(AnsList, BlankList, GuessName, NewBlanks),
% name(Towrite, NewBlanks), write(Towrite).
% Also, since the predicate doesn't deal directly with character codes, this should also work:
% substitute(['c','s','c'],['c','*','c'],'s',L). L should be ['c','s','c'].
substitute(AnsCodes, BlankCodes, GuessName, NewBlanks) :-
maplist(place_guess(GuessName), AnsCodes, BlankCodes, NewBlanks).
place_guess(Guess, Ans, Blank, Display) :-
Guess == Ans -> Display = Ans ; Display = Blank.

maplist/3 & maplist/4 apply their first argument (a predicate of appropriate arity) against all elements of other arguments lists, then your makeBlanks could be:
makeBlanks(AnsCodes, BlankCodes) :-
maplist(answer_blank, AnsCodes, BlankCodes).
answer_blank(Ans, Blank) :-
Ans == 0'_ -> Blank = Ans ; Blank = 0'* .
and substitute:
substitute(AnsCodes, BlankCodes, GuessName, NewBlanks) :-
maplist(place_guess(GuessName), AnsCodes, BlankCodes, NewBlanks).
place_guess(Guess, Ans, Blank, Display) :-
Guess == Ans -> Display = Ans ; Display = Blank.
edit:
on additional requests: 1) can be solved with an additional predicate:
alreadyGuessed(Guess, AnsCodes) :-
memberchk(Guess, AnsCodes).
while regards 2) getGuess and processGuess together make a loop, that will just terminate when no more calls happen. Remove the last rule of checkWin, add an argument as counter to keep track of failed guesses, and extend processGuess to signal failure:
processGuess(AnsList, BlankList, _, CountFailed) :-
( CountFailed == 5
-> format('Sorry, game over. You didn\'t guess (~s)~n', [AnsList])
; write('Nope!'),
CountFailed1 is CountFailed + 1,
getGuess(AnsList, BlankList, CountFailed1)
).

Why so many cuts? Check out SWI library predicates that may be useful to you: memberchk/2, format/2 and nth1/3.

Related

Prolog predicate that extracts all words immediately following a word in a given list of words

As it says in the title, i need to get all the words after a specifc word in prolog, for example:
?- find([in, house, car, in, shop, no, more, apples, in, table], in , X).
X = [house, shop, table] ;
No
This is the code i've written so far:
find([H,H_1|_],H,[H_1]).
find([Head,Head_1|Tail], Term, [Head|Result]) :-
find(Tail, Term, Result).
After i run it, i get:
X = [house] ;
X = [in, car, shop, more, table] ;
No
There is nothing better than writing simple programs to learn a language. After you grasp the basics, you could be interested into more idiomatic approach:
find(L,W,Fs) :- findall(F, append(_,[W,F|_],L), Fs).
The main problem is probably located here:
find([H,H_1|_],H,[H_1]).
This code unifies the list with the first element after the match. You then unify the third parameter (which is here used as a "result") with a list containing the single occurrence.
Note furthermore that it is also possible that we reached the end of the list. So in that case the predicate will fail as well.
Basically there are four cases here:
we reach the end of the list, the "result" parameter should unify with the empty list;
we found the element and there is a next element (that is also a match), we perform one step and continue our search;
we found the element and there is a next element (that is not a match), we add that element and continue our search;
the head does not match, we continue our search.
We can implement these possibilities as:
find([],_,[]). % we reach the end of the list
find([H,H|T],H,T2) :- % there is a match, the successor is also a match
find([H|T],H,T2). % perform one hop
find([H,N|T],H,[N|T2]) :- % there is a match, add the next element
N \= H,
find(T,H,T2). % and continue
find([N|T],H,T2) :- % there is no match
N \= H,
find(T,H,T2). % we continue
This produces:
?- find([in, house, car, in, shop, no, more, apples, in, table], in , X).
X = [house, shop, table] ;
false.
?- find([a,b,c],c,X).
false.
?- find([a,b,c,a,d],a,X).
X = [b, d] ;
false.
?- find([a,a,b],a,X).
X = [b] ;
false.
(Yes/No are in swi-prolog true/false).

Prolog return a list which contains only elements which are equal to head of the list

Hello I would like to ask a doubt I have with the following code:
principio([],[]).
principio([H],[H]).
principio([H,_|_],[H]).
principio([H,H|C],P) :-
principio([H|C],R),P=[H|R].
I would like a way to get from:
?- principio([222,333,101,202,12,222,13,222],X).
X = [222,222,222]
But in this moment I get just the head:
X = [222]
So, to keep it clear I'd like: all successive occurrences of the first element as a list.
My doubt is what does this assignment P=[H|R] why not to put just:
principio([H,H|C],P) :-
principio([H|C],P)
Also, how would you try to modify this to get the result I asked for?
Thank you
Here is two ways how you can narrow down the problem. 1st, start from an unexpectedly failing query. 2nd, start from a query that should fail but rather succeeds.
1st Diagnose unexpected incompleteness
Determine a most specific failing query
?- principio([222,333,101,202,12,222,13,222],[222,222,222]).
false.
Generalize the query
... as much as possible. I could do this manually, or I could let Prolog do the work for me. Here I use library(diadem):
?- use_module(diadem).
true.
?- principio([222,333,101,202,12,222,13,222],[222,222,222]).? Gen.
Gen = principio([222, 333|_], [_, _|_])
; Gen = (dif(A100, B100), principio([A100, B100|_], [_, _|_]))
; ... .
In other words: Not only does your original query fail, but also this generalization fails! Here, we only insist that the first two elements are different, and that the resulting list contains at least two elements — no matter which!
?- dif(X, Y), principio([X,Y|_],[_,_|_]).
Generalize your program
:- op(950, fy, *).
* _P_0.
principio([], _/*[]*/).
principio([_H], _/*[H]*/).
principio([H,_|_],[H]).
principio([H,H|C],P) :-
* principio([H|C],R),
* P=[H|R].
The error must reside in the little remaining part of your program. No need to read any further!
The problem is that for a list starting with two different elements you only have the clause principio([H,_|_],[H]).. So this part has to be generalized somehow.
2nd Diagnose unexpected unsoundness
Another way of finding the error would be to start with the unexpected solution:
?- principio([222,333,101,202,12,222,13,222],[222]).
true. % incorrect !!
And then reduce the size of the query as much as possible.
?- principio([222,222],[222]).
true. % incorrect !!
Now, specialize your program inserting false as long as above query succeeds:
principio([],[]) : - false.
principio([H],[H]) :- false.
principio([H,_|_],[H]).
principio([H,H|C],P) :- false,
principio([H|C],R),
P=[H|R].
The remaining visible part is the culprit! We have to revise it. What it says is:
Any list starting with two elements corresponds to the list with the first element only.
principio([],[]).
principio([H],[H]).
principio([H,D|Xs], [H|Hs]) :-
dif(H,D),
principio([H|Xs],[H|Hs]).
principio([H,H|Xs],[H|Hs]) :-
principio([H|Xs],Hs).
In addition to the very nice answer provided by #false (+s(0)), I would point out the possibility to use DCGs for the task. They usually yield easily readable code when describing lists (see comments beside the grammar rules):
principio([H|T],Hs) :-
phrase(heads([H|T],H),Hs).
heads([],_H) --> % in the empty list
[]. % there's no element matching H
heads([H|Xs],H) --> % if the head of the list matches H
[H], % it's in the list
heads(Xs,H). % same for the tail
heads([X|Xs],H) --> % if the head of the list is
{dif(X,H)}, % different from H it's not in the list
heads(Xs,H). % same for the tail
Thus your example query yields the desired result:
?- principio([222,333,101,202,12,222,13,222],X).
X = [222,222,222] ? ;
no

prolog, taking only first answer

I've read some topics on this subjects, but I can't get it to work. Assume I have following predicate:
wins(L,K,X):-member(X,L),X>=K.
wins(L,K,X):-member(X,L),X<K,K1 is K-X,\+wins(L,K1,_).
It doesn't really matter what it does, the thing is that for example:
?- wins([1,4],1,X).
X = 1;
X = 4;
false.
Now, this predicate helps me to check to find if some condition can be fulfilled and gives the numbers that allow to achieve this. That's OK, but now I would only like to get the first asnwer, which is X = 1 here and then make Prolog stop searching. Yet, I can't use !, becuase then it finds the X = 1 and return false. And I need it to return true, in case I have other following conditions based on resulting X, like:
play(L,K,X):- member(X,L),K1 is K-X,K1>0,wins(L,K1,Y),K2 is K1-Y, ...
Then in ... I want to be able to use only the first one result from win predicate. I need to use SWI-Prolog.
Thanks!
EDIT
Maybe I should expand it more, because once() is working, but not exactly as I would like to. I mean if I wanted to wait for the user input, let's say:
play(L,K,X):-
member(X, L),
K1 is K-X,
K1 > 0,
once(wins(L, K1, Y)),
K2 is K1 - Y,
write('Something: '),
read(Z),
...
In that case on the output I would get:
Something: |:
After I press any portion of text (to read Z), let's say 'THIS', it goes like:
SomethingTHIS.
What I mean is that some part of the previous text put into output disappears (in this case it's ': ' that's missing).
Any idead how I could fix this?

Why do some prolog predicates 'end' automatically, while some require me to press enter/'.' again?

Im using SWI-Prolog and when I try to run some predicates I write, they put a full stop at the end of my answer automatically and go straight to the next line. While some require me to either press enter or put the fullstop there myself. Why is this?
% range(1,5,X) -> X = [1,2,3,4,5]
range(X, X, [X]).
range(Low, High, [Low | Xs]) :-
Low =< High,
Low1 is Low+1,
range(Low1, High, Xs).
This is an example of one I need to 'manually' either press enter or '.' to finish off, it also returns false if I press ';'. But I can't see why it would return false.
When you press ;, you are telling PROLOG that the last result is not good enough for you and it should backtrack to the last decision junction and take another branch. Prolog will not allow you to press ; if there is no decision points. In case of the sample function, the decision is taken when two of the parameters are equal, and both cases range(X, X, [X]). and range(Low, High, [Low | Xs])... are valid choices. The first result you are give will correspond to range(X, X, [X]). and will return [X], which is the last element of the range. If we assume that the original query was range(1,2,X), the search will end with range(2,2,[2]).. But when you press ; it will go to:
range(2, 2, [2|Xs]) ....
which will perform recursive query to
range(3, 2, Xs) ....
Which will eventually fail on Low =< High
and produce the false result.

Explain what this Prolog program does?

I am working on Prolog and I came across the below program. When I execute this program with a query for example: mysterious([2,3,4,5,6,7,8],L), it gives me the answer 20. I don't understand the flow of getting this result. Can anyone help me to understand this code?
How does it produce the answer 20 for the above mentioned query?
mysterious([],0).
mysterious([X],X).
mysterious([X,Y|Xs], Res) :-
mysterious(Xs, Res1),
Res is X + Res1.
Because this is such a basic program, I will give you a hint. First off, this:
foo([]).
foo([X]).
foo([X,Y|Rest]) :-
foo(Rest).
could be also written as:
foo([]).
foo([First|Rest]) :-
bar(Rest, First).
bar([], Last).
bar([This|Rest], Prev) :-
foo(Rest).
It is unnecessarily verbose, but more explicit. In your case, however, it makes something obvious:
mysterious([], 0).
mysterious([X|Rest], Result) :-
myst_1(Rest, X, Result).
myst_1([], Result, Result).
myst_1([_Y|Rest], X, Result) :-
mysterious(Rest, Result1),
Result is X + Result1.
Something which you did not mention in your question is that the compiler must have given you a "singleton variable" warning on the Y in the last clause of mysterious/2. This should have been a strong indication of what is going on already.
I can help you by commenting lines, maybe you will understand the prolog a little more, let's start:
Prolog programs describe relations, defined by means of clauses. Pure Prolog is restricted to Horn clauses. There are two types of clauses: facts and rules. (by wikipedia)
% comment in prolog starts with '%'
mysterious([],0). % its called 'predicate', something like functions in imperative
% languages. it has two arguments: [], and 0. and it is ended
% by dot (.). Clauses with empty bodies are called facts.
% (this is fact).
mysterious([X],X). % is actually a "rule" that says, "if the list has one element,
% then the result is just that element". (by #mbratch)
mysterious([X,Y|Xs], Res) :- % now, when argument 1 is list which looks like
% [SomeElement, NextElement|LastElements],
mysterious(Xs, Res1), % call 'mysterious' with only LastElements
% and variable Res1 (skip first two elements of list)
Res is X + Res1. % then add first element to Res1.
% it calculates the sum of every second element of list, beginning on first.

Resources