CS152 Homework: Prolog

Due Tuesday, May 8, at 11:59PM

This assignment has several purposes:

Do Exercises 6, 20, and 21 from Ramsey and Kamin [35 points] as well as problems A–E [50 points] and J [15 points] below.

Source code for uProlog is available in several places, but only the version on the FAS servers, in [[~cs152/software/bare/uprolog/upr-with-unify.sml]], gives you code for substitution and unification, which is not in the book. (This code is from next year's version of the book; be aware that page numbers may have changed.) If you have already made a private copy of the book software, you will need to go back to the FAS server and get the current version, so that you won't have to re-implement substitution and unification. Otherwise, the source code should be reasonable, although it is still a bit of a mess in spots; you should be especially careful to ignore redundant ``readers'' functions.

Note: do not write a lot of code for Exercises 20 and 21. I added not by adding 4 lines to the interpreter, and I added the cut by changing about 7 lines of existing code. (But I confess that I did exploit my knowledge that existing primitives would not trigger a cut. It is possible to build a principled solution that does treat cut as an ordinary primitive, and that such a solution would require changing more code.) Focus on test cases that convince us you have got the correct semantics.

Background for problems A-E

Consider the game ``peg solitaire'' as played on a board of ten holes arranged in the following layout:
  o o
 o o o
o o o o
where _ represents an empty hole and o represents a hole with a peg in it. A ``move'' results when one peg jumps over another to land in a hole. The two pegs and hole must be colinear, and the stationary peg that was jumped over is removed from the board. So after a legal first move of the 1st peg on the third row (peg 4) we have:
  _ o
 _ o o
o o o o
and after moving the last peg on the same row (peg 6) we have:
  _ o
 o _ _
o o o o
and so on. When no peg can jump over any adjacent peg to land in a hole, the game is over. The object of the game is to leave a single peg, preferably in a designated hole. After my first attempt, I left this configuration:
  o o
 _ _ _
_ _ _ o
If you want to play the game yourself, try it with small coins, or you can run [[~cs152/bin/peg10]] or [[~cs152/bin/peg]].

For the problems below, number the pegs from 1, i.e., number the 10-hole layout like this:

  2 3
 4 5 6
7 8 9 10

Peg-Solitaire problems

Solve the following problems:

  1. Write Prolog rules such that the query [[cansolve(n)]] succeeds if and only if 10-hole peg solitaire has a solution leaving [[n]] or fewer pegs. You can assume that [[n]] will always be passed in, e.g., we should expect [[cansolve(3)]] to succeed always. [15 points]

  2. Add new rules for [[minleaving]] such that querying [[minleaving(N)]] puts in [[N]] the minimum number of pegs that can be left on the board. Hint: this is much easier with the cut. [10 points]
Now, switch to a 15-hole layout for peg solitaire:
Or more schematically,
   o o
  o o o
 o o o o
o o o o o
  1. Solve problem B over the new layout. [5 points]

  2. Number the holes from top to bottom, left to right, and write Prolog rules such that [[solution(n, M)]] either produces in [[M]] a list of moves leaving a single peg in hole [[n]], or fails if there is no such sequence. Represent a single move by the term [[move(Start, Finish)]], so for example the two possible initial moves would be represented as [[move(4,1)]] and [[move(6,1)]]. [10 points]

  3. We don't always have to start with the top hole empty. Write Prolog rules such that [[moves(S, F, M)]] produces a sequences of moves [[M]] that takes the board from a configuration in which all holes except [[S]] have pegs to a configuration in which only hole [[F]] has a peg. Using these rules, [10 points]

Logic problem

  1. The following scenario is adapted from a problem by Raymond Smullyan, who has made a career out of this sort of nonsense.
    Someone has stolen the jam! The March Hare said he didn't do it (naturally!). The Mad Hatter proclaimed one of them (the Hare, the Hatter, or the Dormouse) stole the jam, but of course it wasn't the Hatter himself. When asked whether the Mad Hatter and March Hare spoke the truth, the Dormouse said that one of the three (including herself) must have stolen the jam.

    By employing the very expensive services of Dr. Himmelheber, the famous psychiatrist, we eventually learned that not both the Dormouse and the March Hare spoke the truth. Assuming, as we do, that fairy-tale characters either always lie or always tell the truth, it remains to discover who really stole the jam.

    Write a Prolog program to solve this logical puzzle. In particular, write rules for a predicate [[stole]] such that the query [[stole(X)]] succeeds if and only if [[X]] could have stolen the jam. The query should work even if [[X]] is left as a variable, in which case it should produce all the suspects who could possibly have stolen the jam.

    Approaches to the problem. There are two ways to approach this kind of problem.

    1. The first approach uses exhaustive search of the entire state space to found all the possibilities that are consistent with the facts as given. You will explore that approach in section and get a handout on it.
    2. The second approach is to explore a much smaller state space and use logical implication to get the rest. This technique won't be covered in section, but you'll have a chance to attack it in your own.
    You can earn at most 14 points on this problem by using the first approach (exhaustive search). To earn the last, lousy point, you'll have to use the second approach (logical implication).


    [15 points]


Part of this assignment is to come up not just with solutions, but with reasonably efficient solutions. Here are some problems I've seen in several students' Prolog code, which if not corrected may lead to unnecessary inefficiency:

Extra Credit

Symmetry. In the 10-hole game, there is no real choice for the first two moves—all positions that can be reached in two moves are identical under the symmetry group. Use this fact to speed up your solution to problem B, and measure the speedup.

More symmetry. Use symmetry to speed up your solution to problem D. Measure the speedup.

Generality. Solve one or more of problems C–E, but make the number of holes in the triangle a parameter to the problem. So for example, I would try to solve the board in the introduction by [[solution(4, 1, M)]] where 4 is the number of holes along one side of the triangle, 1 is the desired final hole, and M is the desired sequence of moves. Measure the performance cost of this generalization.
Hint: The tough part is figuring out what's the numbering for a potential move. Think about shearing the board to form a lower-triangular matrix. What are the rules then for the permissible directions of motion? You may find it useful to number by row and column instead of just numbering the individual holes. This is perfectly OK.

Complementarity. The ``complementarity problem'' for peg solitaire asks for which holes H we can start with a peg in every hole except H, then finish with a board that is empty except for a single peg in H. Write Prolog rules for [[complements(HS)]] that leaves in [[HS]] a list of holes for which the complementarity problem can be solved. Exploit symmetry to reduce searching time. Which holes do you actually have to check?

Types. This programming-language stuff all fits together. In Prolog, write a type checker for the first-order typed lambda calculus with products ([[pair]], [[fst]], [[snd]]) and integer literals. The core of your type checker should be a relation [[has_type(Gamma, Term, Type)]] where you supply the environment and the term and Prolog computes the type. For even more extra credit, add polymorphism.

For the simplest possible type system, a checker in Prolog should take about a dozen lines of code. Adding sums, products, and polymorphism will more than double that. Here's a sample from my code, running on an old homework problem: <>= | ?- has_type([], tylambda(alpha, tylambda(beta, lambda(p, cross(alpha, beta), pair(snd(var(p)), fst(var(p)))))), T). T = forall(alpha,forall(beta,arrow(cross(alpha,beta),cross(beta,alpha)))) @ Can you ``run it backwards'' and get the engine to exhibit a term with a particular type? If not, why not? Can you modify your code to produce a derivation as well as a type? If not, why not?

Pegs as games. Implement peg solitaire as a game, and use the AGS from the Standard ML Modules homework to solve problem E and the Complementarity extra credit. Hint: you'll find it easiest if you make the solitaire itself a functor, which you can then parameterize by the initial and final positions desired. <>= signature PEG_BASICS = sig ... end structure PegBasics : PEG_BASICS = struct type config ... end signature PEG_PARMS = sig structure Basics : PEG_BASICS val initial_hole : int val final_peg : int end functor PegGame(Parms : PEG_PARMS) : GAME = struct open Parms.Basics (* justified because all these names are re-exported *) fun who_won conf = if pegs_left conf = 1 andalso contents(conf, Parms.final_peg) = Peg then Player.WINS Player.X else Player.TIE ... end @ and so on... You might start out simply by implementing a game like ~cs152/bin/peg, in which you win if you clear the board.

What to submit

Using submission script submit-prolog, submit these files: You can use either uProlog ([[~cs152/bin/uprolog]]) or XSB Prolog ([[~cs152/bin/xsb -i]] or [[/usr/local/XSB/3.0.1/bin/xsb -i]]). XSB Prolog is faster, and this may be helpful for some of the peg problems. For extra credit, you may also turn in [[pegBx.pl]], [[pegDx.pl]], [[general.pl]], [[complement.pl]], [[types.pl]], and more.