This project is part of a growing collection of Prolog projects suitable for those learning the language, more projects along with the source code is available here.
Let’s start with the basic rules of Rock, Paper, Scissors.
%! rule(+Item, -Verb, +Item2) is det. % Rules of rock, paper, scissors. rule(rock, blunts, scissors). rule(scissors, cut, paper). rule(paper, wraps, rock). rule(Item, draws, Item).
These determine what beats what and uses the same variable,
the draws rule to ensure they’re both the same item. We’ll get to using
these rules in a bit. First we also need the rules of play. What is a
game? What is a turn?
%! game is det. % A game consists of a turn and a query to play again. game :- turn, play_again. %! play_again is det. % A query to play again, returns to game if it doesn't read n. play_again :- writeln("Play again?"), read(n), ! ; game. %! turn is det.\ % A turn consists of a countdown, player and computer both shoot, % the shots are compared and the winner is announced. turn :- countdown(3), player_shoot(Item1), computer_shoot(Item2), compare_shoot(Item1, Item2, Rule), congratulate(Item1, Item2, Rule).
So game is as per the comment and the code. It’ll take turns and ask if
you want more.
play_again/0 is a bit clever, it asks the user if the
want to play again and tries to read the letter ‘n’ from the command
?- play_again. Play again? |: n. true
As soon as it reads, it cuts (
!) the back tracking so if it doesn’t
read ‘n’, it doesn’t try again, but instead it goes to the “or” case and
Finally we’ve described what a turn consists of, but that’s a lot of functors that we’ve not written yet, so let’s write them!
A Turn for the Better
Let’s do them in order of play, starting with countdown. For this we could go the easy route:
countdown :- writeln("3. 2. 1. Shoot!").
But where’s the fun in that! Instead, for the sake of learning, I
introduce a generic
countdown/1 predicate so we can countdown from
any number we choose. When
countdown/1 reaches 0, it’ll write
“Shoot!”, before then it’ll write out the numbers, reducing the count as
%! countdown(+N) is det. % countdown from N to shoot, write to stdout. countdown(0) :- writeln("Shoot!"). countdown(N) :- format("~d. ", N), M is N - 1, countdown(M).
There’s an academic difference between these two definitions of countdown that you may find sways you in favour of the more complex version. The first defines a countdown as a string of text written to stdout. The second is a definition for what a countdown is, the reduction of numbers to 0. Plus there’s the whole code reuse thing.
player_shoot/1. For this we want to read in from the user
which item they’d like to shoot. But they might make a mistake, so we
need to check if they’re input is valid, and if not ask for it again. We
check for validity using
%! player_shoot(-Item) is nondet. % read the players choice of item, if it's not recognisable ask again % until we get rock, paper, or scissors. player_shoot(Item) :- read(Item), member(Item, [rock, paper, scissors]). player_shoot(Item) :- player_shoot(Item).
computer_shoot/1, which needs to choose a random item,
luckily there’s a built in,
random_member/2, that’s ideal for this.
%! computer_shoot(-Item) is det. % get a random item for the computer. computer_shoot(Item) :- random_member(Item, [rock, paper, scissors]).
So we’ve got our two items, now let’s make use of those rules we made earlier to see which applies. We don’t know who’s chosen what, or what order they’ll be in. In non-declarative programming we’d have to write an algorithm now to work out who beat who and find the correct rule. But in declartive programming, we just write the two ways it can be true:
%! compare_shoot(+Item1, +Item2, -Rule) is det. % use the rules (`rule/3`) to find which one to apply compare_shoot(Item1, Item2, Rule) :- Rule = rule(Item1, _, Item2), Rule. compare_shoot(Item1, Item2, Rule) :- Rule = rule(Item2, _, Item1), Rule.
, Rule. at the end of each body is to make sure that Rule
actually unifies with an asserted
rule/3. Without it you’ll get
rule(paper, _326, scissors) as your rule.
Finally we need to (hopefully!) congratulate our player. There are three outcomes to the game: win, loose or draw. We’ll need to declare what is “true” in each case, we’ll use pattern matching in the head to determine which case we’re handling. No need to write an algorithm!
%! congratulate(+Item1, +Item2, +Rule) is det. % Write out the results of the turn for the player. congratulate(I, I, rule(I, draws, I)):- format("You both shoot ~w, it's a draw.~n", I), !. congratulate(Item1, Item2, rule(Item1, Verb, Item2)):- format("~w ~w ~w.~n", [Item1, Verb, Item2]), writeln("You Win!"), !. congratulate(Item1, Item2, rule(Item2, Verb, Item1)):- format("~w ~w ~w.~n", [Item2, Verb, Item1]), writeln("You Loose"), !.
That’s it, you can play away. Load it up in
swipl and query it:
What’s next? How about Rock, Paper, Scissors, Lizard, Spock? Scoring? Best of three?