Assignment: Project #3
Due Date: Thursday 10/18, 11:00PM
Open/Closed policy: CLOSED

Medieval Soldiers


Objective

This project will require you to write at least one typical Java class (BasicSoldier ), which defines the characteristics and behavior of a soldier. We hope you will also be motivated to think creatively about some ideas related to the field of Artificial Intelligence as you try to develop (just for fun) some additional classes representing soldiers who fight differently and perhaps more successfully. If you choose to write additional soldier classes, you may name them anything you wish, just be sure to submit at least one class called BasicSoldier for grading.

Overview

We have developed a framework in which two armies (red and blue) will engage in battle. Below is a screenshot of a typical battle on a small battlefield:



Each army can be selected from various classes of soldiers that are available. There are three built-in soldier classes: Easy,Medium, and Smart. You will write at least one of your own, called BasicSoldier, and you may choose to write others just for fun if you'd like to experiment with different strategies. You can have classes you write fight against each other, or against any of the built-in classes. Do you think you can design soldier classes that are smart enough to beat the built-in AI's? You could win a prize if you develop a class of fighters who can routinely defeat in-built Smart soldiers (see the contest details at the bottom of the page.)

BattleField

The battles will take place in a BattleField, which is a rectangular grid. Each location in the grid could contain a red soldier, a blue soldier, an obstacle, or nothing at all. Each soldier will have access to a grid variable, which is a reference to the BattleField he is fighting in. When the soldier is deciding on his current action, he can query the battlefield for information about his surroundings. A typical query to the battlefield would be something like "tell me what is in row 2, column 3". The battlefield will respond with a code representing either "red soldier", "blue soldier", "obstacle", "nothing", or "you asked me about a location that is out of bounds".

More details about how a soldier must interact with the battlefield will follow when we describe the instance methods that you will be implementing. Also, please see the Javadoc for the BattleField class.

BasicSoldier Class

We have started you with an empty shell for this class. Look in the package called fighters and you will find it. When complete, your BasicSoldier class must contain the following public members.

final static constants

The four constants below represent the relative fighting attributes of all soldiers of this class. These attributes dictate what happens when a soldier of this class attacks someone or is attacked by someone. The intricacies of how these constants are interpreted internally will remain our secret -- you will have to experiment to see what combinations work well. The numerical values listed below are just an example -- You may assign these four attributes any values you want (between 1 and 97), but they must add up to 100 total . These variables are static because all soldiers in the class share the same attributes. They are final because they never change while the program runs.

  • public final static int INITIAL_HEALTH = 10;
    Amount of damage the soldier can sustain when he first comes to life, or when he has fully recovered from injuries.
  • public final static int ARMOR = 20;
    Defense level (How hard is he to hit?)
  • public final static int STRENGTH = 30;
    Amount of damage done when successfully striking an enemy
  • public final static int SKILL = 40;
    Likelihood of striking an enemy during an attack


The additional constants below will be used by your methods when they need to specify a direction. Do not modify the numerical values!
  • public final static int UP = 0;
  • public final static int RIGHT = 1;
  • public final static int DOWN = 2;
  • public final static int LEFT = 3;
  • public final static int UP_AND_RIGHT = 4;
  • public final static int DOWN_AND_RIGHT = 5;
  • public final static int DOWN_AND_LEFT = 6;
  • public final static int UP_AND_LEFT = 7;
  • public final static int NEUTRAL = -1;
You may add final static constants of your own, but you are forbidden from adding static variables that are not final.



Instance Variables

Each soldier has his own copy of these variables. All of these variables will be initialized in the constructor for the class. You may add other instance variables, if you choose to.

  • public final BattleField grid;
    The battlefield where this soldier fights. The soldier will need access to this battlefield object so that he can ask it about his surroundings.
  • public int row, col;
    The current location of this soldier within the battlefield. (0, 0) represents the upper-left corner, as usual. You will modify these values when your soldier decides to move.
  • public int health;
    The current health-level of this soldier. Your constructor will initialize this value to be equal to the constant INITIAL_HEALTH. The framework will decrease the soldier's health level automatically if he is damaged by another fighter. If damaged, the soldier will gradually heal, and so the framework will gradually increase his health level until it reaches the value of INITIAL_HEALTH. IMPORTANT: The health variable will be initialized in the constructor you write, but must never be modified by your code in any other place. The framework will take care of adjusting the soldier's health during battle. If the soldier's health reaches 0, the framework will remove him from battle.
  • public final int team;
    Indicates which team this soldier is fighting on. It will be either BattleField.RED_TEAM or BattleField.BLUE_TEAM. This will be important later when, for example, this soldier discovers that there is a blue soldier next to him -- this soldier will need to know which team he, himself, is on so that he will know whether that blue soldier is a friend or a foe!



Constructor

You must implement a constructor with the prototype below. The constructor will be called by the framework to initialize your soldiers at the beginning of a battle. The constructor will assign the four parameters to the corresponding instance variables. It must also set the health variable equal to the value INITIAL_HEALTH . (You may have your constructor do additional things as well, if you wish.)

public BasicSoldier(BattleField gridIn, int teamIn, int rowIn, int colIn)

Instance Methods

You'll find that while implementing these methods, the current soldier will need to interact with the battlefield. In particular, the soldier will need to ask the battlefield about his surroundings using the battlefield's get method. Please see the Javadoc for the BattleField class for more information about using the battlefield. You may add instance methods of your own, if you choose to.

  • public boolean canMove()
    This method returns true if it is possible for this soldier to move, false otherwise. To be able to move, one of the adjacent locations (up/down/left/right) must be empty. There is no diagonal movement. This method does not actually move the soldier, it just returns true or false.

  • public int numberOfEnemiesRemaining()
    This method returns the number of enemies who are on the battlefield. (Enemies are soldiers who are not on this soldier's team!)

  • public int getDistance(int destinationRow, int destinationCol)
    This method calculates the number of moves it would take this soldier to reach the destination specified by the parameters. Hint: Remember that a soldier does not move diagonally -- only up, down, left, and right. So the return value must represent the number of up/down/left/right moves required for the current soldier to reach this location along the shortest possible route (we are ignoring obstacles or other soldiers that might be in the way).

  • public int getDirection(int destinationRow, int destinationCol)
    This method determines which way this soldier would have to go in order to arrive at the destination specified by the parameters. The return value will be either NEUTRAL or one of the symbolic constants representing directions (UP, DOWN , etc.) determined using the following rules:

    • DOWN if the destination is below the soldier (in the same column)
    • UP if the destination is above the soldier (in the same column)
    • LEFT if the destination is to the left of the soldier (in the same row)
    • RIGHT if the destination is to the right of the soldier (in the same row)
    • UP_AND_RIGHT if the destination is both above and to the right of the soldier
    • DOWN_AND_RIGHT if the destination is both below and to the right of the soldier
    • DOWN_AND_LEFT if the destination is both below and to the left of the soldier
    • UP_AND_LEFT if the destination is both above and to the left of the soldier
    • NEUTRAL if the destination is the same as the soldier's current location

  • public int getDirectionOfNearestFriend()
    This method will return the direction of the nearest teammate. Distances should be calculated by calling your getDistance method. In cases of a tie (more than one teammate is equally close), you may return the direction of any of the nearest teammates. The direction to be returned should be determined by calling the getDirection method. Note: If there are no friends available then the method must return NEUTRAL.

  • public int countNearbyFriends(int radius)
    This method will count the number of teammates whose distance (measured in "moves") from the current soldier is less than or equal to the specified radius. Please note that the radius is measuring the number of "moves away" rather than a geometrical distance. For example: If the radius is 6, then the method is looking for the number of friends who could be reached in 6 moves or fewer. Obviously you should not count the current soldier, himself, as a friend.

  • public int getDirectionOfNearestEnemy(int radius)
    This method will return the direction of the nearest enemy whose distance from the current soldier (measured in "moves") is less than or equal to the given radius. Please note that the radius is measuring the number of "moves away" rather than a geometrical distance. For example: If the radius is 6, then the method is looking for the nearest enemy who could be reached in 6 moves or fewer. In cases of a tie (more than one enemy within the radius is equally close), you may return the direction of any of the nearest enemies who have tied. The direction to be returned should be determined by calling the getDirection method. Note: If there are no enemies within the given radius then the method must return NEUTRAL.

  • public void performMyTurn()
    This method is the heart of your soldier's "AI" (Artificial Intelligence). It will be called by the framework each time it is the soldier's turn to perform an action. He has three choices: move, attack, or do nothing. In order to make his decision, the soldier will want to gather information about his surroundings by making calls to the get method of the battlefield. Your soldier can decide on his actions in any way you choose, but he must adhere to the following rules:
    • Attack is possible only if there is an enemy soldier directly above, below, to the left, or to the right. You cannot attack an obstacle, a teammate, an empty location, or a location that is out-of-bounds.
    • Moving is possible only if there is an empty location directly above, below, to the left, or to the right. You cannot move where there is an obstacle, a soldier, or into an out-of-bounds location.
    • In his turn, the soldier can do at most ONE action (either one move or one attack) -- not one of each.
    • He can only do nothing if it is impossible to move or attack.
    Once the soldier's decision is made (be sure not to violate the rules above), your code must implement his choice as follows:
    • If the soldier decides to do nothing, this method should return without any effect.
    • If the soldier decides to move, you must adjust his row and col to the new coordinates and then return from the method. He may only move into an empty location that is directly above, below, to the left, or to the right. There is no diagonal movement.
    • If the soldier decides to attack, you must call the attack method of the battlefield, passing as arguments the row and column of the enemy that you are attacking. You may only attack an enemy that is either directly above, below, to the left, or to the right. There is no diagonal attacking. You may not attack a location that does not contain an enemy -- in particular, you may not attack one of your own teammates!



Running the Game

To simulate a battle, run the main method in the class called Driver (it's in the package called GUI). By default, this will initiate a battle between two built-in soldier classes, Easy and Medium.

You can modify the game parameters at the top of the GUI and then press "Begin New Game" to start a completely new battle. If you would like to replay the same battle that is currently being fought, just press the "Replay This Battle" button. There is a slider at the bottom which allows you to control the speed of the simulation.

The Red Team and Blue Team boxes can be used to choose different classes for the battle. The built-in classes are Easy, Medium, and Smart. If you'd like to see your own soldiers fighting, then just enter the name of one of your classes (e.g. BasicSoldier) into one or both of these boxes. (You can have both teams use the same class if you want -- that works fine).
If you would like to change the default start-up parameters for the game (for example, you would like it to always start off with a 20 by 20 battlefield where the blue team is BasicSoldier and the red team is Smart) you may modify the Driver class in the obvious way to suit your needs.



Additional Fighting Classes

You are only required to implement one class, BasicSoldier. We hope that some of you will enjoy this project and will have fun creating some additional fighting classes in a quest to produce the ultimate soldiers! See the contest rules, below, for more motivation. If you choose to write some additional classes, you may name them anything you wish. Be sure to put your fighting classes in the package called fighters so that the framework can find them. Any additional fighting classes you write must contain the following elements, which were described above for the BasicSoldier:

  • final static constants INITIAL_HEALTH, STRENGTH, ARMOR, and SKILL (these must add up to 100).
  • the same instance variables as were in the BasicSoldier (plus any others that you want to use)
  • the same kind of constructor as was used in BasicSoldier
  • the performMyTurn method, which must adhere to the same contract that was described for BasicSoldier. The other instance methods we listed for BasicSoldier are not required for other soldier classes you might write.



Random Number Generation

(This section is optional reading, and is only important if you want the "Replay This Battle" button in the GUI to function correctly for battles involving classes you have written.) It is likely that you will want your soldiers to sometimes behave randomly. Rather than using one of the usual random number generators, please use the one we invented. It is called Random131. If you use this generator, the very same "random" numbers will be reproduced after you press "Replay This Battle", so that the exact same battle can be studied repeatedly.

The Random131 class has a static method called getRandomInteger that you can call whenever you need a random integer in a range that you specify. For example, if you would like to obtain a random number from 0 to 9, then you could use this:

int randomValue = Random131.getRandomInteger(10);
// randomValue will be assigned a random integer from 0 to 9



Academic Honesty -- No Collaboration Before the Due Date!

After the project due date has passed, it might be fun to have your soldiers do battle against a classmate's soldiers to see who wins! Or after the due date, you may choose to work with other students as a team to try to win the contest. But... be sure that all work you do before the due date (including the 24-hour late period) is done individually, without collaboration! You may not share code before the due date under any circumstances.



The Contest

So... you think your soldiers are pretty smart, huh? If you manage to develop a class that will beat the built-in Smart AI most of the time on a big battlefield with lots of soldiers, then send an email to Nayeem. Note: In order to qualify, your AI must not be appreciably slower than mine. If I find that your AI is better than my Smart AI, you will win a prize. What kind of prize? I don't know yet -- maybe a T-shirt. Or a free lunch. You tell me. We will certainly post the name(s) of anyone who wins the contest on the class webpages so that you can bask in your glory!

Submission

Submit your project from Eclipse by right-clicking the project folder and selecting "submit". You may submit as many times as you want -- we we only grade the submission that scores the highest on the automated tests. After you have submitted your project, you should visit the submit server. There you can obtain limited feedback about how well your project is performing. The number of times you can run our tests on your project (before the due date) is limited. The earlier you begin working on the project, the more opportunities you will have to see how your project performs on our tests before the due date!

Grading

90% of your grade on this project will be from the automated tests on the submit server (both public tests and release tests). 10% of your grade on the project will be based on using correct "style" for Java programming.