Conflict resolution

It's not a real game until we someone wins. In this lesson we will learn how to determine if conflict has occurred. When this happens we'll paint a special image to indicate that battle has engaged and the units can no longer be moved. Then we will use a very simple algorithm for resolving the conflict at the end of the turn. We'll expand on this algorithm latter when things get more interesting with multiple units. Requires a browser that supports applets
This is meant to be a replacement when we do things (javascript menus) that might get hidden behind the applet.

Determining conflict

Collision detection

The first thing we want to do is to paint the battle image when the two units come into conflict (when they both try to occupy the same territory). First we will need to setup and load the battle image. This is left as an exercise for the reader. The image can be downloaded here. Since we only have two units, it is fairly simple to test if there's conflict. We simply need to check if the two units occupy the same territory.
        if (Stakeout.unit1Row == Stakeout.unit2Row 
        && Stakeout.unit1Column == Stakeout.unit2Column) {
            canvas.drawImage (battleImage, UpperLeft (Stakeout.unit1Column), UpperLeft (Stakeout.unit1Row), this);
        }
Two units occupy the same territory when both their rows and their columns are the same.

No backing out

Once a unit has entered into battle we don't want him to be able to get out of it. He's enganged and has to fight to the death. To accomplish this we will have to add some if statements to our keyPressed function to disallow movement if there is conflict.
        if (Stakeout.unit1Row == Stakeout.unit2Row 
        && Stakeout.unit1Column == Stakeout.unit2Column) {
                return;
        }

Duplicate code

You'll notice in the last two sections we repeated the same "if" statement twice. We will use these lines a lot throughout our program. It's a relatively simple statement, but it is still easy to mess up. We can avoid stupid mistakes by factoring this code out into a function. It will reduce the amount of code, make it easier to understand, and easier to maintain. We'll place this function in the Stakeout class so that we will have access to it from both the StakeoutAdapter and the StakeoutComponent.
    public static boolean IsConflict () {
        if (unit1Row == unit2Row 
        && unit1Column == unit2Column) {
            return true;
        }
        return false;
    }
Now we can replace our previous "if" statments with "if (Stakeout.IsConflict ())"

Resolving conflict

More variables

From battle, death results. When a unit is dead, he can no longer move. He can no longer engage in battle. In fact we shouldn't even paint him on the screen. Now that our units can be dead or alive, we'll need another variable to track this.
    public static String team1Name = "";
    public static int unit1Row = 1;
    public static int unit1Column = 1;
    public static Color unit1Color = Color.RED;
    public static int unit1Moves = 2;
public static boolean unit1Alive = true;
When a unit dies, we simply set this new variable to false. We'll have to add a significan't amount of code to take that into effect, but first lets kill some units.

Rolling the dice

At the end of the turn, if there's conflict, we will want to kill one of the units. To determine which unit to kill we will roll some virtual dice. Our virtual die is not like a normal die. It returns a decimal number between 0 and 1 with uniform distribution. Uniform distribution means that it is equally likely to return any number between 0 and 1 as any other number. So if we kill the attacker if the number is less than 0.6, then the attacker will die 60 percent of the time. This will give the defender a slight higher chances of survival. We will perform this conflict resolution at the end of the turn (when the 's' key is pressed). And we'll get to use our IsConflict function again.
        if (keyId == KeyEvent.VK_S) {
if (Stakeout.IsConflict ()) { // There's conflict: odds are 60 / 40 in favor of the defender double blackSpot = Math.random (); if (blackSpot < 0.6) { // The attacker dies if (Stakeout.teamsTurn == 1) { // unit1 is the attacker Stakeout.unit1Alive = false; } else if (Stakeout.teamsTurn == 2) { // unit2 is the attacker Stakeout.unit2Alive = false; } } else { // The defender dies if (Stakeout.teamsTurn == 1) { // unit2 is the defender Stakeout.unit2Alive = false; } else if (Stakeout.teamsTurn == 2) { // unit1 is the defender Stakeout.unit1Alive = false; } } }
if (Stakeout.teamsTurn == 1) { // Switches to team 2's turn Stakeout.teamsTurn = 2; Stakeout.unit2Moves = 2; System.out.println ("It is now " + Stakeout.team2Name + "'s turn"); } else if (Stakeout.teamsTurn == 2) { // Switches to team 1's turn Stakeout.teamsTurn = 1; Stakeout.unit1Moves = 2; System.out.println ("It is now " + Stakeout.team1Name + "'s turn"); } else { assert (false); } }
If it is team 1's turn, we know that he must be the attacker because only he could have moved to engage in battle. The disturbing thing is that if you run this code, you will find that the battle never ends. Our dead units keep on fighting. If you don't believe me, put print statements next to the lines that kill the units and output the value of unitXAlive. You will find that it is false, and yet the battle still continues. Needless to say, we don't want to allow dead units to continue fighting. Actually, there's lots of places where you have to take death into account...

Taking death into account

IsConflict

To end the battle when one of the units dies, we have to change our conflict detection function so that it doesn't report a battle even if the units are on the same terrain if one of them is already dead. Fortunately since we factored this code into a separate function, we'll only have to change it in one place.
    public static boolean IsConflict () {
        if (
unit1Alive && unit2Alive
&& unit1Row == unit2Row && unit1Column == unit2Column) { return true; } return false; }
Both units have to be alive for a battle to take place. If we hadn't factored this code into a function, we'd have to change this code in multiple places. If we forgot any of those places, we'd have a bug in our program. Now the first thing you notice when you try to run this code, is that the battle doesn't end, or at least it seems that way. But actually this is just an allusion (a euphamism for bug) if you try to move the units after the turn you will find that they are now free to move and can no longer engage in battle. The reason the battle image didn't disappear after we changed the turn is because we never repainted the screen. So we should add a repaint() call at the end of our if statement that deals with the 's' key being pressed.

Moving

Unless you're incoporating some zombie concept into your version of the game, dead units should not be allowed to move. So unit movement is another place where we will need to take "death" into account. Since we already have a situation where we don't allow units to move (when they don't have any moves left), fixing this problem will be fairly easy. All we have to do is enhance the conditional that checks the number of moves to also check whether the unit is alive.
            if (Stakeout.teamsTurn == 1) {
                newRow = Stakeout.unit1Row;
                newColumn = Stakeout.unit1Column;
                if (
! Stakeout.unit1Alive ||
Stakeout.unit1Moves < 1) { return; } }
Now the unit that is dead is no longer able to move.

Painting

Not being able to move the unit isn't a very good way of telling that he died. We should either paint a different image indicating that he is dead or we should remove him from the board altogether. I don't want to mess up our clean board with bloody corpses lying around everywhere, so let's go with the latter option. To prevent a unit from being painted we just enclose the paint call in an if statement which tests that it is alive.
if (Stakeout.unit1Alive) {
// Paint the first unit canvas.drawImage (unit1Image, UpperLeft (Stakeout.unit1Column), UpperLeft (Stakeout.unit1Row), this);
}
if (Stakeout.unit2Alive) {
// Paint the second unit canvas.drawImage (unit2Image, UpperLeft (Stakeout.unit2Column), UpperLeft (Stakeout.unit2Row), this);
}
That was easy enough.

Game over

The last thing we need to do is to quit the game when all the (one) units for a team are dead. We'll do this at the end of the end-of-turn "if" statement.
            if (! Stakeout.unit1Alive) {
                // Team 2 wins
                System.out.println (Stakeout.team2Name + " is the winner!");
                System.exit (0);
            }
            if (! Stakeout.unit2Alive) {
                // Team 1 wins
                System.out.println (Stakeout.team1Name + " is the winner!");
                System.exit (0);
            }
Actually none of the previous code that we added to accomodate unit death was really necessary because we only have one unit per team. As soon as a unit dies, the game will be over and we will exit, but this is good groundwork for the next lesson where we won't be able to make this assumption.

Follow-up exercise

  • Create your own image to use for signifying a battle
  • Create your own image for a dead unit and paint it on the screen instead of painting nothing
  • Come up with your own algorithm for determining who wins and test that it works as you would expect


Rate this lesson: *****