Java 1.4 Game Programming (Wordware Game and Graphics Library)

Now that we have a basic understanding of streams, let's see how we can get user input from the console window. In this example we will be using System.in, which is an instance of the InputStream class and is normally connected to the keyboard. However, to make use of the InputStream, we need to create a BufferedReader so that we can read lines of input from the console window. Let's now look at a complete example to see how we can get console input from the user.

Code Listing 6-1: Console input

import java.io.*; public class ConsoleInputExample { public ConsoleInputExample() { BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in)); String inputStr = new String(); System.out.println("Type something and press enter..."); System.out.println("Type \"quit\" to exit"); try { while(!(inputStr=keyboard.readLine()).equalsIgnoreCase ("quit")) { System.out.println("You typed in: "+inputStr); } } catch(IOException e) { System.out.println(e); } } public static void main(String args[]) { ConsoleInputExample mainApp = new ConsoleInputExample(); } }

When we execute the example console application and then type in some sample data (each followed by the Enter key), we can see that it will look like the following figure.

Figure 6-1: Console input example

Our console application basically takes a line of input from the user, stores it in a string, and finally outputs it back to the console. The only special case is if the user types in "quit," in which case the application terminates.

Let's now look at the code and see how it works. First we include the java.io.* package so we have access to all the input classes (such as the BufferedReader).

Next we create a BufferedReader object, which we create by first creating an InputStreamReader, passing in our System.in stream as a parameter to its constructor. This can be seen in the following code:

BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));

Once we have our BufferedReader object, which we have called keyboard, we then create a string called inputStr so we can store the data we read in.

Next, we create a while loop and then attempt to read a line of input from our keyboard object. Basically, this will wait until the user presses the Enter key, and then it will get all the characters that were pressed before the Enter key and store them in the inputStr string. Notice also how we check if the string is equal to the string quit (ignoring case). This is simply to allow the code to quit out of the program.

while(!(inputStr=keyboard.readLine()).equalsIgnoreCase("quit"))

This code might look a little strange, but it is quite straightforward. We first call the readLine method of the keyboard object, which blocks (waits) until the user enters the data and presses the Enter key. Once this is done, the entered string is assigned to inputStr, which is then the value used to compare with the string literal quit for testing if the while loop terminates or not.

If the user did not enter quit, simply output what the user entered.

All we are left to do now is catch the possible I/O exception and finish the while loop. Catching the exception can be seen here:

catch(IOException e) { System.out.println(e); }

The IOException exception is the base of all exceptions relating to problems with input and output. You will encounter this exception a lot, notably later on in the book when we utilize streams for networking in Chapter 17, "Introduction to Networking."

Console Game Example—Tic-Tac-Toe

Now that we know how to get input from the user via the console window, we are all set to produce some kind of logical game with user interaction and game logic, albeit from the perils of doom that is the ASCII console window. Let's look at a very simple console game called tic-tac-toe. In case you don't know how to play tic-tac-toe, the idea of the game is to get a line of three O's or X's (depending on which player you are) on a board consisting of 3x3 squares. Let's first look at the complete source code for this example, and then we will take a look at how the code works.

Code Listing 6-2: Tic-tac-toe example

import java.io.*; public class TicTacToe { public void start() { char inputChar = ' '; String inputLine = null; initializeGame(); drawGameState(); BufferedReader reader = new BufferedReader( new InputStreamReader(System.in)); do { try { // wait for input from player inputLine = reader.readLine(); if(inputLine.length() == 1) inputChar = inputLine.charAt(0); else inputChar = (char)-1; } catch(IOException e) { System.out.println(e); } // handle the input handleInput(inputChar); // print output drawGameState(); } while(programRunning); } public void initializeGame() { // clear the board for(int i=0; i<BOARD_SIZE; i++) for(int j=0; j<BOARD_SIZE; j++) board[i][j] = ' '; // initialize move variables moveCounter = 0; turn = 0; moveType = COLUMN; System.out.println("Start playing Tic-Tac-Toe"); } public boolean checkForWin() { char symbol = SYMBOL[turn]; // check vertical win Label1: for(int i=0; i<BOARD_SIZE; i++) { for(int j=0; j<BOARD_SIZE; j++) if(board[i][j] != symbol) continue Label1; // if reached, winning line found return true; } // check horizontal win Label2: for(int j=0; j<BOARD_SIZE; j++) { for(int i=0; i<BOARD_SIZE; i++) if(board[i][j] != symbol) continue Label2; // if reached, winning line found return true; } // check back slash diagonal win for(int i=0; i<BOARD_SIZE; i++) if(board[i][i] != symbol) break; else if(i == BOARD_SIZE-1) return true; // winning line found // check forward slash diagonal win for(int i=0; i<BOARD_SIZE; i++) if(board[i][BOARD_SIZE - i - 1] != symbol) break; else if(i == BOARD_SIZE-1) return true; // winning line found // if reach here then no win found return false; } public void makeMove() { // is board position available if(board[moveCoords[COLUMN]][moveCoords[ROW]] == ' ') { // make move board[moveCoords[COLUMN]][moveCoords[ROW]] = SYMBOL[turn]; moveCounter++; if(checkForWin() == true) { // player has won drawBoard(); System.out.println("Congratulations, " + SYMBOL[turn] + "'s win the game"); // start new game initializeGame(); } else if(moveCounter == (BOARD_SIZE * BOARD_SIZE)) { // no win and board is full, so the game has been drawn System.out.println("Game drawn"); drawBoard(); // start new game initializeGame(); } else // else continue playing game, change turn turn = (turn + 1) % 2; } else System.out.println("Illegal move, board position already filled"); } public void handleInput(char key) { switch(key) { case 'q': case 'Q': // quit the game programRunning = false; break; case '0': case '1': case '2': // move coordinate entered moveCoords[moveType] = Integer.valueOf(String.valueOf (key)).intValue(); if(moveType == ROW) { makeMove(); moveType = COLUMN; } else // moveType is curently COLUMN coordinate moveType = ROW; break; default: // invalid input to game System.out.println("ERROR: Invalid entry, this input has no function"); moveType = COLUMN; } } public void drawGameState() { if(moveType == COLUMN) { drawBoard(); System.out.println("Type 'q' to quit program"); System.out.println(SYMBOL[turn] + "'s move..."); System.out.print("Enter column number ->> "); } else System.out.print("Enter row number ->> "); } public void drawBoard() { System.out.println(); // new line System.out.print(" "); for(int i=0; i<BOARD_SIZE; i++) System.out.print(" " + i); System.out.println(); // new line for(int j=0; j<BOARD_SIZE; j++) { System.out.print(j + " |"); for(int i=0; i<BOARD_SIZE; i++) System.out.print(board[i][j] + "|"); System.out.println(); // new line } System.out.println(); // new line } public static void main(String args[]) { TicTacToe game = new TicTacToe(); game.start(); } private final int BOARD_SIZE = 3; private final int COLUMN = 0; private final int ROW = 1; private final char SYMBOL[] = {'O', 'X'}; private boolean programRunning = true; private char board[][] = new char[BOARD_SIZE][BOARD_SIZE]; private int moveCoords[] = new int[2]; private int moveCounter; private int turn; private int moveType; }

When we run the console example, we can see that it draws the board and then awaits input from the user. This can be seen here:

Figure 6-2: The tic-tac-toe game

When a move is given, it then redraws the board showing the move that was made or it displays an error message if the move was invalid. Between moves, we work out the consequences of the move (i.e., who's won or if there is a tie). Let's look at the code that we used to create this simple (yet fun) game.

The first method that is called is of course the main method, so let's take a look at this method first:

public static void main(String args[]) { TicTacToe game = new TicTacToe(); game.start(); }

All we do in the main method is create an instance of our TicTacToe class and then call its start method.

The start method is used to first set up the game and then goes into a do/while loop, known generally as the game loop, until the user requests that the game terminates. Let's look at the code we have used in the initialization part of the start method now.

First we create a variable called inputChar to hold the character that was entered by the player, which we will extract from the line the player inputs that will be held in a variable called inputLine. We create these two variables with the following two lines of code:

char inputChar = ' '; String inputLine = null;

Next, we call the initializeGame method, which looks like the following:

public void initializeGame() { // clear the board for(int i=0; i<BOARD_SIZE; i++) for(int j=0; j<BOARD_SIZE; j++) board[i][j] = ' '; // initialize move variables moveCounter = 0; turn = 0; moveType = COLUMN; System.out.println("Start playing Tic-Tac-Toe"); }

In this method, we first create an empty board array to store the positions where the players will place their Os or Xs, and then we initialize three variables that we will use to control the actual flow of the game, which will be discussed later.

After we have initialized the game, we call the drawGameState method to display the board and prompt the user for input. Note though that this function does not actually request any input from the user. It simply displays the board data and shows text on the screen asking the player for the input. This method can be seen in the following block of code:

public void drawGameState() { if(moveType == COLUMN) { drawBoard(); System.out.println("Type 'q' to quit program"); System.out.println(SYMBOL[turn] + "'s move..."); System.out.print("Enter column number ->> "); } else System.out.print("Enter row number ->> "); }

Notice that we only draw the board if the player is entering the column value to make a move, as this is the first of two entries per move, so we only need to refresh the board at the beginning of the two required inputs: the column and the row moves.

After the game state has been written out to the console, we then initialize a BufferedReader, as we did in the previous console input example, so we are able to take input in from the user for retrieving the given column or row value. We create our BufferedReader object with the following line of code.

BufferedReader reader = new BufferedReader( new InputStreamReader(System.in));

Next, start the main game loop, which will execute until our program is terminated. Once in this loop, we attempt to get input from the user by reading a line from the console. This is accomplished with the following code segment.

do { try { // wait for input from player inputLine = reader.readLine();

Once we have the input string, extract the first character from it with the following code segment:

if(inputLine.length() == 1) inputChar = inputLine.charAt(0); else inputChar = (char)-1;

Note that we also check if the text input from the user was of length 1, hence one single character, as our input mechanism works with single characters. If it was not 1, set the input character to –1, which will represent an invalid character later on when we analyze this input value. Otherwise, assign the character to our inputChar variable.

Now that we have the character, pass it to the handleInput method with the following line of code:

handleInput(inputChar);

Let's look at how this method deals with the input now. Switch the character and check if it was either q, 0, 1, or 2 (or some other character). If the letter q was entered, we know that the user wishes to terminate the game, so we set the programRunning variable to false, which is used as the condition for termination for the main game loop.

If the user entered 0, 1, or 2, first get the integer value of the character that was entered with the following line of code:

moveCoords[moveType] = Integer.valueOf(String.valueOf (key)).intValue();

As you can see, we set the given moveCoords array element to the integer value of the character entered, using the moveType variable to determine whether this number relates to the column or row (0 and 1, respectively, in the moveCoords array). As we get the column from the user first and then the row, we need to make a check to see if the input type is the second coordinate (i.e., the row), meaning that the complete move has been entered. If it has, we can then make the move by calling the aptly named method makeMove and then set the moveType back to column. We will look at the all-important makeMove method in a moment. If the move entered was the first input (column), simply set the next input type to row. Note that if the input was not valid (i.e., it did not match any of the cases), the switch will jump to the default statement, which prints an error message to the screen and resets the moveType variable to be the column to restart the move. This can be seen in the following code:

default: // invalid input to game System.out.println("ERROR: Invalid entry, this input has no function"); moveType = COLUMN;

Now that we know how the input is handled, let's look at what happens when we call the makeMove method.

First check if the board at the select position is empty. This is done with the following if statement:

if(board[moveCoords[COLUMN]][moveCoords[ROW]] == ' ') {

If the space on the board is free, set the position of the board to the player's symbol and increment the moveCounter, which records the number of moves that have been made in the current game. Here are the two lines of code we use to do this:

board[moveCoords[COLUMN]][moveCoords[ROW]] = SYMBOL[turn]; moveCounter++;

Next check if the player has won the game by calling our checkForWin method. We will look at this method in a moment. If the player has won, draw the board by calling the drawBoard method to show the victorious board and then display a line of text informing the player that he/she has won. After this, call the initializeGame method to set up the application for the next game. This can be seen in the following block of code:

if(checkForWin() == true) { // player has won drawBoard(); System.out.println("Congratulations, " + SYMBOL[turn] + "'s win the game"); // start new game initializeGame(); }

If the player has not won, we need to check if there are still moves available on the board to check for a tie. To check for this, simply compare the area of the board (i.e., the width multiplied by the height) with the current moveCounter. If the moveCounter is equal to the total area of the board, the game is drawn and we need to tell the players and once again initialize a new game. This can be seen in the following block of code:

else if(moveCounter == (BOARD_SIZE * BOARD_SIZE)) { // no win and board is full, so the game has been drawn System.out.println("Game drawn"); drawBoard(); // start new game initializeGame(); }

So if the player has not won and the game is not drawn, we simply need to change the current turn to the other player, which is accomplished with the following code:

else // else continue playing game, change turn turn = (turn + 1) % 2;

The code (turn + 1) % 2 simply turns an odd value into "0" and an even value into "1", hence we swap turns either from 0 to 1 or vice versa.

Finally, we need to add an else statement for our initial if statement, which will catch if the player has tried to place their counter on a board position that is already taken. This is done with the following two lines of code:

else System.out.println("Illegal move, board position already filled");

Now the important part of this game is the checkForWin method. This method simply needs to check the conditions for a win in the game of tic-tac-toe. The possible wins consist of three horizontal checks, three vertical checks, and two diagonal checks for three matching symbols in a line. This is quite straightforward. For example, look at the code to check for a vertical win:

// check vertical win Label1: for(int i=0; i<BOARD_SIZE; i++) { for(int j=0; j<BOARD_SIZE; j++) if(board[i][j] != symbol) continue Label1; // if reached, winning line found return true; }

This code simply iterates across the three column coordinates, each time checking if all three of the row elements are equal to the given symbol. If one symbol does not match the move symbol in the nested loop, we continue the first loop using a continue label statement (these were discussed in detail in Chapter 2). The other checks in this method work in the same way.

Категории