These exercises are about finishing the simple robot plan execution simulator. These exercises require just basic Lisp programming knowledge, except for the exercise on packages.
The Rules of the Queue still apply, both testing and critiquing.
Test suite name: pick-up-test
Define test cases to test step-update
with pick-up actions, then extend the code
to implement pick-up. A robot can pick up an object only if the robot is
in the same location as the object, and the robot is not already holding something.
If these conditions are not met, the action should fail, i.e., the resulting state should
be NIL, otherwise, the location of the object should be the robot.
For now, use a simple CASE or ECASE
in step-update to select which code to execute for pick-up
versus go. You will change this in a later exercise.
Refactor! Do not repeat code. If there are common Lisp tasks for the two robot actions, define well-named modular subfunctions to do those tasks.
Submit both your code and your test cases for pick-up.
I will be evaluating the quality of both your code and your test cases. A good
test suite covers all likely places where things might fail, but does not
include redundant tests or tests that could never fail.
When defining test cases, you will almost certainly discover situations where it's not clear what the right answer should be. Post those questions to the newsgroup for discussion. Use the answer the newsgroup has converged on, if it's already been discussed. If agreement was not reached, pick the answer you prefer.
Test suite name: drop-test
Define test cases to test step-update
with drop actions, then implement drop.
A robot can drop something only if the robot is
holding something. If this condition is not met, the action should fail,
otherwise the location of the object should be the room the robot
is in. Note that drop takes no parameters.
Submit both your code and your test cases for drop.
Test suite name: plan-update
Define test cases to test plan-update. The syntax
is (plan-update state plan robot).
One or more tests should use the example plan in
*plan* in the original simulator code file, but you
should have other plans.
Test for plans that work and ones that don't. The
empty plan should always work.
Submit both your code and your test cases for plan-update.
Only submit step-update if you had to change it.
Note: no new tests are needed for this exercise. You need to know how to create function closures to do this exercise.
Using a conditional or case statement to implement actions is fine when first starting, but it's not the best long-term approach. Every time we want to add a new action or modify an existing one, we have to edit the simulator source code.
A better approach is to make the simulator extensible, so that new
actions can be added without touching the source code of the simulator.
This means changing step-update so that it uses a table of some
kind that maps action names to functions. All step-update should
do is look up the name of the action for a step in the table. If it's there,
it should run the function, which should return a new state, possibly
NIL. If no entry is found, step-update
should signal an error with error.
Decide what kind of data structure you want to use for the table.
Change your code file so that it creates the table, and stores functions
for go, pick-up, and drop. Change
step-update appropriately.
Make sure all your tests still run.
Submit the new code only.
Package names: robot-sim, robot-sim-tests
To do this exercise, you need to know about packages, as described in Graham (Chapter 8), the glossary (pp. 343-346), and my notes.
The robot simulator should be in its own package, to avoid conflicts with other libraries. The simulator test code should be in a different package, to guarantee that the test code is not accidentally depending on unexported internal simulator functions.
Define two files, robot-sim.lisp and robot-sim-tests.lisp.
The first should contain the simulator
code in the package robot-sim. The second should contain the test code
in the package robot-sim-tests.
The three major issues in package design are to:
Export as little as possible. The more you export, the more name conflicts you might cause, and the less freedom you'll have to make future changes to your library. This same principle applies in module systems in other languages, such as Java and C++.
Depend on as little as possible. Your packages should only use those packages that are necessary. This reduces potential name conflicts, and insulates your library from changes in other libraries. Don't reinvent the wheel, but don't use a package if you don't need the code from that package.
In particular, do not use "user" packages, such as cs325-user, cl-user
and Allegro's cg-user (in Allegro). User packages collect together other packages
to provide convenient access to functions in an
interactive Listener window. But because they include so many packages, they
can cause name conflicts when defining library modules. Instead,
library packages should use the underlying packages,
such as common-lisp, that define the code needed by the library.
You'll need to figure out what packages you need. You can use the function
package-use-list to find out what packages cs325-user uses.
Ignore packages when matching symbols. The robot simulator
determines what code to run based on symbols, such as go and pick-up.
Symbols are symbols, and pick-up in the simulator package will not be
the same as pick-up in a user package like cs325-user. The
wrong answer is to export these symbols, because that can cause name conflicts.
The better answer is to change your code so that it doesn't matter
what package the input symbol is in. There are a number of ways to do this.
I'll leave it to you to explore them.
To test, load both files into your CS325 Lisp. Do not call use-package.
Instead, evaluate
(run-all-tests robot-sim-tests)
If you've done things correctly, all your robot tests should run.
Submit just your defpackage forms.
Comments?
Send me mail.