IndexNetLibs.java
/*
* "Net Libs" Copyright (C) by Michael Benson - 7/27/97
*
* Loosely based on "Mac Libs" which is loosely based on the old
* "Mad Libs" books for creating stories from forms.
*/
package NetLibs;
import java.awt.*;
import java.applet.Applet;
import java.util.*;
import java.io.*;
import java.net.*;
import COM.bensoft.base.*;
import COM.bensoft.widgets.*;
/**
* NetLibs is a sort of game which generates silly stories. It is
* loosely based on Mac Libs which is a more sophisticated Macintosh
* version, and that is loosely based on the old Mad Libs books.
*
* The user can choose from several story templates and then generate a
* story automatically by retrieving the words from predefined dictionaries,
* or by typing the words in a form. The templates contain prompts
* which correspond to certain word categories such as noun, verb, adjective, etc.
* Each category has its own dictionary file containing words. When the story
* is generated automatically, the prompts are used to find the categories and
* then choose a word at random. When the user is typing the words in a form,
* the prompts appear next to a text field to show the user what type of word
* is needed. After all words are chosen, the story template is scanned and the
* prompts are replaced by the chosen words. The resulting story is then
* displayed in the window.
*
* The story template files refer to the prompts by putting the index in angle
* brackets like this:
*
* One day, <1> went to the <2>.
*
* This enables the same word to appear in different places in the story.
*
* The template files all end with the ".txt" suffix and have a corresponding
* prompt file with the same name and the ".prompts" suffix. In the prompts
* file, each prompt is on a line separated by a 'new line' character.
* The number in the angle brackets in the template correspond to the line
* number in the prompt file (starting at 1).
*
* The prompts in the prompt file consist of the name of a dictionary file
* without the ".words" suffix. There are also currently two special ways
* prompts can be used. To generate a random number, a prompt like this can
* be used:
*
* number from 1 to 100
*
* Also, any prompt can begin with a captial letter. In that case, the chosen
* word will automatically be capitalized.
*
* The word files all end with the ".words" suffix. Each word is on a line
* separated by a 'new line' character. When a random word is selected, the
* appropriate word file is read in and the words are separated into a vector.
* Then a random number is generated to select the nth word. The word vectors
* are cached, so if a story template requests 5 nouns, the word file will not
* be read and scanned 5 times. Also, if you generate another story with the
* same template, it will be much faster the second time.
*
* Note: In the Macintosh version ("Mac Libs"), the user can create and edit
* templates and dictionaries. Templates and the resulting stories can contain
* formatted text and graphics. Also, the prompts can reference properties
* and rules. The rules can be implemented in the dictionary in a
* HyperCard-like language. For example, the "verb" dictionary has rules for
* "past_participle", "present_participle", etc. This eliminates the need to
* have separate dictionaries for each type.
*
* @author Michael Benson
*/
public class NetLibs extends Applet implements NetLibsConst, CommandListener
{
private static final int _WRAP_FACTOR = 80;
private static final int _PROGRESS_HEIGHT = 11;
private static final int _PROGRESS_INDENT = 70;
private static final String _extraSpace = " ";
private static final String _genStory = "Generate Story";
private static final String _autoGen = "Automatic";
private static final String _askGen = "Prompt for words";
private CheckboxGroup _prompter;
private Choice _storyChoice;
private TextArea _storyTextArea;
private Button _genStoryButton;
private ProgressMeter _progressMeter;
private StoryTemplate _storyTemplate;
private NetLibsCommand _netLibsCommand;
/**
* NetLibs initialization. Creates main applet frame. Fills the
* Story Templates choice box by reading the file "templates.prompts".
*/
public void init()
{
Vector storyNames = null;
Label header;
/*
+------------------------------------------------------------+
| +-----------------------northPanel-----------------------+ |
| | +-------------------------p1-------------------------+ | |
| | | icon "Mac Libs" icon | | |
| | +-------------------------p2-------------------------+ | |
| | | +------p3------+ | | |
| | | _storyChoice | o Automatic | "Generate Story" | | |
| | | | o Prompt | | | |
| | | +--------------+ | | |
| | +----------------------------------------------------+ | |
| +-----------------------centerPanel----------------------+ |
| | | |
| | _storyTextArea | |
| | | |
| +-----------------------southPanel-----------------------+ |
| | _progressMeter | |
| +--------------------------------------------------------+ |
+------------------------------------------------------------+
East and west have empty panels for margin space.
Also, p3 is a 1 by 2 grid. The others are border layouts.
*/
Panel northPanel = new Panel();
northPanel.setLayout(new BorderLayout());
Panel p1 = new Panel();
p1.add(new ImageCanvas(this, "images/maclibs.gif", 32, 32));
p1.add(header = new Label("Net Libs", Label.CENTER));
p1.add(new ImageCanvas(this, "images/resume.gif", 32, 32));
Font headerFont = new Font("Helvetica", Font.PLAIN, 24);
header.setFont(headerFont);
northPanel.add("North", p1);
Panel p2 = new Panel();
p2.add(new Label("Story template:", Label.RIGHT));
p2.add(_storyChoice = new Choice());
_storyTemplate = new StoryTemplate();
// Add story names to the popup menu:
try {
storyNames = _storyTemplate.getPrompts(this, "templates");
} catch (IOException e) {
System.out.println(e);
}
for (int i = 0; i < storyNames.size(); i++) {
_storyChoice.addItem((String)storyNames.elementAt(i));
}
p2.add(new Label(_extraSpace, Label.CENTER));
Panel p3 = new Panel();
p3.setLayout(new GridLayout(2, 1));
_prompter = new CheckboxGroup();
p3.add(new Checkbox(_autoGen, _prompter, true));
p3.add(new Checkbox(_askGen, _prompter, false));
p2.add(p3);
p2.add(new Label(_extraSpace, Label.CENTER));
_genStoryButton = new Button(_genStory);
p2.add(_genStoryButton);
northPanel.add("Center", p2);
Panel centerPanel = new Panel();
centerPanel.setLayout(new GridLayout(1, 1));
centerPanel.add(_storyTextArea = new TextArea());
Panel southPanel = new Panel();
_progressMeter = new ProgressMeter();
_progressMeter.resize(MAXWIDTH - _PROGRESS_INDENT, _PROGRESS_HEIGHT);
southPanel.add(_progressMeter);
setLayout(new BorderLayout());
add("North", northPanel);
add("Center", centerPanel);
add("South", southPanel);
add("East", new Label(_extraSpace, Label.CENTER));
add("West", new Label(_extraSpace, Label.CENTER));
// NetLibsCommands handles listeners and delegating commands:
_netLibsCommand = new NetLibsCommand(this);
_netLibsCommand.addCommandListener(this);
resize(MAXWIDTH, MAXHEIGHT);
}
/**
* Get the NetLibsCommand object to issue a command.
*
* @return the NetLibsCommand object.
*/
public NetLibsCommand getNetLibsCommand()
{
return _netLibsCommand;
}
/**
* Overrides the action method in Component to check for
* button clicks.
*
* @param evt the event that caused the action.
* @param arg the action.
* @return true if the event has been handled; false if not.
* @see java.awt.Component#action()
*/
public boolean action(Event evt, Object arg)
{
boolean ret = true;
// "Generate Story" button clicked:
if (_genStory.equals(arg)) {
if (_autoGen.equals(_prompter.getCurrent().getLabel())) {
// Generate story automatically:
_netLibsCommand.doCommand(CMD_AUTO_GENERATE_STORY);
_netLibsCommand.doCommand(CMD_STORY_GENERATED);
} else {
// Prompt user for words:
_netLibsCommand.doCommand(CMD_PROMPT_GENERATE_STORY);
}
} else {
ret = false;
}
return ret;
}
/**
* Generates a story automatically from the dictionaries. Reads the prompt
* file, gets a random word from a dictionary for each prompts, then
* substitutes the words into the story.
*/
public void autoGenerateStory()
{
try {
_autoGenerateStory();
} catch (IOException e) {
System.out.println(e);
_storyTextArea.setText("");
}
}
/**
* Generates a story automatically from the dictionaries. Reads the prompt
* file, gets a random word from a dictionary for each prompts, then
* substitutes the words into the story.
*
* @exception IOException if error reading prompt file.
* @exception MalformedURLException if error finding prompt file.
*/
private void _autoGenerateStory() throws IOException, MalformedURLException
{
Vector strings = new Vector();
String word;
// Get the word prompts:
Vector prompts = _storyTemplate.getPrompts(this, _storyChoice.getSelectedItem());
// For each prompt, get a random word:
_progressMeter.setLimits(0, (prompts.size() * 3) / 2);
for (int i = 0; i < prompts.size(); i++) {
word = _storyTemplate.getRandomWord(this, (String)(prompts.elementAt(i)));
strings.addElement(word);
_progressMeter.increment(1);
}
// Put the words into the story:
substituteWords(_storyChoice.getSelectedItem(), strings);
}
/**
* Generates a story by prompting the user for words. Reads the prompt
* file, then opens the prompt window. When the user clicks "OK" in
* that window, the words will be substituted into the story.
*/
public void promptGenerateStory()
{
try {
_promptGenerateStory();
} catch (IOException e) {
System.out.println(e);
_storyTextArea.setText("");
}
}
/**
* Generates a story by prompting the user for words. Reads the prompt
* file, then opens the prompt window. When the user clicks "OK" in
* that window, the words will be substituted into the story.
*
* @exception IOException if error reading prompt file.
* @exception MalformedURLException if error finding prompt file.
* @see NetLibs.PromptWindow#action()
*/
private void _promptGenerateStory() throws IOException, MalformedURLException
{
// Get the word prompts:
Vector prompts = _storyTemplate.getPrompts(this, _storyChoice.getSelectedItem());
// Set up and open the prompt window:
_progressMeter.setLimits(0, prompts.size());
PromptWindow pwin = new PromptWindow(this, prompts, _storyChoice.getSelectedItem());
pwin.show();
}
/**
* Gets the story template and substitutes the chosen words into the story.
*
* @param storyName the name of the story template.
* @param strings the list of words to be substituted into the story.
*/
public void substituteWords(String storyName, Vector strings)
{
try {
String template = _storyTemplate.getTemplate(this, storyName);
_showStory(template, strings);
} catch (IOException e) {
System.out.println(e);
}
}
/**
* Scan through the story template, substitute the chosen words, and
* display the resulting story.
*
* @param template the story template text.
* @param strings the list of words to be substituted into the story.
*/
private void _showStory(String template, Vector strings)
{
StringBuffer story = new StringBuffer();
int wrapIndex = 0;
boolean prefixAorAn = false;
boolean capitalize = false;
StringBuffer strIndex;
Integer intIndex;
int index;
char theChar;
String str;
String aoran;
// Figure out the progress meter increments:
int incr = template.length() / (_progressMeter.getMax() - _progressMeter.getValue());
int count = 0;
// Look through the template character by character and
// substitute words from the vector whenever a "<" character
// is encountered:
int i = 0;
while (i < template.length()) {
theChar = template.charAt(i);
// To get around the word wrapping bug in some versions
// of Java, let's just keep track of it here:
if (wrapIndex >= _WRAP_FACTOR && theChar == ' ') {
// Insert a carriage return:
story.append('\n');
wrapIndex = 0;
}
else if (theChar == '<') {
// Find the number or prompt in the "" brackets:
i++;
strIndex = new StringBuffer();
while (template.charAt(i) != '>') {
strIndex.append(template.charAt(i));
i++;
}
// Look for "a" prompt (indefinite article):
if (strIndex.charAt(0) == 'a') {
prefixAorAn = true;
capitalize = false;
} else if (strIndex.charAt(0) == 'A') {
prefixAorAn = true;
capitalize = true;
} else {
// Convert the string to a number:
index = 0;
try {
intIndex = new Integer(strIndex.toString());
index = intIndex.intValue();
} catch (NumberFormatException e) {
System.out.println(e);
}
// Substitute the corresponding word:
if (index > 0) {
str = (String)(strings.elementAt(index - 1));
if (prefixAorAn) {
// Prefix "a" or "an" if necessary:
aoran = BensUtils.getIndefiniteArticle(str, capitalize);
wrapIndex += aoran.length() + 1;
story.append(aoran);
story.append(" ");
prefixAorAn = false;
capitalize = false;
}
wrapIndex += str.length();
story.append(str);
}
}
}
else {
// Copy the character to the story buffer, but skip
// one redundant space in the beginning of the line:
if (theChar == ' ' && wrapIndex == 0) {
wrapIndex++;
}
else if ( ! (theChar == ' ' && prefixAorAn)) {
story.append(theChar);
// Take care of the kludgy word wrapping:
if (theChar == '\n') {
wrapIndex = 0;
} else {
wrapIndex++;
}
}
}
i++; // Next character.
// Increment the progress meter if necessary:
count++;
if (count > incr) {
count = 0;
_progressMeter.increment(1);
}
}
// Show the story:
_storyTextArea.setText(story.toString());
_storyTextArea.select(0, 0);
}
/**
* The story is about to be generated. Take care of various interface
* elements. For example, disable the "Generate Story" button, reset
* the progress meter, etc.
*/
private void _preGenerateStory()
{
_progressMeter.setValue(0);
_genStoryButton.disable();
_genStoryButton.update(_genStoryButton.getGraphics());
}
/**
* The story generation is complete. Take care of various interface
* elements. For example, enable the "Generate Story" button, reset
* the progress meter, etc.
*/
private void _postGenerateStory()
{
_progressMeter.setValue(0);
_genStoryButton.enable();
_genStoryButton.update(_genStoryButton.getGraphics());
}
//
// CommandListener interface:
//
/**
* This is a "listener" which is called whenever a command is
* selected. It is used for doing things like updating buttons,
* menu items, etc.
*
* @param command the command which was executed.
* @see NetLibsConst
*/
public boolean commandSelected (int command)
{
boolean ret = true;
switch(command) {
case CMD_AUTO_GENERATE_STORY:
_preGenerateStory();
break;
case CMD_PROMPT_GENERATE_STORY:
_preGenerateStory();
break;
case CMD_STORY_GENERATED:
_postGenerateStory();
break;
default:
// No action for default case.
}
return ret;
}
}