Startup sequence revisited
Feedback
In the follow-up exercise to the previous lesson you were suppossed to prompt the user for various information that we want to use to customize our game. In case you skipped that exercise, here's the code that we need to add to do that.
public class Stakeout {
public static void main (String args[]) throws IOException {
System.out.println ("Welcome to Stakeout!");
// Prompt for the unit color
boolean invalidColor = true;
while (invalidColor) {
System.out.println ("What color do you want your unit to be? [red or green]");
String input = CmdInput.GetInput ();
if (input.contentEquals ("red")) {
System.out.println ("You have chosen to be the Red Rebels!");
invalidColor = false;
}
else if (input.contentEquals ("green")) {
System.out.println ("You have chosen to be the Green Goblins!");
invalidColor = false;
}
else {
System.out.println ("You entered an invalid color!");
}
}
// Prompt for the initial unit row
boolean invalidRow = true;
while (invalidRow) {
System.out.println ("What row do you want to start on? [1-10]");
String input = CmdInput.GetInput ();
int row = Integer.valueOf (input).intValue ();
if (row >= 1 && row <= 10) {
System.out.println ("Starting row is " + row);
invalidRow = false;
}
else {
System.out.println ("You entered an invalid row!");
}
}
// Prompt for the initial unit column
boolean invalidColumn = true;
while (invalidColumn) {
System.out.println ("What column do you want to start on? [1-10]");
String input = CmdInput.GetInput ();
int column = Integer.valueOf (input).intValue ();
if (column >= 1 && column <= 10) {
System.out.println ("Starting column is " + column);
invalidColumn = false;
}
else {
System.out.println ("You entered an invalid column!");
}
}
// Setup the window
System.out.println ("Creating the Stakeout window...");
JFrame window = new JFrame ();
StakeoutWindowAdapter windowAdapter = new StakeoutWindowAdapter ();
window.addWindowListener (windowAdapter);
window.setTitle ("Stakeout Window Title");
window.setSize (340, 370);
StakeoutComponent component = new StakeoutComponent ();
window.add (component);
window.setVisible (true);
// Prompt to move the unit
while (true) {
System.out.println ("What direction do you want to move your unit? [u, d, l, or r]");
String input = CmdInput.GetInput ();
if (input.contentEquals ("u")) {
System.out.println ("You have chosen to move up!");
}
else if (input.contentEquals ("d")) {
System.out.println ("You have chosen to move down!");
}
else if (input.contentEquals ("l")) {
System.out.println ("You have chosen to move left!");
}
else if (input.contentEquals ("r")) {
System.out.println ("You have chosen to move right!");
}
else {
System.out.println ("You entered an invalid direction!");
}
}
}
}
Comments
You will notice some lines preceeded by "//" that don't really look like code. That's because they aren't. Anything that comes after a "//" on a line in the code is called a comment and is ignored by the compiler. Also, anything that's enclosed between "/*" and "*/" is a comment. This second type of comment can span multiple lines. The purpose of comments is to make it clearer for us, the programmers, to understand what's going on without having to read all the code. Our program is getting big enough now that it is very useful to have this extra information.
Strings to ints
You'll notice something a little different about the code that allows the user to select an initial position. We are converting the input (a String) to an int. This isn't strictly necessary, but it saves a lot of typing. We could have written the code like this:
boolean invalidRow = true;
while (invalidRow) {
System.out.println ("What row do you want to start on? [1-10]");
String input = CmdInput.GetInput ();
if (input == "1") {
System.out.println ("Starting row is 1");
invalidRow = false;
}
else if (input == "2") {
System.out.println ("Starting row is 2");
invalidRow = false;
}
else if (input == "3") {
System.out.println ("Starting row is 3");
invalidRow = false;
}
// and so on for all 10 posibilities
else {
System.out.println ("You entered an invalid row!");
}
}
Instead we can compress all of this into one if statement by converting to an int because with an int we can check for ranges. You might ask why we can't check for ranges with Strings. Strings are just a bunch of characters. They don't mean anything to the computer. A number on the other hand represents a quantity and quantities can be compared. And you definitely can't compare different types. For example, the comparison ("2" > 1) is ludicrous to a computer becuase you're comparing a String and an int. It's the same as saying ("two" > 1). Ask yourself is the sequence of characters t-w-o (not the number it represents) bigger than the number one? That question doesn't make any sense. It's like comparing apples and oranges. And it doesn't make any sense to the computer either. So the compiler will produce an error when it sees something like this.
int row = Integer.valueOf (input).intValue ();
Now the mechanics of converting a string of characters to a number isn't trivial. What we are actually doing in the above code is calling a function of the
Integer class called valueOf which takes a String as a parameter and returns an instance of the Integer class that represents that value. It's like converting "2" to the number 2. But we don't want an Integer, we want an int. So we call another function on this instance called "intValue" which returns the int representation of this number. We could have written it out more explicitly as:
Integer rowInteger = Integer.valueOf (input);
int row = rowInteger.intValue ();
Tracking globally
Feedback
Tracking
In our code above we've done nothing to store the users selection so that we can use it later. We could create a function level variable (as we've done in the past) to hold these values, but it would only be accessible from within our "main" function. But the place where we need to access these values is in our StakeoutComponent's paint function. One way to solve this problem is to store this information in "public static" variables inside our Stakeout class. "public static" variables are ones that are available anywhere, from any other class or function without the need for an instance. They are class-level variables that are universally accessible. We will declare them just like the variables in the past except that instead of putting them inside the main function we'll put them just under the Stakeout class declaration and we'll prefix them with the "public static" keywords.
import java.io.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
class StakeoutWindowAdapter extends WindowAdapter {
public void windowClosing (WindowEvent e) {
System.out.println ("Game over!");
System.exit (0);
}
}
class StakeoutComponent extends Component {
public void StakeoutComponent () {
setSize (300, 300);
setVisible (true);
}
public void paint (Graphics canvas) {
canvas.setColor (Color.BLACK);
canvas.fillRect (0, 0, 300, 300);
canvas.setColor (Stakeout.unitColor);
canvas.fillRect ((Stakeout.unitColumn - 1) * 30, (Stakeout.unitRow - 1) * 30, 30, 30);
}
}
public class Stakeout {
public static int unitRow = 0;
public static int unitColumn = 0;
public static Color unitColor = Color.BLACK;
public static void main (String args[]) throws IOException {
System.out.println ("Welcome to Stakeout!");
// Prompt for the unit color
boolean invalidColor = true;
while (invalidColor) {
System.out.println ("What color do you want your unit to be? [red or green]");
String input = CmdInput.GetInput ();
if (input.contentEquals ("red")) {
System.out.println ("You have chosen to be the Red Rebels!");
unitColor = Color.RED;
invalidColor = false;
}
else if (input.contentEquals ("green")) {
System.out.println ("You have chosen to be the Green Goblins!");
unitColor = Color.GREEN;
invalidColor = false;
}
else {
System.out.println ("You entered an invalid color!");
}
}
// Prompt for the initial unit row
boolean invalidRow = true;
while (invalidRow) {
System.out.println ("What row do you want to start on? [1-10]");
String input = CmdInput.GetInput ();
int startRow = Integer.valueOf (input).intValue ();
if (startRow >= 1 && startRow <= 10) {
System.out.println ("Starting row is " + startRow);
unitRow = startRow;
invalidRow = false;
}
else {
System.out.println ("You entered an invalid row!");
}
}
// Prompt for the initial unit column
boolean invalidColumn = true;
while (invalidColumn) {
System.out.println ("What column do you want to start on? [1-10]");
String input = CmdInput.GetInput ();
int startColumn = Integer.valueOf (input).intValue ();
if (startColumn >= 1 && startColumn <= 10) {
System.out.println ("Starting column is " + startColumn);
unitColumn = startColumn;
invalidColumn = false;
}
else {
System.out.println ("You entered an invalid column!");
}
}
// Setup the window
System.out.println ("Creating the Stakeout window...");
JFrame window = new JFrame ();
StakeoutWindowAdapter windowAdapter = new StakeoutWindowAdapter ();
window.addWindowListener (windowAdapter);
window.setTitle ("Stakeout Window Title");
window.setSize (340, 370);
StakeoutComponent component = new StakeoutComponent ();
window.add (component);
window.setVisible (true);
// Prompt to move the unit
while (true) {
System.out.println ("What direction do you want to move your unit? [u, d, l, or r]");
String input = CmdInput.GetInput ();
if (input.contentEquals ("u")) {
System.out.println ("You have chosen to move up!");
unitRow = unitRow - 1;
}
else if (input.contentEquals ("d")) {
System.out.println ("You have chosen to move down!");
unitRow = unitRow + 1;
}
else if (input.contentEquals ("l")) {
System.out.println ("You have chosen to move left!");
unitColumn = unitColumn - 1;
}
else if (input.contentEquals ("r")) {
System.out.println ("You have chosen to move right!");
unitColumn = unitColumn + 1;
}
else {
System.out.println ("You entered an invalid direction!");
}
component.repaint ();
}
}
}
Here I am setting the initial color to be black so that we can be sure that our code to set the initial color in the startup sequence (to either green or red) is working. Likewise we set the starting position for the unit to be off the screen at the position (0, 0). From within the Stakeout class we use these variables just like we did in the past with function-scoped variables. But referencing these variables outside of the class is a little more complicated. You have to prefix the variable reference with the name of the class to tell Java where it comes from (see the paint function in StakeoutComponent).
Changing coordinates
In this code we are no longer using pixels to determine where the unit should be. Instead we are using rows and columns because this is what the user will expect. Regardless of how we present it to the user, the fillRect function that we call from our paint function only accepts pixels, so we have to convert from rows and columns to pixel coordinates. To do this we first have to subtract 1 from the row or column number and then multiply by 30 (the number of pixels in one section of our grid). It may not be immediately clear why you have to subtract 1. Try a few examples to make it more concrete. The subtraction operation has to be enclosed in parethesis because Java follows the "order of operations" rule from Mathematics whereby multiplication takes precedence over subtraction unless enclosed in parenthesis. After we update the unit location we have to repaint our component.
repaint is a function of the Component class which basically just calls our paint function with the appropriate Graphics argument. We don't have a Graphics object so we can't call paint directly.