Getting Started
- new project, possibly called Adventure, in your IDE.
- Download the Adventure.java, TestAdventure.java Config.java, and wumpus.advcfg and put them in the appropriate project folder. If your project has an src folder then put the .java files there. The wumpus.advcfg file can go in the project folder itself.
- Significant parts of the code are provided for you. Read through them to get a sense of what they are doing. Look at the example output below to see an example execution of the game.
Implement Methods
- Implement and test the following methods: parseCoordinates, parseFields, determineLocation, enter and writeMap per their method header comments. Make sure that your code follows the various style and commenting standards.
- Submit your code to Final Project – Part A.
- Your enhancements in Part B may change the working of the project such that the tests in Final Project – Part A no longer pass. So, make sure all the Final Project – Part A tests pass, prior to implementing extensions to the program. The highest grade achieved in Final Project – Part A, prior to the deadline, will be kept.
Extend the Project
- Extend the code in the following ways:
- your own configuration (.advcfg) file. See wumpus.advcfg for notes and an example. For full credit, it must be uniquely yours, different from the example, and include at least 4 different kinds of things. For example, wumpus.advcfg contains wumpus, child, bats and pits.
- Choose at least 1 of the following ways to extend the game:
- Tools: added your own tools/weapons and use of them in the game, maybe rope, arrows, rocks, etc.
- Audio: playing of audio files when sensing the neighbors, rather than simply text. Pass the names of the sound files through the .advcfg file. A playSound method is provided.
- Test Cases: adding test cases to TestAdventure.java that thoroughly test at least 3 of the parseCoordinates, parseFields, determineLocation, enter and writeMap methods, for full credit.
- Your Own: describe any other significant changes or additions we should consider for credit. Should be as significant an effort as these other options for full credit.
- Complete the READ ME notes to the Grader in the bottom of the Config.java file describing your extensions and choices.
- Submit your code to Final Project – Part B for Gradescope automated and human grading.
Final Submission
- Make sure your code follows all the standards in the Grading Rubric and expectations described in the CS 200 Style Guide. Turn in the version with the implemented methods to zyBooks Final Project – Part A for zyLab grading. Complete the extensions, clearly document them at the bottom of the Config.java file, and submit to Final Project – Part B for Gradescope automated and human grading. The latest version that compiles and runs in Part B prior to the deadline Tuesday, December 15th, at 10:05 am (our scheduled final exam time) will be the version that is graded.
- We anticipate completing grading by Saturday, December 19th. You will only have 24 hours for a regrade request as we must turn in final grades by Monday.
Grading Rubric
Commenting and Documentation
- File and JavaDoc Class Header
- JavaDoc Method Headers include a summary description, algorithmic comments, and @param and possibly @return tags with descriptions.
Style and Structure
- Appropriate use of spacing, both vertical and horizontal
- No lines exceed 100 columns
- Consistent placement of parentheses and braces
- Helpful identifier naming that follows course conventions (e.g., methods, variables, constants)
- Doesn’t use Java features past Chapter 12 such as packages, and StringBuilder.
Extensions
These should all be, clearly, uniquely yours.
- Configuration Example: Looking for an interesting (not too simple) map for someone to play. For full credit, it must be different from wumpus.advcfg and include at least 4 different kinds of things.
- Configuration Description: Describe features of your configuration.
- writeMap output: A reasonable map of the game showing the names of the things in a grid pattern that is used for debugging purposes, not for the player. For full credit the map should shown the names (Config.NAME) in a grid (rows/columns) and also designate the location of the player.
- Your choice of extending the game:
- Tools: added your own tools/weapons and use of them in the game, maybe rope, arrows, rocks, etc. For full credit, at least one tool/weapon configured through the .advcfg file, with use of the tool/weapon through the player’s menu.
- Audio: playing of audio files when sensing the neighbors, rather than simply text. Pass the names of the sound files through the .advcfg file and play at the appropriate time in the enter and/or sense methods, for full credit. A playSound method is provided.
- Test Cases: adding test cases to TestAdventure.java that thoroughly test at least 3 of the parseCoordinates, parseFields, determineLocation, enter and writeMap methods, for full credit.
- Your Own: describe any other significant changes or additions we should consider for credit. Should be as significant an effort as these other options for full credit.
Example of Game Play
(Command line options: -d -s 123)
DEBUG: Drawing map in map.txt Welcome to the Wumpus Adventure! Find the child without getting eaten by the wumpus. May you return safely! > n DEBUG: player location 0,4 > w)up s)down a)left d)right n)nearby q)quit DEBUG: player location 0,4 > w open DEBUG: player location 7,4 > n you hear a rustling DEBUG: player location 7,4 > s Welcome to the Wumpus Adventure! Find the child without getting eaten by the wumpus. May you return safely! DEBUG: player location 0,4 > n DEBUG: player location 0,4 > s open DEBUG: player location 1,4 > n DEBUG: player location 1,4 > s open DEBUG: player location 2,4 > n you hear a rustling DEBUG: player location 2,4 > s A huge bat picked you up and dropped you somewhere… open DEBUG: player location 4,1 > a open DEBUG: player location 4,0 > n DEBUG: player location 4,0 > a open DEBUG: player location 4,5 > n awful smell you feel a draft DEBUG: player location 4,5 > d open DEBUG: player location 4,0 > d open DEBUG: player location 4,1 > d open DEBUG: player location 4,2 > w open DEBUG: player location 3,2 > n you hear a child softly crying DEBUG: player location 3,2 > a open DEBUG: player location 3,1 > n DEBUG: player location 3,1 > d open DEBUG: player location 3,2 > w You’ve found the child safe and happy to see you! DEBUG: player location 2,2 Thanks for playing!
Here is Adventure.java
//TODO
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;
/**
* This class contains a text based adventure game.
*
* @author Jim Williams
* @author TODO when you contribute, add your name.
*/
public class Adventure {
/**
* This writes a map to a file showing the Config.NAME field at each set of coordinates
* and indicates which location the player is at.
*
* This is intended for the programmer to have a visualization of the map while they are
* debugging the map and program. It is not intended for the player as it would give away
* the experience of exploring the map. Therefore, there is flexibility with
* how you display the information. When you submit, include an example output in the
* comments at the bottom of Config.java for grading.
*
* @param map The map, indexes are row, column, fields/attributes.
* @param player The players current location.
* @param fileName The name of the file the map is written to.
*/
public static void writeMap(String[][][] map, int[] player, String fileName) {
//TODO output a map to the file
}
/**
* This determines if there are integer coordinates preceding a / at the beginning
* of a line. If so, the coordinates are returned as an array of int, with the
* first integer at index ROW and the second integer at index COLUMN. If there is not
* a set of coordinates, then null is returned.
*
* Examples of coordinate strings and their results:
* “” should return null
* “0,4” should return an array with {0,4}
* “8,6” should return an array with {8,6}
* “public class…” should return null
* “/*” should return null
*
* The indexes into the fields array are Config.NAME, Config.ENTER_TEXT, Config.ENTER_RESULT,
* and Config.NEARBY_TEXT.
*
* @param coordinateString Contains the portion of the line read from the .advcfg file before
* the first /.
* @return an int array containing the coordinates separated by a comma, or
* null.
*/
public static int[] parseCoordinates(String coordinateString) {
return null; //TODO
}
/**
* This returns an array of field/attribute information as an array from a line read
* from the .advcfg file after the first /.
*
* The fields are returned as an array of String with index 0 containing the value
* in the first field following the coordinates on the input line.
*
* Examples:
* “size/This is the size of the map//” should return {“size”,”This is the size of the map”,””,””}
* ” // /” should return {“”,””,””,””}
* “*” should return {“*”}
*
* Hint: Use the String class split method to get an array and then
* trim each of the fields.
* fieldInformation.split(“/”,-1); //the -1 means keep blank fields
*
* @param fieldInformation Contains field information read from a line in the Config.java file
* after coordinates and the first /.
* @return The fields in the form of a trimmed array of Strings.
*/
public static String[] parseFields(String fieldInformation) {
return null; //TODO
}
/**
* This reads the map configuration information from the specified file. The map is returned
* and the playerStartingLocation array is updated with the starting row and column of
* the player.
*
* Algorithm:
* Open the file
* Read each line
* if the line contains / then call parseCoordinates with the part of the line
* before the /,
* Otherwise go to the next input line.
* If parseCoordinates returns null, then go to the next input line.
* Otherwise, call parseFields with the part of the line after the / to obtain the
* fields array
* If these are the first coordinates read from the file, then they are the
* number of rows and columns in the map. Allocate the map array
* with this number of rows and columns, leaving the 3rd dimension
* (fields/attributes) null for the moment.
* If these are Not the first coordinates read from the file, then they are
* location coordinates and put the fields array at these coordinates
* in the map. If the Config.NAME field contains Config.NAME_START then
* update the playerStartingLocation to be this location.
* return the map.
*
* @param fileName The configuration file to read, typically ending with .advcfg.
* @param playerStartingLocation Updated within this method to contain the starting row
* and column of the player.
* @return The map with the 3 dimensions being, row, column and then fields/attributes at that
* row and column in the map.
* @throws FileNotFoundException On error opening the file.
*/
public static String[][][] loadAdventure(String fileName, int[] playerStartingLocation)
throws FileNotFoundException {
File file = new File(fileName);
Scanner fileInput = new Scanner(file);
String[][][] map = null;
boolean sizeOfMap = true;
while (fileInput.hasNextLine()) {
String line = fileInput.nextLine();
int firstFieldSeparator = line.indexOf(“/”);
if (firstFieldSeparator
continue;
}
int[] coords = parseCoordinates(line.substring(0, firstFieldSeparator));
if (coords == null) {
continue;
}
String[] fields = parseFields(line.substring(firstFieldSeparator + 1));
if (sizeOfMap) {
map = new String[coords[Config.ROW]][coords[Config.COLUMN]][];
sizeOfMap = false;
} else {
if (fields[Config.NAME].equalsIgnoreCase(Config.NAME_START)) {
playerStartingLocation[Config.ROW] = coords[Config.ROW];
playerStartingLocation[Config.COLUMN] = coords[Config.COLUMN];
}
map[coords[Config.ROW]][coords[Config.COLUMN]] = fields;
}
}
return map;
}
/**
* Given the map, the currentLocation of the player, and the direction
* of movement, this returns a new array with the new location.
*
* Note that the map wraps, such that moving to the right passed the maximum column result
* in moving to the 0th column, similarly, for rows.
*
* @param map The map indexes are row, column, fields/attributes.
* @param currentLocation The current row and column of the player, must not change.
* @param direction The direction of movement Config.UP, Config.DOWN, Config.LEFT,
* Config.RIGHT
* @return The new row and column given the direction of movement.
*/
public static int[] determineLocation(String[][][] map, int[] currentLocation, String direction) {
return null; //TODO
}
/**
* This randomly picks a location in the map that has null fields/attributes
* and changes the row and column of the location parameter to be that new location.
* The map is assumed to be rectangular and valid locations are those locations
* that have null for the field/attributes values in the map.
*
* @param rand A random number generator
* @param map The map, that we can assume is rectangular.
* @param location Changed to be the coordinates of the new location.
*/
public static void pickRandomLocation(String[][][] map, int[] location, Random rand) {
ArrayList availableLocations = new ArrayList<>();
for ( int row = 0; row
for ( int column = 0; column
if ( map[row][column] == null) {
availableLocations.add( new int[]{row,column});
}
}
}
if ( availableLocations.size() > 0) {
//randomly pick an available location
int [] newLocation = availableLocations.get( rand.nextInt( availableLocations.size()));
//update location
location[Config.ROW] = newLocation[Config.ROW];
location[Config.COLUMN] = newLocation[Config.COLUMN];
} else {
System.out.println(“Error, no new random location to move to found in the map.”);
}
}
/**
* Check each of the 4 direct neighbors (up, down, left, right) and show their sounds
* or smells. The neighbors are checked in a random order so that the order they are
* shown doesn’t provide a clue to the player.
*
* @param map The map indexes are row, column, fields/attributes.
* @param location The player’s location.
* @param rand A random number generator.
*/
public static void senseNearby(String[][][] map, int[] location, Random rand) {
ArrayList neighbors = new ArrayList<>(
java.util.Arrays.asList(Config.MOVE_UP, Config.MOVE_DOWN, Config.MOVE_LEFT,
Config.MOVE_RIGHT));
while (neighbors.size() > 0) {
//randomly choose a neighbor to check and remove them from the list
int index = rand.nextInt(neighbors.size());
String direction = neighbors.get(index);
neighbors.remove(index);
//calculate a new location based on the current location and direction
int[] tempLocation = determineLocation(map, location, direction);
//obtain the fields from the current location in the map.
String[] fields = map[tempLocation[Config.ROW]][tempLocation[Config.COLUMN]];
if (fields != null && !fields[Config.NEARBY_TEXT].isBlank()) {
System.out.println(fields[Config.NEARBY_TEXT]);
}
}
}
/**
* The player enters the location in the map. Describe that location to the player.
*
* Algorithm:
* Get the fields for the specific player location.
* If the fields are not null and the Config.ENTER_RESULT field is
* Config.RESULT_RANDOM_MOVE then print out Config.ENTER_TEXT (describes what is
* happening) to the player, and then call pickRandomLocation to get a new location
* Get the fields for the new location.
* If the fields are null, then print “open” and return false.
* Otherwise, if Config.ENTER_RESULT field is Config.RESULT_WIN or Config.RESULT_LOSE
* then print out Config.ENTER_TEXT and return true. Otherwise print out
* Config.ENTER_TEXT and return false.
*
* @param map The map indexes are row, column, fields/attributes.
* @param location The players location as row and column in the map
* @param rand A random number generator
* @return true on game finished, false otherwise.
*/
public static boolean enter(String[][][] map, int[] location, Random rand) {
return false; //TODO
}
/**
* This method plays a sound file.
*
* Many sound files are available on the web or you can make your
* own in the .wav format. Some are free for personal use.
* http://www.animal-sounds.org/farm-animal-sounds.html
* http://soundbible.com/
* https://www.freesoundeffects.com/free-sounds/wind-sounds-10041/
*
* The call:
* playSound(“sounds/wumpus.wav”);
* means there is a sounds folder within the IDE project folder.
* Within that folder there is a sound file named wumpus.wav.
* Play the “sounds/wumpus.wav” sound file.
*
* Oracle’s Java Tutorials can be helpful in learning much more about a topic:
* https://docs.oracle.com/javase/tutorial/sound/index.html
*
* @param wavFileName Name of the .wav file to play.
*/
public static void playSound(String wavFileName) {
File file = new File(wavFileName);
if (file.exists()) {
try {
AudioInputStream audioInputStream = AudioSystem.getAudioInputStream(file);
Clip clip = AudioSystem.getClip();
clip.open(audioInputStream);
clip.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* This contains the starting configuration and the game loop. Within the game loop
* the player is prompted and the results are shown, until the game is finished.
*
* @param args Used for testing and debugging information.
*/
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String configurationFile = “wumpus.advcfg”;
String mapFilename = “map.txt”;
boolean seedInput = false;
long seed = 0;
boolean debug = false;
//Developer & tester command-line options so not shown to a player.
//You may add to these options for your own use but make sure these all work the same
//as we will be using them when testing.
//
//Usage: java Adventure [-s seed] [-c configuration_filename] [-m map_filename] [-d]
for ( int i = 0; i
if ( args[i].equalsIgnoreCase(“-s”)) {
seed = Long.parseLong(args[i+1]);
seedInput = true;
++i; //additional increment since we are reading the seed
} else if ( args[i].equalsIgnoreCase(“-c”)) {
configurationFile = args[i+1];
++i; //additional increment since we are reading the filename
} else if ( args[i].equalsIgnoreCase(“-m”)) {
mapFilename = args[i+1];
++i; //additional increment since we are reading the filename
} else if ( args[i].equalsIgnoreCase(“-d”)) {
debug = true;
}
}
//if a seed is passed on the command line, use it for the random number generator.
Random rand;
if(seedInput) {
rand = new Random(seed);
} else {
rand = new Random();
}
//load the map and the initial player location in the map
int[] playerLocation = new int[]{0, 0};
String[][][] map = null;
try {
map = loadAdventure(configurationFile, playerLocation);
if (map == null) {
return;
}
} catch ( FileNotFoundException e) {
System.out.println(“Error, unable to load file: ” + configurationFile);
return;
}
if (debug) {
System.out.println(“DEBUG: Drawing map in ” + mapFilename);
writeMap(map, playerLocation, mapFilename);
}
//show current location and then loop until game is complete.
boolean finished = enter(map, playerLocation, rand);
while (!finished) {
//prompt for player movement
System.out.print(“> “);
String direction = input.nextLine().trim().toLowerCase();
switch (direction) {
case Config.MOVE_UP:
case Config.MOVE_DOWN:
case Config.MOVE_LEFT:
case Config.MOVE_RIGHT:
playerLocation = determineLocation(map, playerLocation, direction);
finished = enter(map, playerLocation, rand);
break;
case Config.NEARBY:
senseNearby(map, playerLocation, rand);
break;
case Config.QUIT:
finished = true;
break;
default:
System.out.println( Config.MOVE_UP + “)up ” + Config.MOVE_DOWN + “)down ”
+ Config.MOVE_LEFT + “)left ” + Config.MOVE_RIGHT + “)right ”
+ Config.NEARBY + “)nearby ” + Config.QUIT + “)quit”);
break;
}
if (debug) {
System.out.printf(“DEBUG: player location %d,%d\n”, playerLocation[Config.ROW], playerLocation[Config.COLUMN]);
writeMap(map, playerLocation, mapFilename);
}
}
System.out.println(“Thanks for playing!”);
}
}
Here is Config.java
///////////////////////// TOP OF FILE COMMENT BLOCK ////////////////////////////
//
// Title: A text based adventure program.
// Course: CS200 Fall 2020
//
// Author: your name
// Email: your @wisc.edu email address
// Lecturer’s Name: name of your lecturer
//
///////////////////////////////// CITATIONS ////////////////////////////////////
//
// Source or Recipient; Description
// Examples:
// Jane Doe; helped me with for loop in reverse method
// https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html;
// counting for loop
// John Doe; I helped with switch statement in main method.
//
/////////////////////////////// 80 COLUMNS WIDE ////////////////////////////////
/**
* This file contains constants for the text Adventure project. Also at the bottom of this file
* are READ ME Notes for the Grader.
*
* @author Jim Williams
* @author TODOadd your name here when you have made changes
*/
public class Config {
/**
* Indexes into coordinate arrays specifying a location in the map.
*/
public static final int ROW = 0;
public static final int COLUMN = 1;
/**
* Indexes into the fields/attributes array for each map location (3rd dimension of
* the map array)
*/
public static final int NAME = 0;
public static final int ENTER_TEXT = 1;
public static final int ENTER_RESULT = 2;
public static final int NEARBY_TEXT = 3;
//add additional indexes here for additional field information added to each configuration
//line.
/**
* Values within the location fields/attributes that are used for game logic other than
* simply printing out.
*/
//In name field, designates the starting location of the player
public static final String NAME_START = “start”;
//The result of the player entering the field.
public static final String RESULT_RANDOM_MOVE = “randomMove”;
public static final String RESULT_WIN = “win”;
public static final String RESULT_LOSE = “lose”;
//add additional game logic fields here (not text fields that are simply printed).
/**
* Values used by the player to navigate through the map.
*/
public static final String MOVE_UP = “w”;
public static final String MOVE_DOWN = “s”;
public static final String MOVE_LEFT = “a”;
public static final String MOVE_RIGHT = “d”;
public static final String NEARBY = “n”;
public static final String QUIT = “q”;
//add additional game control characters here, throwing, shooting, etc.
}
/*
Your READ ME notes to the Grader
Describe your enhancements to the program, map, etc. here. You may include a link to a
YouTube video of a maximum of 2 minutes demonstrating your program.
0) Overview of your changes or a YouTube video link (max 2 minutes)
1) Configuration Example: Contents of your own configuration (.advcfg) file
2) Configuration Description: Describe unique elements in your configuration file
3) writeMap Output: Example output of the writeMap method for your configuration file.
4) Choose at least 1 of the following ways you extended the game and describe them:
a) Tools: added your own tools/weapons and use of them in the game
b) Audio: playing of audio files when sensing the neighbors, rather than simply text
c) Test Cases: adding test cases to TestAdventure that thoroughly test the methods you wrote.
d) Your Own: describe any other changes or additions we should consider for credit.
*/
Here is TestAdventure.java
///////////////////////// TOP OF FILE COMMENT BLOCK ////////////////////////////
//
// Title: descriptive title of the program making use of this file
// Course: course number, term, and year
//
// Author: your name
// Email: your @wisc.edu email address
// Lecturer’s Name: name of your lecturer
//
///////////////////////////////// CITATIONS ////////////////////////////////////
//
// Source or Recipient; Description
// Examples:
// Jane Doe; helped me with for loop in reverse method
// https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html;
// counting for loop
// John Doe; I helped with switch statement in main method.
//
/////////////////////////////// 80 COLUMNS WIDE ////////////////////////////////
import java.util.Arrays;
/**
* This contains testing methods for the Adventure program.
*/
public class TestAdventure {
/**
* Uncomment testing methods to have them run.
* @param args unused
*/
public static void main(String[] args) {
testParseCoordinates();
//testParseFields();
//testDetermineLocation();
//testEnter();
//TODOadd tests and testing methods
}
/**
* Tests for the parseCoordinates method.
*/
private static void testParseCoordinates() {
boolean error = false;
{ //example test:
String coordinateString = “8,6”;
int [] expected = new int[]{8,6};
int [] actual = Adventure.parseCoordinates(coordinateString);
if ( !Arrays.equals(expected,actual)) {
System.out.println(“testParseCoordinates 1) Expected: ” + Arrays.toString( expected)
+ ” Actual: ” + Arrays.toString( actual));
error = true;
}
}
//Additional tests for testParseCoordinates
if ( error) {
System.out.println(“Error in testParseCoordinates.”);
} else {
System.out.println(“All tests in testParseCoordinates passed.”);
}
}
}