Lab12 - Canisius College Computer Science

advertisement
CSC 111 Lab 12
April 19, 2007
OBJECTIVES:
1. Learn more about LEGO Mindstorms
robots
2. Incorporate arrays into a robot program
3. Investigate machine learning
4. Play with sounds some more
C’mon, little robot! Follow me (to your
doom!)
PRELUDE:
Today we continue our investigation of robots and how to program them using Java.
Some extra time will be scheduled for Wehle Room 206, across the hall from the Computer Science
Department. The precise schedule when Dr. Meyer will be there will be posted on the website. You
can also visit the Advanced Lab, Wehle Room 208, where the tutors live. They have about 4 robots
that you can check out.
The points you earn for this lab are attached to the tasks you are asked to complete.
ACTIVITIES:
Part 1: Getting Started...
1.
Log on to the Wehle server using your own username. Copy the LAB12 folder to your H: drive and
also to the desktop.
2.
First, make a new project with your lab name, such as
Meyer Lab 12
1
Remember to make a Lejos RCX project.
3.
Now import all the programs from the LAB12/CODE folder on your desktop into this project.
4.
You will use Eclipse to edit and compile your robot code, but you will also have to use the command
window to download the programs into your robot, since that cannot be done inside Eclipse. To do
this, press the START button in the lower left, and select Run... When the dialog box pops up, type
cmd
and press RETURN.
5.
Navigate to the folder in your workspace. If you are working off the H: drive, then type the
following commands and press RETURN. Note: the C:\> is merely the prompt. Do not type it!
C:\>
C:\>
h:
cd \workspace\Meyer Lab 12
Part 2: Arrays and timing
1.
Lejos permits you to create arrays and we will make use of that capability to write a program that
allows us to train a robot. We will press the right or left bumper and the robot will store ‘L’ or ‘R’ in
an array for later replay. This array must be a partially filled array because we need to add chars to it
without knowing how many we will enter. See the program Partial.java for an example of a
partially filled array.
2.
We also need to keep track of the amount of time between bumper presses so we can have the robot
move through its world later, “replaying” the route. The distance the robot goes depends on how
much time the motors are left turned on. Java allows us to ask the system what the time is in terms
of milliseconds since midnight Jan. 1, 1970. The program SeeTime.java is a plain Java program
that shows this information. To verify the numbers are right, it divides by the number of seconds in
a year and shows us that Jan. 1, 1970 was about 37.2 years ago, which is correct.
(There are 86,400 seconds in one day and 31,536,000 seconds in a year if there are 365 days. That
means the date is approximately 1,173,139,200 now. That’s a lot of seconds! The number of
milliseconds would be 1000 times that, or 1.175 trillion milliseconds.)
3.
Study the program TrainMe.java which compiles and downloads onto the robot but has lots of
holes, shown as comments in the code. You will fill these in for Assignment 1.
4.
First a warning about the time calculations in the TrainMe.java program. Because the time is
given in milliseconds, it needs to be a long. But lejos can’t do long division because the RCX chip is
so limited. So what this program does is to convert it to a weird integer that actually looks wrong.
However, the way that the time is calculated works out correctly to the number of true centiseconds
between button presses. Thus, if you try to understand the logic of the time calculations, you may be
quite confused. Just use what is there.
5.
Let’s investigate the algorithm of TrainMe.java more closely. The idea is to use the bumpers to
tell the robot which direction to turn while we train it. Then we will keep track of the sequence of
2
left or right bumps and how long it moves forward between bumps. When the user presses a button
to announce the training period is over, the robot waits for another button press to cause it to replay
the path you taught it.
Follow the skeleton program TrainMe.java. Notice that there are two phases in the program: a
training phase and then a playback phase. Button presses distinguish between the phases. When the
user presses a button, a boolean variable is set, which causes the program to leave a particular while
loop.
You will use two arrays to remember the path that you teach the robot. Each segment of the trip ends
with a turn, either left ('L') or right ('R'). Also, you will remember how long the segment was by
recording the difference in times, measured in centiseconds (hundredths of a second). Since you
don't know how many segments the trainer will include, create your arrays with a maximum size,
like 100, and then have another variable that "points to" the next open slot in the array. After the
training ends, this variable will tell how many segments have been recorded. This is the essence of a
partially filled array.
Suppose that the trainer is teaching the robot to draw a square. Here's what the two arrays might look
like:
char[] directions:
-----------------directions[0] = 'R'
directions[1] = 'R'
directions[2] = 'R'
directions[3] = 'R'
int[] lengths:
---------------lengths[0] = 10
lengths[1] = 9
lengths[2] = 11
lengths[3] = 10
numSegments = 4
After four segments the user presses the PRGM button which stops the training. Notice that the
lengths aren't exactly 10 because we can't expect the trainer to press the bumper at exactly the right
moment to get it perfectly 100 centiseconds (100 centiseconds = 1 second).
The int variable numSegments will have 4 in it, indicating that there were 4 segments. When we
replay, we only use the first 4 elements of the two arrays, even though they have 100 elements in the
array.
You cannot expect the trainer to press the bumper exactly 4 times. She might teach it a path that has
20 turns in it, or only a few! 4 is just an example. This is why we must use a partially filled array.
The trainer pressed the PRGM button very soon after touching the right bumper for the 4th time. But
we won't count this final fifth segment as a segment. Thus, when the robot replays, the time between
the last bump and the PGRM press won't be stored or replayed.
After training, the robot goes into a while loop after stopping the motors, and waits for the human to
press the RUN button. This gives the human time to place the robot into another spot before
replaying, perhaps the original starting point.
Once the RUN button is pressed, the Lejos program exits the second while loop and goes into a for
loop (why a for loop?) This for loop replays the first N segments of the arrays, causing the robot to
retrace the path that it was taught. Notice that when you use the values lengths[i] in the pause()
3
method, you must multiply by 10 because these lengths are centiseconds, not milliseconds, but
pause() takes in milliseconds.
After the robot retraces its learned path, it just stops.
Part 3: Playing music EXTRA CREDIT
1.
Look at the program PlayScale.java. It plays a scale from Middle C up one octave. There is a
static method that takes in three parameters:
1. note name (a String)
2. a register (1 on up)
3. a timing amount in centiseconds
The register is used to create other octaves because in Western Music, any note that is one octave
higher has a frequency that is double the frequency of one octave below. Thus, middle C is 261.63
vibrations per second, so one octave higher would be 523.25 vibrations per second. (We usually
abbreviate vibrations per second as Hz, or cycles per second, named after Heinrich Rudolf Hertz
(German physicist, 1857-1894).
2.
See Assignment #2 below for a related task.
ASSIGNMENTS: Try to finish these during lab today but you can come to the open lab for more time.
1.
Complete the program called TrainMe.java.
Put your name in comments at the top, along with the lab number (12) and the assignment number
(1).
2.
This part is Extra Credit: Make a new program PlaySong.java. It is based on PlayScale.java,
and uses the play() method in that program without change.
We are going to encode each note as a string. The pitch comes first, then a colon, then the register,
another colon, and finally the duration. Here’s a simple tune:
{"C:1:50", "D:1:25", "E:1:25", "G:2:50"}
Write a method called playSong() which takes an array of String and runs through it, playing each
note. You will have to use the splitString() method that I have written to cut apart each String
into its three pieces. (We should have liked to have used String.split(), which is part of
standard Java, but lejos doesn’t include it.) Then you will have to toInteger() to convert a
String like “25” to the actual integer 25. See the program Splitter2.java. Again, we could
have used Integer.parseInt(), except that lejos omitted that method, too, in order to cut
down on the memory requirements. Remember, the RCX only has 32,768 bytes of memory! That’s
32Kb, which is tiny compared to the 256MB memory of a standard desktop, and even 256
megabytes isn’t enough to run Windows Vista! 256MB would be 8,192 times larger than 32Kb.
Wow.
4
PROGRAMS USED IN TODAY’S LAB
import josx.platform.rcx.*;
import java.util.*;
public class TrainMe implements SensorConstants, ButtonListener {
public static final int NINETY_DEGREES = 1050;
public boolean done = false;
public boolean ready = false;
// milliseconds
public static void main(String[] args) {
TimeIt tp = new TimeIt();
Button.PRGM.addButtonListener(tp);
Button.RUN.addButtonListener(tp);
tp.run();
}
public void run() {
Sensor.S1.setTypeAndMode(SENSOR_TYPE_TOUCH, SENSOR_MODE_BOOL);
Sensor.S2.setTypeAndMode(SENSOR_TYPE_TOUCH, SENSOR_MODE_BOOL);
int start, end;
// section 1:
// array of char that will contain 'L' or 'R', to remember which
//
bumper was touched
// array of ints that remembers how long to pause before turning
// we store the time as centiseconds to permit much longer pauses
// between pushes of the flippers. This is why we divide the
// the time in milliseconds by 10
while (true) {
start = ((int)System.currentTimeMillis()) / 10;
forwardBoth();
int left=0, right=0;
// which sensor was pushed?
while(left == 0 && right == 0 && !done) {
left = Sensor.S1.readValue();
right = Sensor.S2.readValue();
}
if (done) break;
stopBoth();
end = ((int)System.currentTimeMillis()) / 10;
int diff = end - start;
LCD.showNumber(diff);
pause(200);
// section 2:
// if the left bumper was pressed, then
//
turnLeft(NINETY_DEGREES);
//
and remember it was the left
// else the left bumper was pressed, so
//
turnRight(NINETY_DEGREES);
//
and remember it was the right
// Now remember how long we went (store diff into the
//
array of integers)
}
// Now wait for the human to start me up again by pressing the
// RUN button.
while(!ready) {
pause(200);
}
Sound.beepSequence();
// section 3:
// Now run through the array of turns and timings to retrace
// the path
}
5
public static void pause(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {}
}
public static void forwardBoth() {
Motor.A.forward();
Motor.C.forward();
}
public static void reverseBoth() {
Motor.A.backward();
Motor.C.backward();
}
public static void stopBoth() {
Motor.A.stop();
Motor.C.stop();
}
public static void turnLeft(int milliseconds) {
stopBoth();
Motor.A.backward();
Motor.C.forward();
pause(milliseconds);
stopBoth();
}
public static void turnRight(int milliseconds) {
stopBoth();
Motor.A.forward();
Motor.C.backward();
pause(milliseconds);
stopBoth();
}
public void buttonPressed (Button b) {
if (b.getId() == Button.PRGM.getId()) {
stopBoth();
// set a boolean var
}
if (b.getId() == Button.RUN.getId()) {
// set a different boolean var
}
}
public void buttonReleased (Button b) {}
}
6
//-------------------------------------------------------------------------------// This is a gutted playsong. It encodes Beethoven's Ode to Joy
// from the finale of the Ninth Symphony.
//-------------------------------------------------------------------------------import josx.platform.rcx.*;
public class PlaySong implements SensorConstants {
public static void main(String[] args) {
// Beethoven's "Ode to Joy"
play("E", 2, 40);
play("E", 2, 40);
play("F", 2, 40);
play("G", 2, 40);
play("G", 2, 40);
// ... many more lines of notes
// Instead of having a sequence of plays like this,
// formulate the song as an array of String and send that into
// playSong().
}
private static void playSong(String[] notes) {
// You write this code...
// Each note is an element of this array. It is encoded
// like
"C:2:20"
where C is the note, 2 is the register,
// and 20 centiseconds is the duration.
// Use splitString() below to break out the 3 parts. The
// numerical parts need use of the toInteger() method.
}
// You will need the following to convert from a String like
// "27" to an int 27 because Lejos' Java is so weak! It doesn't
// have a lot of things in it, especially Integer.parseInt.
public static int toInteger(String s) {
int retval = 0;
for (int i=0; i<s.length(); i++)
retval = retval * 10 + (int)(s.charAt(i))-48;
return retval;
}
private static void play(String
int frequency = 0;
if (note.equals("C"))
frequency = 262;
else if (note.equals("C#")
frequency = 277;
else if (note.equals("D"))
frequency = 294;
else if (note.equals("D#")
frequency = 311;
else if (note.equals("E"))
frequency = 330;
else if (note.equals("F"))
frequency = 349;
else if (note.equals("F#")
frequency = 370;
else if (note.equals("G"))
frequency = 392;
else if (note.equals("G#")
frequency = 415;
else if (note.equals("A"))
frequency = 440;
else if (note.equals("A#")
frequency = 466;
else if (note.equals("B"))
frequency = 494;
note, int register, int centiseconds) {
|| note.equals("Db"))
|| note.equals("Eb"))
|| note.equals("Gb"))
|| note.equals("Ab"))
|| note.equals("Bb"))
Sound.playTone(frequency*register, centiseconds);
pause(centiseconds*10);
7
}
public static void pause(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {}
}
public static String[] splitString(String s, String delim) {
char delimChar = delim.charAt(0);
// first determine how many elements in our return array are needed
// by counting how many time delim occurs in s.
int count = 0;
for (int i=0; i<s.length(); i++)
if (s.charAt(i) == delimChar) count++;
String[] returnPieces = new String[count+1];
int where = 0;
// which element to store the word in
int p = 0;
// this works its way through the string s
String nextWord = "";
while (p < s.length()) {
char ch = s.charAt(p);
if (ch == delimChar) {
returnPieces[where++] = nextWord;
nextWord = "";
}
else
nextWord += ch;
p++;
}
returnPieces[where] = nextWord;
// the final word, no delim at
// the end
return returnPieces;
}
}
8
Download