Seen an example of how a larger program is assembled.
NOTE:
This module and the next two will take us a bit out of
our comfort zone into building realistic applications.
We will see (but not write) much larger volumes of code.
Beyond being larger in code size, the applications will
have many parts, and it will be challenging to understand how
they come together.
Your module folder will have many java files, all pertinent
to the application.
2.0 Audio:
Step 1: Build some useful methods
As a first step, you will write some methods that will
be used repeatedly in the game.
The class
MyGameArrayTool.java
will feature these methods:
public static int[] shiftedArray (int N)
{
// This needs to return an array with 1,2,...,N*N as the values.
// INSERT YOUR CODE HERE
}
public static void shuffle (int[] numbers)
{
// This needs to shuffle the numbers.
// INSERT YOUR CODE HERE
}
public static void putNumbersOnBoard (int[][] board, int[] numbers)
{
// This needs put the numbers in the second array (M of them)
// on the N*N board. M=N*N. However, it needs to put them
// in Cartesian order going left to right starting at the bottom.
// INSERT YOUR CODE HERE
}
public static boolean isPrime (int n)
{
// Is n a prime number?
// INSERT YOUR CODE HERE.
}
public static void printBoard (int[][] board)
{
// INSERT YOUR CODE HERE for a Cartesian print
}
A few more details:
The method
shiftedArray(int N)
needs to return an array of size N*N containing the
numbers 1,2,...,N*N, and NOT 0,...N*N-1.
The input to the
shuffle(int[] numbers)
method is an array of integers. Your goal is to
randomly rearrange these numbers. One way to do that is to
walk down the array and at each spot in the array, find a
random other element to swap that with. This is not the
only way to do a shuffle.
For convenience (see inside the downloaded code) we'll
provide a method that returns a random integer within a given
range.
The
putNumbersOnBoard(int[][] board, int[] numbers)
method takes a 2D array that's initially all zeroes, and assigns
values (numbers) from the second array. But this is to be done
in a particular order by treating the board as a Cartesian grid.
These are the details:
Suppose the board size is n×n. There will be m=n×n
numbers in the second array to spread all over the first (2D) array.
They are to be assigned in sequence so that the first n
numbers are assigned to
board[0][0], board[1][0], ... board[n-1][0].
Then, the next n numbers from the second array are to be
assigned to
board[0][1], board[1][1],
... board[n-1][1], and so on.
The method
isPrime(int n)
needs to determine whether n is a prime number. You can
do this any which way. Go back to Module 0 in Unit-0 to
review prime numbers and see some example code.
Lastly, the method
print(int[][] board)
needs to print the 2D array in Cartesian order, as we learned
in Module 0.
2.1 Exercise:
Download
MyGameArrayTool.java
and write your code in that file. Compile and run the tests
to make sure your implementations are working.
2.2 Audio:
Step 2: Learn about the game by playing
Let's start with a brief description:
The game is called Prime.
⇒
It will involve our favorite
kind of number: prime numbers.
It's a 2-person board game with each person taking turns.
The only choice made at each turn is whether to move
"down" or "horizontally" by one cell, or to stay in place.
Player A can only move right when moving horizontally.
Player B can only move left when moving horizontally.
A player cannot move out of bounds, nor can move into a cell
occupied by another player. Either such move results
in immediately losing the game.
After the move, the player's score is updated based on
the cell that the player chose to move to.
A cell with a prime number adds a multiple of that number to the player's score.
For example if the multiplier is 10,
landing on a prime cell with the number 7, results in
70 being added to your score.
Otherwise the cell's number is subtracted from the player's
score.
For example,
landing on a cell with the number 42, results in
42 being subtracted from your score.
Obviously, one should try to get to the prime cells by
going through as few of the other cells as possible.
Also because one must move down or horizontally, there
are limited number of moves (2N-1) in a game.
And that's it as far as the rules are concerned.
Your second programming goal (which will be explained below in more detail)
will be to write code for a strategy for each of two players:
You will write code for PlayerA in
PlayerA.java
in which your code decides how to move.
You will write code for PlayerB in
PlayerB.java
in which your code decides how to move.
The game can be played in one of 7 types of competitions:
Game type 1: your PlayerA plays your PlayerB (software vs. software).
Game type 2: RandomA vs Manual. Here you can manually
play (as a B-player) against a strategy that moves randomly (as an A-player).
Game type 3: You can manually (as B) play your code strategy
(playing as A).
Game type 4: Your playerA plays RandomB. A minimal
expectation for your strategy is to consistently beat RandomB.
Game type 5: RandomA vs RandomB. This is just to test that
it's all working after you've written your GameArrayTool code.
Or to while away an afternoon watching random players against each other.
Game type 6: Your playerA against OptimusPrimeB. The latter
is a more careful strategy aimed at winning. We would be
impressed if your playerA consistently beats OP.
Game type 7: You can manually (as A) play against OptimusPrimeB.
Note: because the numbers are randomly distributed, a player can
get lucky. However, over many games, a good strategy can beat
a poor one.
2.3 Exercise:
Download
primegame.zip.
However, do not unzip by clicking on the zip file.
Instead, un-zip the file at the commandline in Terminal.
To do that, enter the directory where the zip file is,
and type
unzip primegame.zip.
This will make a directory called
primegamezip.
Enter that directory and compile the file
PrimeGame.java.
Then, at the command line of terminal type
java PrimeGame 5.
This will start off a game of type 5 (random vs random).
Some observations and next steps:
Notice that, as the game is executing, the initial board
configuration and each player's subsequent moves
are being printed to the terminal.
The game uses nothing more complex than DrawTool, which
is itself a very basic drawing program.
If you run the game repeatedly, you will notice that the
numbers 1,2,...,100 are not randomly spread around the grid.
This is deliberate: the numbers are fixed to allow predictability
in testing out your code. You can then make a small adjustment
to make a random board each time (we'll do this later).
So far we've not used your code in MyGameArrayTool.
That will come later.
Notice that as we start to develop more complex programs
and applications, multiple Java files will be involved. Examine the
number of Java files in the directory by typing
ls *.java.
2.4 Audio:
Step 3: Write your strategies
Things to keep in mind:
You will write a player-A strategy in the
move()
method in the file
PlayerA.java.
At each step of the game, when it's A's turn,
the game calls the
move()
method in your
PlayerA.java
to determine what your code decides as its next move.
Observe that the return type is
char.
All you have to do in the method is to decide whether
to return 'd', 'h', or 's'.
Examine the file
RandomA.java.
This is a silly strategy but it will help get you started
by showing you, for example, how to avoid breaking the rules.
Clearly, your strategy will make use of where you happen
to be on the board each time it's your move. This information
is given to you as parameters to the
move()
method.
Suggestions for proceeding:
First copy over the code from
RandomA.java
and compile. Then run the game with gametype=3 so that
you manually play this strategy.
Write your own strategy by replacing the code
and continue playing manually.
When your strategy is sufficiently refined, use
gametype=4 to play against RandomB and confirm that
you consistently win.
Use random boards to further refine:
Open the file
PrimeGame.java
and use control-w to search for
shuffle
in the file.
You will see that the line containing that has been
commented out.
Un-comment the line by removing the comment slashes.
This will have the effect of randomizing the board each time.
Once you've done your PlayerA, work on a different idea
for PlayerB.
2.5 Exercise:
Implement your PlayerA and PlayerB strategies.
Play them against each other, against Random, and against
OptimusPrime. Find other students to play against.
How did your approach fare?
2.6 Audio:
Step 4: Use your arraytool
While developing your strategy, the toolbox used
was
GameArrayTool.
The source code for this was not included so that you
can develop your version.
To use your version:
Use control-w to search for
GameArrayTool
in the file
PrimeGame.java.
Replace each occurrence of
GameArrayTool
with
MyGameArrayTool
so that your methods are called.
2.7 Exercise:
Use your
MyGameArrayTool
for the game.
Assuming your implementations worked, the game should
function as before.
Congratulations! You've helped build a game.
Step 5: Examine a complex strategy
2.8 Exercise:
Open the file
OptimusPrimeB.java
in your editor and cursorily examine the code.
About this strategy:
It should be quite incomprehensible.
In a typical CS curriculum, an Algorithms course is often
the 4th or 5th course, long after programming. And towards
the end of such a course is when a technique called
dynamic programming is taught. It is difficult
to understand and apply.
It just so happened that this algorithmic approach is well-suited
to this game.
We do NOT expect beginning students to understand how it works.
About game strategies:
Writing strategies for games is a subfield in itself,
and has a long history.
There are games like tic-tac-toe for which optimal
strategies can be computed, but others like chess in which
brute computing power must be combined with strategies to win.
Step 6: Examine the game code
2.9 Exercise:
Open the file
PrimeGame.java
in your editor and carefully examine the code.
Let's walk through the design:
First, let's take a look at the key global variables:
static int N = 10; // Grid size N×N
static int[][] board = new int [N][N]; // board[i][j] == a number
static int[][] points = new int [N][N]; // the points won/lost at a cell
static int primePointMultiplier = 10; // 10*prime# for positive points
// Current locations of the two players:
static int playerAX=0, playerAY=N-1;
static int playerBX=N-1, playerBY=N-1;
// Current scores:
static int playerApoints=0, playerBpoints=0;
Thus the main idea of the whole program is:
Do the initialization:
Make the board, draw the board, initalize locations
while (! over) {
move player A
move player B
}
print points and game result
This is essentially what's in the method
executeGame()
(the initialization is done in methods called from
main()):
static void executeGame ()
{
boolean over = false;
boolean canMoveA = true, canMoveB = true;
int numSteps = 0;
while (! over) {
numSteps++;
canMoveA = checkCanMoveA (); // Need to see if A can move.
if (canMoveA) { // A can move.
boolean isLegal = moveA (); // A's move worked.
if (! isLegal) {
playerApoints = -1; // If not, A loses.
break; // End the game right away.
}
}
// ... similar code for player B ...
}
Player A is "stuck" if it's on the lowest row and rightmost
cell:
static boolean checkCanMoveA ()
{
// You can't move A if it's on the lowest level and x >= N.
return ( ! ((playerAY == 0) && (playerAX >= N-1)) );
}
What remains is to actually arrange for either a manual move
or to call the appropriate program for a move.
Here's some of the code in
moveA():
static boolean moveA ()
{
char c = ' '; // Default (to catch errors).
int[][] b = copyArray (board); // Make a copy to prevent tampering.
if (gameType == 1) { // PlayerA vs Player B.
sleep (sleepTime);
c = PlayerA.move (b, playerAX, playerAY, playerBX, playerBY);
}
else if (gameType == 2) {
// ... etc .. for all the game types ...
}
// Evaluate legality of move and then actually change
// playerAX, playerAY, and adjust points.
// ...
}
The code for player B is nearly identical.
There are also some supporting methods like copying the board.
Why copy the board? We send a copy to each player in case a player's
strategy code inadvertently writes over the board.
Finally, let's take a look at the initialization code:
static void makeBoard ()
{
// First make an array of numbers 1 .. M=N*N
int[] numbers = GameArrayTool.shiftedArray (N);
// Next, place numbers on the board.
GameArrayTool.putNumbersOnBoard (board, numbers);
System.out.println ("Initial board:");
GameArrayTool.printBoard (board);
// Now do points.
for (int y=0; y<N; y++) {
for (int x=0; x<N; x++) {
if (GameArrayTool.isPrime(board[x][y])) {
points[x][y] = board[x][y] * primePointMultiplier;
}
else {
points[x][y] = -board[x][y];
}
}
}
}
But you already know this code.
Ready to build your own game?
There are simple games and puzzles without much by way of graphics
or storytelling.
⇒
These are easy for a single person to develop.
A full-fledged video game, on the other hand, needs a large
team:
Graphic artists to design and create imagery.
Game engine software developers to create the logic of the game.
Software developers to handle communication, servers and
such for multi-player online games.
Software developers for interfacing with game hardware.
Employment in the game industry is a popular choice for
students but it's highly competitive.
To secure a future in the game industry, it's probably
helpful to do a few things:
Take courses in fine arts so you learn how to use tools
for digital arts and animation.
Get a Master's degree in game development and computer graphics,
which will certainly cover the above and typically position
you for both the game and animation industries.
Build some games because that's how you open doors for interviews.
Seek an internship in a game development company.
(And let's not forget ... complete the GatewayToCS program.)
Meta
Let's step back and point out a few things:
Not only was the application more complicated than anything
we've seen before, but also the folder had many files, not
all of which were essential to understanding.
Even running the application was complicated by the
variety of options (the 7 types of playing configurations).
When faced with such complexity, one can take a top-down
or bottom-up approach.
We recommend doing a bit of both simultaneously:
Start top-down by understanding the game and its rules.
Then understand what happens in
main().
Get a feel for the different parts by skimming through.
Trace through the code once.
Then understand methods at the high-level: what goes in, and
what comes out.
You do NOT need to understand everything to be able to get the job done.
One of the hardest tasks in software development is to
understand someone else's code and why it was designed the way you
see it.
As we've said before: the more you do this, the better you
will get at it.