Lesson 3 - frc2052/2023-KnightKrawlerJavaTraining GitHub Wiki
In this lesson we'll begin abstracting our code in an object orientated manner. At the moment all our code is located within the Main class, but if we were to continue adding onto this game our Main class would eventually become thousands of lines long with most of it being repeated code. We'll begin by refactoring (rewriting and rearranging) our code to improve it's functionality.
First, create a new enum called Direction with the values NORTH
, SOUTH
, EAST
, and WEST
.
Next, create a new class called Player. This class will contain all the logic and functionality of our player like the their name, position in the dungeon, and Scanner input. Start by creating the class level variables we will need for this class. Logically think through what the types of each variable need to be. For example input
has the type of Scanner
. The variables needed will be input
, name
, desiredDirection
, currentRoom
(Hint: this is an index in an array), moved
with the initial value true
(more on this variable later), isDead
, and hasWon
both with the initial values of false
.
Next we'll create our Player class constructor which gets run whenever our class is instantiated. This constructor will be called from our Main class after we've created our Scanner input
and received the player's name, so this constructor will take in a Scanner input
as well as a String name
. Next we'll assign these method level variables to our class level variables as follows.
public Player(Scanner input, String name) { this.input = input; this.name = name; }
Notice: the this.
in front of the variable assignments in the constructor above. This is because our constructor takes in a variable named input
, but our class also has a separate variable named input
, so which variable is our constructor referencing in the assignment? The answer is if we were to call input.nextLine();
within that constructor we would be referencing the constructor parameter variable, bit if we called this.input.nextLine();
we are referencing the class level variable and not the constructor level variable. So by setting this.input
equal to input
we are simply copying the constructor variable into the class level variable. When this same principle is applied to a method instead of a constructor ex.
Don't include this in your code
private int value; public void setValue(int value) { this.value = value; }
The above code is what's referred to as a "setter". Notice that although value
is private we are still able to set its value through this method. We can reverse this logic as well to create a "getter" method.
Don't include this in your code
private int value; public int getValue() { return value; }
This time the method returns the private variable allowing classes outside this class to have access to the number stored inside value
. This is known as a best practice because although we could just make value public this would be a bad practice because it can be harder to debug problems and allows filtering values.
Create a public method called processUserInput
with a return type of void and a private method called getUserInput
with a return type of String. The processUserInput
method will be responsible to getting the player's input and storing it in the input
class level variable. The logic for receiving and processing input will look like this. Take a moment to look through what's going on here.
public String getUserInput() { // Take the users input, convert it to lower case, and remove any blank spaces return input.nextLine().toLowerCase().trim(); } public void processUserInput() { moved = false; // Ask the user for their input System.out.print("What's your move " + name + "? "); while (true) { // Look through our list of valid inputs and check if one of them equals our input value switch (getUserInput()) { case "n": desiredDirection = Direction.NORTH; return; case "s": desiredDirection = Direction.SOUTH; return; case "e": desiredDirection = Direction.EAST; return; case "w": desiredDirection = Direction.WEST; return; case "h": //printHelpMenu(); break; default: //invalidInput(); break; } } }
You may have noticed that some of our cases use
break;
while others usereturn;
. While break only breaks out of the switch and the while loop continues, return will exit the entire method which will break out case, while loop, and exit the method.
Our class is almost complete! Next we need to think through what getters and setters we'll need. For example, the variable desiredDirection
needs to be accessible from outside our Player class in order to move our player, so we'll create a getter from desiredDirection. However, we won't need a setter for this variable because the only time it should ever be changed is when we process the player's input. Below are the getters and setters needed for desiredDirection
and currentRoom
.
public Direction getDesiredDirection() { return desiredDirection; } public int getCurrentRoom() { return currentRoom; } public void setCurrentRoom(int room) { if (currentRoom != room) { moved = true; currentRoom = room; } }
Finally create a getter for moved
and getters and setters for both isDead
and hasWon
, and just like that our Player class is done! (for now)
Notice that within the processInput
method there are two commented out method calls. Implement those methods and have them contain a simple print statement with instructions for the player.
Compare your code with the code in the lesson3 folder (ignore anything referencing Action
or desiredAction
(more on this later)).
Don't forget to commit and push your code.