CS 396
Functional Programming: Scheming about Lisp
Rules: Individual Effort, no sharing of code!
DUE DATES:
  • Intemediate deliverable: Part 1: Friday 2/12, 4pm, my office
  • Intemediate deliverable:Part 2: Monday 2/15, in class
  • Final Deliverable: Monday 2/22, in class
 

Overview: As we will be using Scheme for the programming assignments in here, it is vitally important for you to get acquainted with the language as quickly as possible. Fortunately, the extremely simple syntax and elegant nature of Lisp will make this fairly easy --- one you manage to free yourself from your imperative preconceptions and embrace the enlightened functional paradigm that underlies Lisp. Some things you'll learn to love include:

There are many other weird and wonderful things about Lisp you'll likely learn along the way. The bottom line is that you need to become proficient at the language. Subsequent programming assignments will assume that you are.

The assignment:

This assignment consists of several discrete programming challenges to acquaint you with various aspects of the language. They are loosely ordered from easiest to hardest, i.e., you should probably start with the first one.

Part 1: The basics
Part II: Function-maker
Part III: Battleship!

Nefarious hacker personalities have polluted Scheme's functional pureness somewhat over the years, adding in various constructs that are more procedural in nature. The following constraints will help exercise your functional thinking and keep your souls pure:

  1. No calls to "set!" (or set-car! or set-cdr!) which is the Scheme assignment operator. You may, of course, use "let" (and variants) to control your namespace within functions; similarly, you may use "define" at the top level to define your functions. You should never define/use global variables, unless explicitly allowed in a particular assignment.
  2. No use of explicit looping. Scheme now does include some looping constructs, but they violate the whole idea of the functional paradigm --- I don't want to see them in your code. Think recursively!
  3. No use of "begin". Scheme allows you to sequentially execute statements -- another violation of the functional paradigm added by hard-core proceduralist hackers. I admit that this is sometimes useful, particularly when you need to use "unpure" side-effect functions like "display" and "set!". Hint: You can utilize (bastardize) "let*" to do some primitive sequencing of operations for the few cases where you really need it.

These constraints apply to ALL PROGRAMMING ASSIGNMENTS in the class, unless explicit exceptions are granted within assignment statements. If you have a hankering to do something questionable, ask first. A major point of this exercise is to explore "functional programming", so I'm going to be strict.

Important! You must name your functions exactly as shown here; these are the functions I'll call when I test your output.

Part I: The basics

Suppose we have defined the following lists to use in upcoming examples:

(define x '(1 2 3 4 5 6 7 8 9 10))

(define y '(((a) b) (c d (d (f g) h)) i))

Define the functions:

steamroller -- The simple steamroller function flattens a list passed in as a parameter. That is, it goes through and moves all elements of the list, no matter how deeply nested, to the top level. So, for example, given the definition of x from above. Should work for lists of any length, including empty.

(steamroller y) --> (a b c d d f g h i)

ndelete -- The simple ndelete function takes in a list and some integer N. It returns the list with every nth element removed. So if N=2, the list has every other element removed; if N=5, is has every fifth element removed. It handles lists of all flavors (atoms, numbers, nested lists). It pays attention only to top-level elements (i.e., it doesn't reform sublists). If N is greater than the number of elements in the input list, it does nothing, ie, returns the input list. Examples:

> (ndelete x 3)
(1 2 4 5 7 8 10)
> (ndelete x 1)
()
>
(ndelete x 222)
(1 2 3 4 5 6 7 8 9 10)
> (ndelete y 2)
(((a) b) i)

deep-member? --- Just an advanced form of the built-in member? function. Takes a complex list (i.e. a list that may have nested lists inside of it) and a target atom as input. It searches the list (including all nested sublists) and returns #t if the target atom is found; else it returns #f.

> (deep-member? 3 x)
#t
> (deep-member? 444 x)
#f
> (deep-member? 'f y)
#t
> (deep-member? 'foo y)
#f
>

mymergesort --- We want to implement the elegant and naturally recursive MERGESORT algorithm, to show how lovely and simple this is in a functional language like Scheme.  I've given you the mymergesort function below --- only it's not complete.  In particular, there are two functions missing: merge and splitter.   These are what I want you to write!  Details on each follow below.  Here is the promised mergesort function:

;; Implements the elegant and naturally recursive mergesort algo
;; Idea is that, given a list, we split in two, mergesort these two, then 
;; merge together the resulting sorted lists.
 (define mymergesort
   (lambda (alist)
     (if (null? (cdr alist)) alist
         (let ((splits (splitter alist)))
           (merge (mymergesort (car splits)) (mymergesort (cadr splits)))))))

splitter --- Takes a complex list and turns it into list of two simple lists, each containing half of the original list. For odd numbered lists, the uneven element is added to the first list. [Efficiency and elegance challenge, for maximum points: you should avoid use of the "length" fn (built-in or homemade) in writing this one; your function should split it in one recursive pass down the list. Think about it...]

So continuing with the examples:

> (define z ' (a f c d f g e g h e g e e))
> (splitter z)
((a f c d f g e) (g h e g e e))
> (splitter '(a) )
((a) ())

merge -- Takes two sorted lists of integers and merges them into a single sorted list.

(define x '(1 3 5 7 9))
(define y '(2 4 6 8 10))

(merge x y) --> (1 2 3 4 5 6 7 8 9 10)

Overall MERGESORT performance --- When you have written the two missing functions, the mymergesort function I've written for you should work flawlessly.   To get a B or C, your two functions and overall mergesort should work perfectly on integer lists.  For example:

(define x1 '(12 23 2 5 64 23 6756 234 2 42 535))
> (mymergesort x1)
(2 2 5 12 23 23 42 64 234 535 6756)

To get an A, your splitter, merge, and mergesort should be able to deal with any type of list, even mixed lists.  Examples:

(define x2 '(how does he do that cool stuff?))
 (define x3 '(I can "go" 4 "about" 123 sodas k?))
 > (mymergesort x2)
(cool do does he how stuff? that)
> (mymergesort x3)
(4 123 "about" can "go" i k? sodas)

Intermediate deliverable:

Turn in a 1-2 page sheet showing:

  1. Your function definitions for the function(s) implemented for this deliverable.
  2. Demo of all of the above functions (in order please!) basically working on YOUR OWN sample input. Start by defining a couple of lists you'll be working on, then show the functions in action one after the other. Output should appear immediately adjacent to the commands that produced it! Either type your demo interactively, or put in "display" statements to show the functions/args being run if you want to set it up so that your demos run automatically after loading your program.

Part II: The function-maker

This part of the assignment introduces you to a crown jewel of LISP, namely, the ability to erase the line between data, which you manipulate as you please, and code, which the machine can execute. You are to write:

(fn-maker <fn-spec>) --- this function is, as it implies, a function maker. You pass it a <fn-spec> and it uses it to define a new function. The <fn-spec> has (exactly) the following form:

((name: <name>) (args: <args>) (body: <body>))

It uses this information to define the new function. An example:

> (define temp '((name: addtwo) (args: x) (body: (+ x 2)))) 
> (fn-maker temp)    
> addtwo #<procedure: addtwo> ;; Look! The new function is now defined!
> (addtwo 5) 
7 
>
Here's another example defining a multiple argument function:

> ;; here is a variable test-spec. I've set it to a list -- which just happens to ;; to represent a function specification. But it's just data! I'm just passing this list into ;; my function maker! Which defines the specified function for me!
test-spec
((name: mult-sum-three) (args: x y z) (body: (* (+ x y z) 3)))
>
(fn-maker test-spec)
>
mult-sum-three
#<procedure:mult-sum-three>
>
(mult-sum-three 1 2 3)
18
>

Obviously, fn-maker could be used to define very complex new functions. In fact (foreshadowing!), one could imagine the <fn-spec> argument to be generated by other very clever functions that know how to write functions.


Part III: The Battleship Game

This problem will give you some hands-on experience with list manipulation --- and it will illustrate some of the drawbacks of having a single data structure and limited data manipulation facilities.  Sigh! Every programming language has its downsides!  The good news that I'm giving you the truly hard parts of the challenge; all you have to do is fill in a few key missing functions in the code.

Intro
Just about everyone knows about the good old game Battleship. It's very simple, you have a board that flips open sort of like a laptop (see image). On the flat part, you place some ships...without letting your opponent see of course. Your opponent does the same on the other side. Then you just take turms calling out board coordinates, things like "b7" and "d3". Your opponent tells your whether they are a "hit" or a "miss", and you mark your shots on the vertical board so you know where you've shot. Ships have different hit values depending on their size: it takes two hits to sink a little destroyer, a battleship takes three, a aircraft carrier four. If a player has a ship that has taken all of its hits, that ship must be announced as sunk. The game is over when someone has no more ships above water.

The assignment.
I entertained myself by programming a slightly simplified version of battleship in Scheme. Like all board games, the data structure is obvious: a list with nested lists of "rows" inside it, perfect for practicing recursive list manipulation! It's nothing complex, and I made a few compromises to keep things ever simpler, e.g., ships can be placed only horizontally on the board (vs horizontally or vertically, like in the pic on the right).

The game is actually pretty nice to play, particularly after I automated the game-playing cycle so that you can zip right along. Now your job is just to write one key function in the game, the one that actually drops the bombs...plus any helper functions your need behind the scenes.

So here is all my code, minus the "warfare functions". Paste these into your scheme file as your starting point. You'll see where I've clipped out a part that you'll need to write.

And here is a sample output of the game being played. When finished, your game should have very similar output (although you might want to substitute your own crass output for hits, misses, sinkings and win/loss!).

Your task is to complete the body of the bomb function...plus any other helper functions you want to add to help you get the job done. I already have the start of the function definitition there for you; don't mess with this header!! Obviously my code needs exactly this function signature to work! Here are some details to consider in implementing this function:

When you finish, you're game should essentially function just like mine. In addition, I may try to feed your bomb function all sorts of balony input, so make sure you idiot-proof that function well. For instance, if I tell it to bomb location (99, 223), it should politely decline with a graceful error message rather than crashing horribly.

I'll give you a start on one of the helper functions involved in warfare too, since I also call it elsewhere and therefore need you to implement it too in order for everything to work. The function is called markchar, and is responsible for simply placing a symbol at a designated location on a board. The complete spec and header is in the code I've given you. And here is a little shot of markchar in action, to give you a further hint. In fact, this would be a great little warm-up task to get started with, as it gives a good feel for what manipulating a board (just a nested list, after all) is all about.

Eckstra Credit

I'm afraid the computer play is as dumb as a sack of hammers in this game. This is nice, because you generally always win (!!)...but wouldn't it be fun to make it a little more clever? Here are some extra credit challenges for those who finish up early and want to do something more meaty:

If you decide to implement these, you'll need to come see me in my office hours to demo your brilliance in person, explain your improvements, and collect your EC points. The best proof that you've smartened your game is that it could beat a human regularly! Don't be tempted to (ahem!) cheat by "smartening" the computer by having it consult the player's board and ship placements behind the scenes!

Note: For Part III only, you may bend the general prohibition against using "set!" --- you will need one or two to update the state (i.e. the game board, stored in the pOcean, cOcean, cShiplist, pShiplist global variables) of your system. So it's okay to have one or two in your code for this purpose. But that's all, no others; stick with the functional model of thinking/computation!

MANDATORY pre-submission self-testing: Here is a little "script" in Scheme that loads a given code file, then runs some tests. It's set up to load and test the two test class definition files CLASS1.TXT and inherit.txt that I gave you earlier. This little script is very very similar to what my testing program will do in testing your code. Just put all your files in the same directory and run it: it should load your program, load the test classes, and run some tests. Here is the output produced by a correct implementation. Note that little messages some functions print (e.g. what it says for hit/miss on battleship can/should be different than mine...be original). This is an EXCELLENT way to self-test your solution so that you know you didn't accidentally mis-name some functions or otherwise not match the required function signatures.

 

Final Deliverable, for all three parts. To Turn In: