Buy

Start another battle - how about 3 CloakShape fighters against 4 RZ-1 A-wing interceptors. Behind the scenes: each ship has a strength. The battle() function uses this as the ship's health, and as they battle each other, that health gets lower and lower until one hits zero.

We need to add a new feature: after the battle: display the final health of the battling ships. One will be zero or negative, but how much health did the other have left?

In battle(), those "ship health" variables are not returned in BattleResult. So we don't have access to this information. We could add it to BattleResult, but I want to do something more interesting.

After fighting a battle, let's update the strength of each ship with their new health: like $ship1->setStrength($ship1Health) and the same for $ship2:

66 lines lib/BattleManager.php
... lines 1 - 9
public function battle(Ship $ship1, $ship1Quantity, Ship $ship2, $ship2Quantity)
{
... lines 12 - 16
while ($ship1Health > 0 && $ship2Health > 0) {
... lines 18 - 31
// now battle them normally
$ship1Health = $ship1Health - ($ship2->getWeaponPower() * $ship2Quantity);
$ship2Health = $ship2Health - ($ship1->getWeaponPower() * $ship1Quantity);
}
// update the strengths on the ships, so we can show this
$ship1->setStrength($ship1Health);
$ship2->setStrength($ship2Health);
... lines 40 - 56
}
... lines 58 - 66

After all, in real life - if a $ship is almost defeated, it's probably pretty broken - so it's $strength should reflect that.

Check this out by dumping $ship1->getStrength() and $ship2->getStrength() and die. Refresh! We have -14 and 116, 130 and 0 and so on.

Ok, working nicely, and that's simple. Actually, we just did something really important. Until now, this function has only read data from our ships. But now, we've changed those objects. In other words, in battle.php, we start with two Ship objects and pass them into battle():

106 lines battle.php
... lines 1 - 25
$ship1 = $ships[$ship1Name];
$ship2 = $ships[$ship2Name];
$battleManager = new BattleManager();
$battleResult = $battleManager->battle($ship1, $ship1Quantity, $ship2, $ship2Quantity);
... lines 32 - 106

Once that finishes running, those same two objects are different now: their data has changed.

This is totally different than how arrays work: if $ship1 were an array, and the battle() function changed one of its keys internally, that would have no effect here: $ship1 would still be the same array with the same original values.

Objects are passed by reference: it means that there is only one $ship1 object in existence and when we pass it to a function, we're passing that one object. But when you pass an array or a string to a function, you're actually passing a copy of the original value. If that value changes inside the function, it has no affect on the original variable.

Some of you may be familiar with adding an & symbol before an argument: this does the same thing: it makes that argument pass by reference. For objects, that's not needed, because this is always true.

The takeaway is that if you change an object, you're changing that object everywhere. To prove this, take our $ship1 and $ship2 - which are not returned by the battle() function - and add a new section that prints the finished strength. Add a dl element to make them a little pretty:

106 lines battle.php
... lines 1 - 33
<html>
... lines 35 - 53
<body>
<div class="container">
... lines 56 - 67
<div class="result-box center-block">
... lines 69 - 88
<h3>Remaining Strength</h3>
<dl class="dl-horizontal">
... lines 91 - 94
</dl>
</div>
... lines 97 - 102
</div>
</body>
</html>

First, echo $ship1->getName() and then $ship1->getStrength():

106 lines battle.php
... lines 1 - 33
<html>
... lines 35 - 53
<body>
<div class="container">
... lines 56 - 67
<div class="result-box center-block">
... lines 69 - 88
<h3>Remaining Strength</h3>
<dl class="dl-horizontal">
<dt><?php echo $ship1->getName(); ?></dt>
<dd><?php echo $ship1->getStrength(); ?></dd>
... lines 93 - 94
</dl>
</div>
... lines 97 - 102
</div>
</body>
</html>

Do the same thing for $ship2:

106 lines battle.php
... lines 1 - 33
<html>
... lines 35 - 53
<body>
<div class="container">
... lines 56 - 67
<div class="result-box center-block">
... lines 69 - 88
<h3>Remaining Strength</h3>
<dl class="dl-horizontal">
<dt><?php echo $ship1->getName(); ?></dt>
<dd><?php echo $ship1->getStrength(); ?></dd>
<dt><?php echo $ship2->getName(); ?></dt>
<dd><?php echo $ship2->getStrength(); ?></dd>
</dl>
</div>
... lines 97 - 102
</div>
</body>
</html>

We're missing auto-complete because we have some bad PHPDoc somewhere. We'll fix that in a bit.

Time to try it! Since objects are passed by reference, we should see the new, modified strength values - not the originals. Absolutely perfect.

Now let's get really wild and start fetching our ships from a database.

Leave a comment!

  • 2016-06-20 Victor Bocharsky

    Yes, you're totally right! It was fixed and published already.

    Thank you, Nailee!

  • 2016-06-17 Nailee

    The last code listing is wrong - it's something about database init, while it should be (as I thing) a fragment of battle.php

  • 2016-05-04 weaverryan

    Hi there!

    Ah, this is a bug with our app! Nice find! But, it's an *awesome* bug - because it's super relevant to *this* chapter - about "references". Here's what's happening:

    1) In battle.php, we get an array of ships by saying $ships = $shipLoader->getShips(). This is an array of Ship objects
    2) In battle.php, we use the ship name to get each Ship object from the $ships array. These are the lines:


    $ship1 = $ships[$ship1Name];
    $ship2 = $ships[$ship2Name];

    And *that* is actually the bug! When we "fetch" the Ship object from the array, that object is passed to us "by reference" - it doesn't make a new copy of the Ship object. In other words, if $ship1Name and $ship2Name are the same, then $ship1 and $ship2 literally point to the exact same *one* Ship object in memory. When we pass these two variables in BattleManager(), weird things happen - because we're calling setStrength() on both ships, which is really changing the same *one* Ship objects two times. In essence, since $ship2->setStrength($ship2Health) is called last, whatever $ship2Health is set to will be the final value for both ships (since there is really only one Ship). This is why sometimes you'll see the final healths as both 46, but other times as both 0. But in all cases, they're the same.

    The fix for this - which we may do later, as it *is* a bit more realistic - would be to have ShipLoader return us unique Ship objects - e.g. we add a method called $shipLoader->findShipByName($name), and it always returns a unique Ship object. This is actually more realistic: if we're having 2 ships fight each other, then they should always be *different* Ship objects. In the app right now, if you select the same type, we're kind of having one ship battle itself :).

    GREAT find and question!

  • 2016-05-03 That one guy

    I noticed that if you run this with the following parameters:
    ~5 jedi ships against 5 jedi ships (or any match up that both sides are equal) ~
    whenever one of them is using the jedi power, the health of the other does not seem to get set to zero.... see below:
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    The Matchup:
    1 CloakShape Fighter VS. 1 CloakShape Fighter

    Winner:
    CloakShape Fighter

    The CloakShape Fighter used its Jedi Powers for a stunning victory!

    Ship Health
    CloakShape Fighter 46 CloakShape Fighter 46
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    one of them should have ended with a zero health.

    I am new to PHP and cannot seem to find why this is happening.