Assignment 13

Task

Combine everything you've learned so far to write a very simple maze game. (The maze is simple, not the program! Compare to Rogue and NetHack.)

Concepts: nested loops and conditionals; constants
Textbook: chapter 5 (review); 2.2 (constants)

Overview

Write a turn-based maze game. The maze is a simple 12x8 rectangle with a single exit. (So it's really just a big room, not a maze.) The player is represented by a single character on the screen. The player starts at a random place somewhere in the maze-room, and the exit is at some random location on the right/east side of the maze-room.

Each turn, print the current map of the maze-room and ask the user which direction they want to move.

The game ends when the player reaches the exit. Print some congratulatory message and end the program.

Requirements

Drawing the map. The map must be the requested size, but which characters you use for the player, exit, and floor are up to you (as long as each is different). Here is what the maze might look like on the screen using '*' for the player, '=' for the exit, and '.' for the room floor.

............
............
............
......*.....
............
............
...........=
............
Enter next move (N/E/S/W):

On the other hand, you might use 'P' for the player, 'E' for the exit, and ' ' for the floor. (' ' works for the floor only if you do the borders extra credit, below.)

--------------
|            |
|            |
|            |
|      P     |
|            |
|            |
|           E
|            |
--------------
Which way do you want to move [(u)p, (d)own, (l)eft, or (r)ight]?

Input characters. You may choose from one of 3 direction input systems:

Your program is required to accept one of these systems, using lowercase letters. Clearly indicate in your program's output how the user should enter a move.

You may support additional input options, if you want: more than one system, uppercase characters, typed words, numbers, etc. (Note that it's easy to accept diverse input if you just uppercase or lowercase the input and then take the first character.)

Constants. In your program, use final variables to hold the following details:

You should be able to change only the values assigned to these variables in order to change the size, shape, or look of your maze. (Of course, your program should still work correctly after making such changes.)

If you want to use methods, you can declare your constants inside your class but outside of any method, like this:

  //constants used in drawing the maze
  public static final int MAZE_WIDTH = 12;
  public static final int MAZE_HEIGHT = 8;
  //...etc...

If you do this, these variables will be accessible from any method.

Instructions to the User. Be sure to explain any necessary rules, important characters, or input requirements in your user interface.

Player Placement and Movement. Place the player and exit randomly at the start of the game. (The exit should be somewhere on the right/east wall of the maze though.) Be careful not to initially place the player on the exit square. Also, do not let the player move beyond the bounds of the maze.

Error-handling. If the player enters something other than one of the valid movement choices, print an appropriate error message and ask the user to try again.

Coding Standards. As with all assignments since A11, you must follow Java Coding Standards while writing the code for this program.

Sample output

Here is some sample output to give you a better idea of how a sample game might look from start to finish:

Move your character (*) to the exit (#).
You may move North, South, East, or West.

............
...........#
............
........*...
............
............
............
............
Enter next move (N/E/S/W): n

............
...........#
........*...
............
............
............
............
............
Enter next move (N/E/S/W): E

............
...........#
.........*..
............
............
............
............
............
Enter next move (N/E/S/W): down
Sorry, but "down" is not a valid direction choice.  Try again.
Enter next move (N/E/S/W): quit
Sorry, but "quit" is not a valid direction choice.  Try again.
Enter next move (N/E/S/W): e

............
...........#
..........*.
............
............
............
............
............
Enter next move (N/E/S/W): e

............
...........#
...........*
............
............
............
............
............
Enter next move (N/E/S/W): e
You can't leave the maze except through the exit!

............
...........#
...........*
............
............
............
............
............
Enter next move (N/E/S/W): n

You've made it out of the maze!

Hints

As long as you satisfy the above requirements for the program (also see Grading, below, to double-check), you can go about solving this problem however you want. However, if you're stuck, here are the steps I'd suggest.

However you choose to go about it, remember to program one step at a time! You should be compiling, running, and testing your program after every few lines you write to make sure everything you've written so far is correct. Often, when writing loops, it helps to write code from the inside out, rather than top to bottom.

  1. Define constant variables for the maze details: width, height, floor, player, exit.
  2. Define a variable for the location of the exit. Since this will always be on the right wall of the maze-room, you really only need the row/y-coordinate. Consider something like this:
    mazeExitY = (int) (Math.random() * MAZE_HEIGHT);
    
    If MAZE_HEIGHT is currently 10, this will set mazeExitY to some random number between 0 and 9.
  3. Define two variables that will hold the player's location. You need an X (column) and Y (row) position of where the player is on the board. These will initially be assigned a random position.

    How you determine the coordinates of your maze-room is up to you. It's generally traditional in graphics to treat the upper-left corner as (0,0). This would make the lower-right corner of the room (MAZE_WIDTH - 1, MAZE_HEIGHT - 1). (The rest of these suggestions assume this system.) However, you could make the lower-left corner your origin, and/or you might want to start indexing at (1,1).

    Remember to try printing out your variables a few times before continuing to make sure you're getting random numbers in the complete range.

  4. Print the maze-room. This is probably the trickiest part of this assignment.
  5. Get the next move from the player.
  6. Update the player's position accordingly. For example, if the player is moving north/up, playerY--; would update their position. However, before moving the character, make sure this doesn't move them off the board/out of the maze.
  7. Move loop. Now you're finally ready to build a loop that keeps asking the player to move until they reach the exit location.

It is recommended you use methods to break up the program into more manageable chunks, but it is not required.

Extra Credit

Once you meet the basic requirements for this maze game, you may add some additional features. Here are a few suggestions:

You may come up with additional ideas of your own. If you include any extra features, be sure to mention them in your initial class documentation.

Only a total +3 points of EC will be awarded, and only if the required features of the game work.

What to Submit

Upload your UsernameA13.java file to Tamarin.

Grading

Out of 10 points:

1 - Compiles + Coding Standards
Your program compiles successfully (no errors). Your code follows Java coding standards.
1 - Constants
Maze details are defined and used through constants such that changing only the value initially assigned to these constants changes the program accordingly.
1 - Random placement
The player and exit are both randomly placed (though not at the same place). The exit is always along a wall.
2 - Prints room
Prints the room, displaying the current location of the player and of the exit.
2.5 - Player movement
The player can move their character around the maze-room board using at least one of the three input schemes outlines above. Game ends when the player reaches the exit.
1.5 - Constrained to board
The player cannot move their character off the board.
1 - User Interface
The UI includes sufficient instructions on how to play the game; errors such as incorrect input are detected and clearly reported.
+3 (max) - Extra Credit
For clearly documented extra features. Only 3 points maximum, and only if all basic requirements have been met.

FAQs

Problem set from lab

Section 001: Practice for students (individually or in small groups) in lab. Use one or more loops to do the following:

  1. Print all the positive integers less than 100 that are divisible by 3. (1B: Once you have that done, then print those number 5 per line.)

Given a user-specified width:

  1. Print a line of "width" asterisks (*). That is, if the user enters 3, print 3 asterisks.
  2. Print a square box of *s with the given width (and height) That is, if the user enters 3, print:
     ***
     ***
     ***
    
  3. Print a box with a hole in its center. That is, print a box as in #3 but with the single * in the center (or one close to the center for boxes with an even width) replaced with a space.
  4. Print an equilateral triangle of *s with the given width. The right angle should be on the lower left. So, given a width of 4:
     *
     **
     ***
     ****
    
  5. Print an equilateral triangle of *s with the given width. The right angle should be on the lower right. So, given a width of 4:
        *
       **
      ***
     ****
    
Challenge:

Sample solutions: DivisibleByThree.java (#1 and 1B) and PrintingBoxes.java (#2 to 6).

Hints for an OOP design
Other than the use of constants, there are no requirements on how you structure your code for this assignment. If you want to, you can just write everything in a single main method. While this is simple in terms of methods, you'll probably find that main then gets very complex with so many nested structures--ifs, nested loops, try/catch blocks, etc.

It can be clearer if you try to break the problem into methods. You can use static methods if you want (though we have not yet covered these in lab); or you can use an object-oriented approach. Here is one such design you might want to try that uses a separate Maze class:

Instance Variables and Constants: You'll want a playerX and playerY to track the player's current location, and at least a mazeExitY. You should also define the necessary final constants required above. (These constants can be static class variables if you want.)

Maze() - Initialize your instance variables with appropriate random values in the constructor.

print() - Print the maze to the screen (or else return a String containing the maze for the UsernameA11 to print).

movePlayer(?) - The type and number of the parameters are up to you. I'd recommend just one parameter (a char or int, perhaps?) that indicates the direction the player should be moved. This method should then update the playerX and playerY values to move the character to a new location. This method could then return a boolean--true if the player actually moved or false if not (such as when such movement would take the player off the board or into a wall).

isPlayerAtExit() - Returns true or false if the player is on the same square as the exit.

The rest of your code would go in the main method of UsernameA11. There, you could do something like this:

  Maze maze = new Maze();
  //code: print game instructions
  while (!maze.isPlayerAtExit()) {
    maze.print();
    //code: prompt user to enter a move.  Based on that input:
    boolean moved = maze.movePlayer(???);
    if (!moved) {
      //player hit a wall, so print an error message
    }
  }

Note that error checking of the user input is not shown here. Still, this approach will make your main method much simpler. However, the trade-off is that you have to deal with the complexity of breaking your code into methods.

How did that double for loop go?
Assuming you defined the necessary constants for MAZE_WIDTH, MAZE_HEIGHT, and FLOOR, this will print out a 2D grid that represents the room:
for (int y = 0; y < MAZE_HEIGHT; y++) {
  for (int x = 0; x < MAZE_WIDTH; x++) {
    System.out.print(FLOOR);
  }
  System.out.println();
}
This just prints the FLOOR for every character. So you will need to add some conditionals (if/else-if/else statements) to determine which character to print out at each square. The loop counters x and y tell you what location you are currently printing. You will need to check to see if that (x, y) location is the same as the player's location (playerX, playerY). If so, print PLAYER. If it's the location of the exit, print EXIT instead. Otherwise, just print FLOOR.
I still don't understand how to print the player and the exit in the maze.
As explained in the previous answer, you need to use some conditionals to determine which single character to print at each location in the room. Here is the basic structure, though you will need to replaces the /* comments */ with the appropriate code:
for (int y = 0; y < MAZE_HEIGHT; y++) {
  for (int x = 0; x < MAZE_WIDTH; x++) {
    if (/* x and y  == playerX and playerY */) {
      /* print the player */
    }else if (/* x and y  == exitX and exitY */) {
      /* print the exit */
    }else {
      System.out.print(FLOOR);
    }
  }
  System.out.println();
}
Note how only one character will be printed each time through the loop(s)--either the player, the exit, or the floor.
Do I have to repeat that map-printing code again after I move the player?
No, you only need the code that prints the map in one place. (This can either be the nested for loops discussed above or a call to a print method that contains such loops.) You will use a loop to return to the map printing code after you have moved the player. In other words, your code should have this basic structure:
while (player has not reached the exit) {

  /*
   * Print the map (using two nested for loops, as shown above
   */

  /*
   * Ask user to move.
   * (Make sure they enter a valid move.
   *  Don't move the player off the map.)
   * Update playerX/playerY to new player location.
   */
}

/*
 * (Loop ended because player finally reached the exit.)
 * Print congratulatory message.
 */
You might move some of this code out of the while loop and into a couple methods, but it's not required. You will still need this basic loop structure somewhere.
How to I prevent the player from starting on the exit?
You are required not to place the player on the exit at the start of the game (since then the game would immediately end). However, you should strive for the greatest potential randomness possible.

One option is to use a loop: keep generating the player's initial x and y position until it is not the same as the exit. This is the safest way, and will work for any sized maze larger than 1x1. (Note that just an if here is not sufficient--though very unlikely, you could still randomly generate the same player starting position a second time in a row.)

Another option is to just move either the player or the exit by one square to prevent the collision. Since the exit has to stay on the right well (unless you are doing the EC) and could currently be at the top or bottom row, it's easier to just move the player left by one square if she starts on the exit. This works well enough and is always safe on any maze with a width >= 2.

Finally, you could just never place the player in the same column as the exit. This is not as good as one of the two above techniques in terms of greatest randomness, but it's better than nothing and is the simplest option.

How do I prevent the player from leaving the maze?
You should be moving the player by updating your playerX and playerY variables. Therefore, before you do that, just test whether that would in fact take the player outside of the maze. For example, if the player is currently on the far left side of the maze (playerX == 0) and tries to move left/west, that would mean playerX would go to -1. That would be outside the maze, so, instead of decrementing playerX, print an error message instead.

An alternative technique is just to move the player and then test if the player is now outside the maze. If so, print the error message and move the player back. However, this requires you to test which way the player just moved so you can correctly move her back again, so this technique is not any shorter than testing before you move.