This class is the Applet that runs each lesson. The applet reads
* in the majority (all but image) of the information from text files, whose
* location are given to it.
*
*
The soundfiles must be in .au format, with MuLaw compression [Note: is
* this correct? Talk to Joel]
*
*
There are six different kinds of ASCII files that the applet reads in.
*
The header file
*
Dialog files
*
The brief file
*
New state files (text with choices following the text)
*
The data file
*
Finishing files
* Below are the syntax specifications for each kind of file.
*
*
The Header File
*
The header file is an ASCII file composed of headings, followed by tags.
* All of the headings must be on their own line, and prefixed by a '#'. All
* of the tags following a heading are for that section, and must be of the
* form tag=value. With the exception
* of the panellinks tag, all file locations should be given
* relative to the codeBase of the applet. The
* panellinks values should be absolute URLs. If
* the tag accepts multiple values (e.g., soundfile), the values can be on
* multiple successive lines, provided all but the last line end in a ';'.
* Otherwise each tag/value pair must be on the same line, and each pair must
* be its own line.
* Comment lines can be added to the header file; they must have an '!' as the
* first character. Below are the headings (bold), the valid tags for each,
* and the syntax for the values ("Val:"), if there are specific
* requirements:
*
LessonInfo
*
title
*
client
*
Val: Must be "male", "female", or "pair".
*
Introduction
*
msoundfile
*
fsoundfile
*
textfile
*
DataCollection
*
msoundfile
*
fsoundfile
*
textfile
*
Statistics
*
msoundfile
*
fsoundfile
*
textfile
*
datafile
*
panellinks (accepts multiple values)
*
Val: Should be of the form Title>
* Absolute URL
*
Brief
*
textfile
*
choices
*
Val: Should encode the number of choices on each page of the
* brief as a vector (e.g., <2, 2, 4, 4, 6, 4>)
*
"If" statements (Note: Not a standard tag!)
*
Val: Of the form if vector result.
* vector refers to the combination of brief responses.
* '*' is the wildcard character, and '~' is the negation character.
* result is either of the form goto
* NewState, or result=finish
* , where finish is of the form [i|l|w]#
* . E.g., if <1, 2, 4, 3, 5, 3> goto Diff, or
* if <*, 3, 8, 2, *, *> result=l4. Note: the new state
* names can be multiple words.
*
Incompetent
*
textfile
*
msoundfile (accepts multiple values)
*
Val: The first soundfile refers to i1, the second
* to i2, and so on.
*
fsoundfile (accepts multiple values)
*
Val: See above Val:
*
Lose
*
Same tags as for incompetent
*
Win
*
Same tags as for incompetent
*
New States [The name of each new state must be different]
*
textfile
*
msoundfile
*
fsoundfile
*
"If" statements
*
Val: See comments for "if" statements in the Brief
*
*
*
Dialog Files
*
Dialog files contain only dialog, and are used for the initial parts of
* the lesson. Each speech by a character must begin with the character's
* name (or one of the abbreviations given below), followed by a ':'. This
* tells the parser that it is a new speech, instead of simply the person's
* name. "Personalizing" codes (e.g., that insert the lawyer's name) can be
* inserted directly into the text. The abbreviations and codes are given
* below:
*
Abbreviations
*
For "Lawyer:" -> "L:"
*
For "Client:" -> "C:"
*
For "Statistician:" -> "S:"
*
For "P.I.:" -> "P:", "Investigator:", or "Private Investigator:"
*
For "Opposing:" -> "O:", or "Opposing Attorney:"
*
For "Judge:" -> "J:"
*
"Personalizing" code
*
"##name##" -> Inserts the lawyer's name here
*
"##pronoun##" -> Inserts the proper s/he pronoun here
*
*
The Brief File
*
The brief file contains the brief which the students will fill out.
* A section of text is followed by the specification of the options.
* There is one option displayed per page (i.e., finishing an option signifies
* the end of a page). The following "HTML-like" tags are used to separate
* out the options and the choices within them.
*
<option#>, to signify the start of
* an option.
*
<#>, to indicate a choice within an
* option. This choice can be spread across multiple lines.
*
</option#>, to signify the end of
* an option.
*
*
New State Files
*
New state files contain both the text and options for sections after
* the brief is submitted. All of the dialog text should be followed by the
* options (there can be as many of them as you want, but they cannot be
* interwoven with the dialog). Within the new state text, you can use the same
* abbreviations and personalizing codes as in the dialog files. In the
* options section, you should use the same "pseudo-HTML" tags as in the
* brief file.
*
*
The Data File
*
The data file contains the data and reference for the table to be
* displayed by the statistician. Any text outside of the <data>, and
* </data> tags is considered part of the reference. Within the
* <data> section, the following conventions apply:
*
If there is a line with whitespace at the beginning, it is assumed
* to be the column names.
*
Each line is assumed to be a full row.
*
Within any given line, there must be (at least) one tab between each
* column.
*
*
Finishing Files
*
These are the files that contain the text for the finishes (when the
* student wins, loses, or is found incompetent). Each of the finishes should
* have its own file. However, all of finishes of a particular type (e.g.,
* all of the winning finishes) should be within a single file. Within the
* file, the text of each finish should be enclosed in <#>
* and </#> tags. The number in the tag corresponds to the
* finish named in the "If" statements. E.g., finish w3 is the
* text within the winning textfile that is enclosed by <3>
* and </3>. Within the text, you can use the same
* abbreviations and personalizing codes as in the dialog files.
*
*
HTML Parameters
*
There are five parameters that can be set in the HTML. They are:
*
*
"student": The name of the student
*
"sex": The student's gender
*
"lesson codebase": The directory (below the LessonManager.class
* file) that the lesson is in
*
"header": The lesson header file
*
"online": An optional tag indicating whether we're online (T and
* F are the recognized values)
*
*
* @author David Danks
* @version 0.9; Mar 9, 1999
* @see BriefPanel
* @see DataTableFrame
* @see ImageCanvas
* @see SoundPlayer
* @see TextCanvas
* @see DatabaseBroker
*/
public class LessonManager extends Applet implements ActionListener {
// general lesson variables
/** @serial */
protected URL lessonUrl;
/** @serial */
protected String lessonTitle;
/** @serial */
protected int clientSex;
/** @serial */
protected boolean soundOn = false; // this is temporary
/** @serial */
protected int lawyerSex;
/** @serial */
protected String lawyerName;
// file locations
/** @serial */
protected URL soundFile[][][]; // lawyerSex, scene, number
/** @serial */
protected URL finishSound[][][]; // lawyerSex, result, number
/** @serial */
protected URL textFile[];
/** @serial */
protected URL dataFile;
/** @serial */
protected URL choiceFile[];
/** @serial */
protected URL incompFile;
/** @serial */
protected URL loseFile;
/** @serial */
protected URL winFile;
// panel information
/** @serial */
protected String panelLinkTitle[];
/** @serial */
protected URL panelLink[];
// flow information
/** @serial */
protected Hashtable briefResult;
/** @serial */
protected String[][] text;
/** @serial */
protected String[][][] options;
/** @serial */
protected Hashtable responseResult;
/** @serial */
protected String stateVector;
/** @serial */
protected int finalResult;
// magic numbers for the possible results
static final int INCOMPETENT = 1000;
static final int LOSE = 2000;
static final int WIN = 3000;
static final String IM = "images/";
static final String FEMIM = "images/female_lawyer/";
static final String MALIM = "images/male_lawyer/";
static final int FEMALE = 0;
static final int MALE = 1;
static final int PAIR = 2;
static final int INSET = 30;
// image information
/** @serial */
private URL beginIm[]; // images
/** @serial */
private URL introIm[][][]; // lawyer, client, image
/** @serial */
private URL dataCollIm[][]; // lawyer, image
/** @serial */
private URL statIm[][]; // lawyer, image
/** @serial */
private URL courtIm[][]; // lawyer, image
/** @serial */
private URL finishIm[][][]; // lawyer, outcome, images
// general variables
/** @serial */
GridBagLayout gbl;
/** @serial */
Checkbox on;
/** @serial */
TextField loginName;
/** @serial */
java.awt.List panel;
/** @serial */
URL classList;
/** @serial */
Checkbox[] optBoxes;
/** @serial */
ImageCanvas pict;
/** @serial */
TextCanvas tc;
/** @serial */
Button next;
/** @serial */
Button skip;
/** @serial */
Component[] keep;
/** @serial */
DataTableFrame dtf;
/** @serial */
SoundPlayer sound;
/** @serial */
BriefPanel bp;
/** @serial */
DatabaseBroker db;
/** @serial */
boolean isLocal = false;
/** @serial */
String lessonBase;
/**
* The default constructor simply initializes the Frame, when it is not
* run as an Applet
*/
public LessonManager () {
super();
gbl = new GridBagLayout();
this.setLayout(gbl);
}
/**
* This constructor initializes the Frame, and then loads in the lesson,
* when it is not run as an Applet
*
* @param lessonName The relative filename of the lesson
*/
public LessonManager (String lessonName) {
this();
loadLesson(lessonName);
}
/**
* This init() method starts up the LessonManager when it is run as an
* Applet
*/
public void init() {
String header = getParameter("header");
lessonBase = getParameter("lesson codebase");
if (lessonBase == null) lessonBase = "";
String sex = getParameter("sex");
if (sex.equals("F")) lawyerSex = FEMALE;
else lawyerSex = MALE;
lawyerName = getParameter("student");
String online = getParameter("online");
db = new DatabaseBroker("lawyer", header);
if ((online == null) || (online.startsWith("F"))) {
db.setOnline(false);
isLocal = true;
}
else db.setOnline(true);
loadLesson(lessonBase+header);
runIntro();
}
/**
* actionPerformed is called whenever the student hits a button to move to the
* next section. Sometimes, this method only sets the readyToAdvance variable
* before moving on. Other times, it actually stores some information.
*/
public void actionPerformed (ActionEvent a) {
// exercise wanted
if (a.getSource() == panel) {
int ind = panel.getSelectedIndex();
this.getAppletContext().showDocument(panelLink[ind],
panelLinkTitle[ind]);
}
// in one of the script pages
else if (a.getActionCommand().startsWith("next:")) {
int sec;
int ind;
try {
sec = Integer.parseInt(a.getActionCommand().substring(5,
a.getActionCommand().indexOf(",")));
ind = Integer.parseInt(a.getActionCommand().substring(a.getActionCommand().indexOf(",")+1));
}
catch (NumberFormatException n) {
System.err.println("Problem with changing script pages!");
return;
}
displayScript(sec, ind);
return;
}
// run Data Collection
else if (a.getActionCommand().equals("rundatacollect")) {
this.removeAll();
if (textFile[1] != null) runDataCollect();
else if (textFile[2] != null) runStatistics();
else if (textFile[3] != null) runBrief();
return;
}
// run Statistics
else if (a.getActionCommand().equals("runstats")) {
this.removeAll();
if (textFile[2] != null) runStatistics();
else if (textFile[3] != null) runBrief();
return;
}
// run Brief
else if (a.getActionCommand().equals("runbrief")) {
this.removeAll();
runBrief();
return;
}
// move to next Brief section
else if (a.getActionCommand().startsWith("nextbrief:")) {
int resp = bp.getSelectedIndex();
if (resp == -1) return;
stateVector += String.valueOf(resp) + ",";
int nex = 0;
try { nex = Integer.parseInt(a.getActionCommand().substring(10)); }
catch (NumberFormatException n) {
System.err.println("problem with the brief transition");
return;
}
if (nex == 1) { skip.setEnabled(true); }
skip.setActionCommand("backbrief:"+String.valueOf(nex-1));
if (nex == text.length-1) {
next.setActionCommand("submitbrief");
next.setLabel("Submit Brief");
}
else next.setActionCommand("nextbrief:"+String.valueOf(nex+1));
bp.displayPage(nex);
return;
}
// move to previous Brief section
else if (a.getActionCommand().startsWith("backbrief:")) {
int bac = 0;
try { bac = Integer.parseInt(a.getActionCommand().substring(10)); }
catch (NumberFormatException n) {
System.err.println("problem with the brief transition");
return;
}
if (bac == 0) {
stateVector = "";
skip.setEnabled(false);
}
else { String temp = stateVector.substring(0,
stateVector.lastIndexOf(",", stateVector.length()-2)+1);
stateVector = temp;
skip.setActionCommand("backbrief:"+String.valueOf(bac-1));
}
if (bac == text.length-2) next.setLabel("Next Section");
else next.setActionCommand("nextbrief:"+String.valueOf(bac+1));
bp.displayPage(bac);
return;
}
// done with Brief
else if (a.getActionCommand().equals("submitbrief")) {
int resp = bp.getSelectedIndex();
if (resp == -1) return;
stateVector += String.valueOf(resp);
if (briefResult.containsKey(stateVector)) {
db.submitGrade("brief", stateVector);
Integer nextState = (Integer)briefResult.get(stateVector);
this.removeAll();
runCourt(nextState.intValue());
}
else System.err.println("key not found:"+stateVector);
return;
}
// next new state text
else if (a.getActionCommand().startsWith("newnext:")) {
int ind;
try { ind = Integer.parseInt(a.getActionCommand().substring(8)); }
catch (NumberFormatException n) {
System.err.println("Problem with the new state transition");
return;
}
if (ind == tc.getNumDialogs()) {
removeAll();
displayOptions(0);
}
else displayNewState(ind);
}
// forward in a new state's options
else if (a.getActionCommand().startsWith("nextopt:")) {
for (int i=1; i= WIN) {
db.submitGrade("finish", "100");
runWinFinish((nextState % WIN));
}
else if (nextState >= LOSE) {
db.submitGrade("finish", "50");
runLoseFinish((nextState % LOSE));
}
else if (nextState >= INCOMPETENT) {
db.submitGrade("finish", "0");
runIncompetentFinish((nextState % INCOMPETENT));
}
else runNewState(nextState);
}
/**
* runNewState is called when the student's choices lead to a different state
* (i.e., not court or a final result)
*
* @param newState The index of the new state we should display
*/
public synchronized void runNewState(int newState) {
pict = new ImageCanvas(courtIm[lawyerSex]);
BufferedReader ns = openFile(textFile[newState]);
String[][] script = parseNewState(ns);
stateVector = String.valueOf(newState)+",";
tc = new TextCanvas(script, getFont(),
getSize().width-pict.getPreferredSize().width,
pict.getPreferredSize().height-INSET);
displayNewState(0);
if (soundOn) {
sound.stopSound();
sound = new SoundPlayer(soundFile[lawyerSex][newState], this);
sound.playSound(0);
}
}
/**
* runIncompetentFinish runs the finish when the lawyer performs incompetently
*
* @param scene The index of the incompetent finish we should display
*/
public synchronized void runIncompetentFinish(int scene) {
finalResult = INCOMPETENT;
pict = new ImageCanvas(finishIm[lawyerSex][0]);
keep = new Component[1]; keep[0] = pict;
tc = new TextCanvas(parseFinish(0, scene), getFont(),
getSize().width-pict.getPreferredSize().width,
pict.getPreferredSize().height-INSET);
displayScript(9, 0);
if (soundOn) {
sound.stopSound();
sound = new SoundPlayer(finishSound[lawyerSex][0], this);
sound.playSound(scene-1);
}
}
/**
* runLoseFinish runs the finish when the lawyer loses the case
*
* @param scene The index of the losing finish we should display
*/
public synchronized void runLoseFinish(int scene) {
finalResult = LOSE;
pict = new ImageCanvas(finishIm[lawyerSex][1]);
keep = new Component[1]; keep[0] = pict;
tc = new TextCanvas(parseFinish(1, scene), getFont(),
getSize().width-pict.getPreferredSize().width,
pict.getPreferredSize().height-INSET);
displayScript(9, 0);
if (soundOn) {
sound.stopSound();
sound = new SoundPlayer(finishSound[lawyerSex][1], this);
sound.playSound(scene-1);
}
}
/**
* runWinFinish runs the finish when the lawyer wins the case
*
* @param scene The index of the winning finish we should display
*/
public synchronized void runWinFinish(int scene) {
finalResult = WIN;
pict = new ImageCanvas(finishIm[lawyerSex][2]);
keep = new Component[1]; keep[0] = pict;
tc = new TextCanvas(parseFinish(2, scene), getFont(),
getSize().width-pict.getPreferredSize().width,
pict.getPreferredSize().height-INSET);
displayScript(9, 0);
if (soundOn) {
sound.stopSound();
sound = new SoundPlayer(finishSound[lawyerSex][2], this);
sound.playSound(scene-1);
}
}
/**
* parseFinish reads in the finishing text from whichever finish it actually
* is.
*
* @param result The value of the finish, which tells us which file to open
* @param scene The index of the finish we should display
* @return An array of arrays of Strings, encoded as
* String[individual's_speech][line_of_dialog]
*/
private synchronized String[][] parseFinish(int result, int scene) {
BufferedReader input;
switch (result) {
case 0: input = openFile(incompFile); break;
case 1: input = openFile(loseFile); break;
case 2: input = openFile(winFile); break;
default: return new String[][] {{}};
}
String textTemp[][] = new String[20][30];
int paraNum = -1; int lineNum = 0;
boolean reading = false; String line = "";
while (true) {
try { line = input.readLine(); }
catch (IOException i) {
System.err.println("Problem reading in the ending file");
return new String[][] {{}}; }
if (line == null) break;
else if (line.equals("")) continue;
else if (line.startsWith("") && reading) break;
else if (line.startsWith("")) continue;
else if (line.equals("!COMPLETE")) continue;
else if (line.charAt(0) == '<') {
int opt;
try { opt =
Integer.parseInt(line.substring(1, line.indexOf(">"))); }
catch (NumberFormatException n) {
System.err.println("bad index tag!");
return new String[][] {{}}; }
if (opt == scene) reading = true;
}
else if (reading) {
if (line.startsWith("Judge:")) {
paraNum++; lineNum = 0;
textTemp[paraNum][lineNum] = replaceCodes(line);
lineNum++; }
else if (line.startsWith("J:")) {
paraNum++; lineNum = 0;
StringBuffer temp = new StringBuffer(line);
temp.insert(1, "udge");
textTemp[paraNum][lineNum] = replaceCodes(temp.toString());
lineNum++; }
else if (line.startsWith("Opposing:")) {
paraNum++; lineNum = 0;
textTemp[paraNum][lineNum] = replaceCodes(line);
lineNum++; }
else if (line.startsWith("O:")) {
paraNum++; lineNum = 0;
StringBuffer temp = new StringBuffer(line);
temp.insert(1, "pposing");
textTemp[paraNum][lineNum] = replaceCodes(temp.toString());
lineNum++; }
else {
textTemp[paraNum][lineNum] = replaceCodes(line);
lineNum++; }
}
}
String[][] script = new String[paraNum+1][];
for (int i=0; i= 2*max(rowMax, colMax)
int nameNum = 0; String attrib[] = new String[15]; int lineNum = 0;
String line = ""; boolean inData = false;
while (true) {
try { line = input.readLine(); }
catch (IOException i) {
System.err.println("Problem reading in the data file");
return; }
if (line == null) break;
else if (line.equals("")) continue;
else if (line.startsWith("")) { inData = true; }
else if (line.startsWith("")) { inData = false; }
else if (inData) {
if (Character.isWhitespace(line.charAt(0))) { // column names
boolean inName = false;
for (int i=0; i"))); }
catch (NumberFormatException n) {
System.err.println("problem with choice number");
return new String[][] {{}}; }
line = line.substring(line.indexOf(">")+1);
lineNum = 0;
optionsTemp[optNum][choNum][lineNum] = line;
lineNum++; }
else {
if (inText) {
textTemp[paraNum][lineNum] = replaceCodes(line);
lineNum++; }
else {
optionsTemp[optNum][choNum][lineNum] = replaceCodes(line);
lineNum++; }
}
}
String[][] script = new String[paraNum+1][];
for (int i=0; i 0) {
String temp = orig.substring(0, num);
temp += lawyerName;
temp += orig.substring(num+8, orig.length());
orig = temp;
num = orig.indexOf("##name##");
}
num = orig.indexOf("##pronoun##");
while (num > 0) {
String temp = orig.substring(0, num);
if (lawyerSex == FEMALE) { temp += "s"; }
temp += "he";
temp += orig.substring(num+11, orig.length());
orig = temp;
num = orig.indexOf("##pronoun##");
}
return orig;
}
/**
* removeAllExcept removes all of the Components except the ones in the
* argument array.
*
* @param keep The array of Components to keep
*/
private synchronized void removeAllExcept(Component keep[]) {
Component inCont[] = this.getComponents();
CHECK: for (int i=0; i");
try { choNum = Integer.parseInt(line.substring(1, end)); }
catch (NumberFormatException n) {
System.err.println("choice number not valid!");
return; }
lineNum = 0;
optionsTemp[optNum][choNum][lineNum] = line.substring(3);
lineNum++;
}
else if (line.startsWith("