Computer Science 60
Principles of Computer Science
Spring 2005


Assignment 9: Fun (and Games) with Prolog!
Due Monday, March 28 by 11:59 PM

This assignment is all Prolog all the time! The assignment has five parts. The first three parts are based on material from Tuesday's lecture. The last two parts (Towers of Hanoi and parsing) will be explained in class on Thursday -- give or take a day.

Please be sure to name your "functions" exactly as we've specified in the assignment. This is important since some of your code will be tested by an automatic code tester which will look for precisely these function names. (Keep in mind, too, that they're not really functions, but prolog predicates. This assignment might refer to them as functions at times, however...)

Note also that Prolog has some built-in "functions". In particular, Prolog has a built-in length(L, N) which is true if and only if list L has length N. Prolog also has a built-in append(A, B, C) which is true if and only if the result of appending list B to the end of list A results in list C. You are welcome to use these built-ins. You may also want to define your own helper "functions" as needed or other predicates that we defined in class, e.g., perm (that generates permutations), and member.

Part 1: The Simpsons and Prolog (5 Points)

In the directory /cs/cs60/assignments/assignment9 you will find a file called part1.pl. This file contains an expanded database of the Simpsons family. You should make a copy of this file and add the following rules to this file. You may also add any additional helper rules that you wish, including anything that we looked at in class. However, please don't change the facts about parenthood/relationships among the Simpsons! Recall that "," is the symbol for AND, ";" is the SYMBOL for OR, and "\+" is the symbol for NOT. Please look at the class notes for other Prolog syntax. Your file should be called part1.pl and submit it using cs60submit.

Part 2: Lists in Prolog (15 Points)


Here, you will write three Prolog rules for lists. Please submit these rules in one file called part2.pl using cs60submit. You may use any helper rules that you like. In particular, some of the rules that we saw in class may be useful to you here. You may implement and use any of them.

Part 3: The Game of 24! (30 Points)

This problem asks you to use Prolog to provide a solver for a generalized version of the game "Twenty-Four". In the original game, players view a card with four numbers on it and try to make an arithmetic combination of the numbers using the operators +, -, *, / so that the result is 24 (parentheses are allowed but are implicit). Each number must be used exactly once. Each operator can be used any number of times.

In our generalization of the game, the fixed number 24 is replaced with a variable, the set of operators is specified in a list, and the list of numbers can have any length, not just 4. (By definition, no result can be made if the list is empty.)

Define a 4-ary predicate solve such that

     solve(Ops, Values, Result, Tree)
  
will solve for an arithmetic tree named Tree using operators Ops among the integers in Values to give the final value Result. For example,

 | ?- solve(['+', '*', '-'], [2, 3, 4, 5], 24, Tree).

 Tree = [*,[+,[-,3,2],5],4] 
 

This means that we have found a solution Tree using operators +, *, and - on that will combine with set of numbers [2, 3, 4, 5] to yield 24. You may assume that the set of operators will always be a subset of ['+', '*', '-', '/'] and that '/' denotes integer division. In prolog, '/' is performed using // (which is why 1-line comments in prolog are prefaced by % and not //).

Here is an eval predicate (for evaluating trees of opreations) that will be helpful in solving this problem. You may copy this code and use it freely. It is also available for you to copy from /cs/cs60/assignments/assignment9/part3.pl. You may also use any of Prolog's built-in rules and any helper rules that you like (including those that we wrote in class).

    eval(R, R) :- number(R).
    eval(['+', A, B], R) :- eval(A, AR), eval(B, BR), R is AR + BR.
    eval(['*', A, B], R) :- eval(A, AR), eval(B, BR), R is AR * BR.
    eval(['-', A, B], R) :- eval(A, AR), eval(B, BR), R is AR - BR.
    eval(['/', A, B], R) :- eval(A, AR), eval(B, BR), BR\==0, R is AR // BR.
 

Here are some other examples of solve in action:

  ?-  solve(['+'], [1, 1, 1, 1], 24, Tree).

  no.


  ?-  solve(['+'], [24], 24, Tree).

  Tree = 24


  ?-  solve(['+', '*', '-'], [1, 2, 3, 4], 24, Tree),

  Tree = [*,1,[*,2,[*,3,4]]]
 
Notice that a plain integer is the simplest possible solution tree (not [24]), and there are many more answers to the third example above.

Please submit this part of the assignment in a file called part3.pl.

Part 4: Towers of Hanoi (30 Points)

In this part of the assignment, you will write a Prolog program to solve the Towers of Hanoi puzzle. Please submit your solution in a file called part4.pl. You should carefully review the "alien, spampede, and spam" solution that we examined in class before embarking on this program. The big ideas in the two programs are very similar. Fortunately though, this Hanoi program is considerably shorter. (You should expect your program to be about 20 lines of Prolog code, not including comments and spacing between rules.) I would suggest starting from the "alien, spampede, and spam" code and altering it to create your hanoi-solving program.

Your solution should specify valid moves in this puzzle and Prolog should use these rules to infer correct solutions to the puzzle. Here are the details:

Part 5: Grammars in Prolog (20 Points)

In Assignment 7 you wrote a recursive descent parser in rex for a grammar for arithmetic expressions involving addition, multiplication, and exponentiation. In this assignment you will write a general "parse engine" in Prolog which will allow you to determine if a given string can be parsed by virtually ANY grammar that you give it! Amazing, but true!

Here, we won't ask for a parse tree, but we DO want to know if a particular input can be derived by a given grammar. In other words, we just want to know if there EXISTS a parse tree for a given string using the given grammar. For example, IF we had a grammar for the English language, we would be able to ask our parser if a particular sentence was valid in English. The neat thing is that we won't tell Prolog how to parse, we'll just tell it what a grammar is and what string we're interested in and ask: "Yo Prolog, can this string be parsed by the given grammar?"

Take a look at the files g0.pl,g1.pl, g2.pl, g3.pl and g4.pl supplied to you in /cs/cs60/assignments/assignment9/part5/. Each of these files specifies a different grammar. A grammar is specified by declaring the variables, terminals, and rules.

For example, consider the grammar for valid additions of 0's and 1's. (This is the grammar specified in the file g0.pl.)
s -> v + s | v
v -> 0 | 1 

Here, s and v are the variables and 0 and 1 are the terminals. Notice that no upper-case letters are used. This is important, since Prolog uses upper-case letters to represent its own variables.

You can see how this grammar is specified in file gr0.pl Our convention is as follows: Variables are stated as facts. These facts simply state the names of the variables. In this example, the facts state that s and v are variables as shown below.

variable(s).
variable(v).
We also specify which symbols are terminals as shown below.
terminal(0).
terminal(1).
terminal(+).
Finally, the grammar rules are stated as ordered pairs, where the first element is the left-hand-side of a rule and the second argument is the LIST of elements appearing on the right-hand-side of that rule. For the grammar above, these rules are stated as shown below.
rule(s, [v, +, s]).
rule(s, [v]).
rule(v, [0]).
rule(v, [1]).

Your task is to write a general-purpose parser. This file will contain the rules for a "function" called parse. As described in class, parse takes as input a sentential form and a list of symbols to parse and says "yes" if and only if the sentential form derives the list of symbols using a leftmost derivation for the grammar.

In particular, we will only test your parse predicate with two fully-instantiated lists: the first will be the list of the grammar's start variable (not a Prolog variable) and the second will be a list of symbols to be parsed by that grammar (also not a Prolog variable).

For example, here are some sample inputs and outputs:

| ?- reconsult('part5.pl').         <--- HERE WE ARE LOADING IN OUR parse PROGRAM
yes 

| ?- reconsult('g0.pl').            <--- HERE WE ARE LOADING IN A GRAMMAR
yes

| ?- parse([s], [1, +, 0, +, 1]).   <--- note that everything is instantiated...
yes

| ?- parse([s], [1, +]).
no

| ?- parse([s], [1, 0, +]).
no

You should write the parse inference rules in a file called part5.pl and submit that file. Keep in mind that although we will only call parse with a one-element list (of the start symbol) as a first argument, you may want to write the predicate more generally than this to help with recursion... .

Your code will assume that there are facts already defined for the grammar using the names variable, terminal, and rule as illustrated above. Keep your code simple and elegant and be sure to test it thoroughly on the supplied grammars before submitting it. This can be done using fewer than 10 new lines of Prolog code - if you are writing much more than that you are doing too much work!



Optional Bonus Problem

We've seen several puzzles and the solutions for these puzzles written in Prolog. For up to 15 bonus points (depending on the complexity), write a Prolog program to solve your favorite puzzle. Here are the requirements: In addition to submitting your bonus, please send e-mail to Zach letting him know that you have submitted the bonus problem (so that he can play it himself!)

Last modified March 2005 by Ran Libeskind-Hadas or Z Dodds