views:

141

answers:

2

Hello,

I need to ask someone for a big favor. What you see in the code below are my calculations for train movement for a train game I am writing. There is a problem with the calculations, but I just can't seem to find it. The problem seems to be located in this code:

      if($trainRow['direction'] == '-') {
        $trackUnitCount = $routeData['track_unit_count'] - $trackUnitCount;
      }

I will explain the exact problem in more depth later, but for now, I'd like to explain the train movement logic (the game may be found at http://apps.facebook.com/rails%5Facross%5Feurope):

The game board consists of a map of Europe containing cities. Cities are connected by routes. Routes have a starting city and an ending city. Routes are connected by track which is divided into track units. Each route has a given number of track units. A train may travel a route in either a positive (+) or negative (-) direction. If the city the train started at is the route's starting city, then the train is traveling in a positive direction. If the train's starting city is the route's ending city, then the direction is negative.

A train starts in a city and the user chooses a destination city to travel to. The train has a given number of track units it may travel in a single turn. If the train does not have enough track units to reach the destination city in a single turn, then the train's status is set to 'ENROUTE' and the train's current track unit is set to the track unit the train stopped on. The train's remaining track units is zero (since the train used all it's track units). If the train has more than enough track units to reach the destination, then the train stops at the destination and the train's status is set to 'ARRIVED'. The train's remaining track units is set to the trains starting track units minus the number of track units used to reach it's destination.

The player's turn will not end until his train's remaining track units reach zero, so once the player has finished his business at the current city, he will choose another city destination and the train will move it's remaining track units toward that city. If the train has more than enough track units remaining to reach that city, then it stops at the city and the track units traveled are subtracted from it's remaining track units. This goes on until the remaining track units reach zero.

The problem is that this code sets $trackUnitCount to zero, which then in turn sets the train's current track unit to zero, which indicates that the train has arrived at its destination city (which is incorrect). This results in the train hopping from one city to another without running out of track units remaining which should end the player's turn (before he arrived at the destination city)

If you can help me out with this, you are truly a cyber-saint :)

  public function moveTrain($destCityId) {
    require_once 'Train.php';
    $trainModel = new Train();

    require_once 'Route.php';
    $routeModel = new Route();

    $userNamespace = new Zend_Session_Namespace('User');
    $gamePlayerId = $userNamespace->gamePlayerId;

    $trainData = $trainModel->getTrain($gamePlayerId);

    if($destCityId == $trainData['dest_city_id']) {
      $originCityId = $trainData['origin_city_id'];
    } else {
      $originCityId = $trainData['dest_city_id'];
    }
    $routeId = $routeModel->getRouteIdByCityIds($originCityId, $destCityId);
    $trainRow = array();
    if($routeId !== null) {
      $routeData = $routeModel->getRouteByCityIds($originCityId, $destCityId);
      $trainRow['direction'] = $routeModel->getRouteTravelDirection($originCityId, $destCityId); //+
      //$routeData['track_unit_count'] are the total number of track units in this route
      //$trainData['track_unit'] is the track unit the train is currently stopped on
      $trackUnitCount = $routeData['track_unit_count'] - $trainData['track_unit']; //6-3=3
      //$trainData['track_units_remaining'] are the number of track units the train has left to move
      $trackUnitsRemaining = $trainData['track_units_remaining'] - $trackUnitCount; //5-3=2
      if($trackUnitsRemaining > 0) {
        $trackUnitsTraveled = $trackUnitCount; //2
      } else {
        $trackUnitsTraveled = $trainData['track_units_remaining'];
        $trackUnitsRemaining = 0;
      }
      //$trainRow = $trainData;

      $trainRow['route_id'] = $routeId;
      $trainRow['origin_city_id'] = $originCityId;
      $trainRow['dest_city_id'] = $destCityId;

      if($trainRow['direction'] == '+') {
        $trainRowTrackUnit = $trackUnitsTraveled + $trainData['track_unit']; //2+3=5
      } else {
        $trainRowTrackUnit = $routeData['track_unit_count'] - $trainData['track_units_remaining'];
        if($trainRowTrackUnit < 0) $trainRowTrackUnit = 0;
      }
      $trainRow['track_unit'] = $trainRowTrackUnit; //5
      $trainRow['track_units_remaining'] = $trackUnitsRemaining; //2
      $trainArrived = ($trainRowTrackUnit == 0 || $trainRowTrackUnit == $routeData['track_unit_count']);
      $trainRow['status'] = ($trainArrived == true) ? 'ARRIVED' : 'ENROUTE';

      $trainId = $trainModel->getTrainId($gamePlayerId);
      $where = $trainModel->getAdapter()->quoteInto('id = ?', $trainId);
      $trainModel->update($trainRow, $where);
    }
    return $trainRow;
  }
A: 

My interpretation of your data:

trainData['track_units_remaining'] -- number of units the player can move
trainData['track_unit'] -- the unit on which the train is now
trainData['direction'] -- the direction of train movement
routeData['track_unit_count'] -- route length

EVERYTHING FROM HERE ON WAS EDITED:

Let's clarify the source/destination switching, otherwise the code is hard to understand. The idea is not to instantiate a variable in two mutually exclusive branches. Better have a default value and change it if needed. At least that works better if there is a clear default value.

$originCityId = $trainData['origin_city_id'];
if ( $destCityId != $trainData['dest_city_id'] ) {
    $originCityId = $trainData['dest_city_id'];
}

I can't be sure that the rest of your code is correct, so I suggest you to extract the code we reworked and test it out:

Copy this in a separate file and try to run. I'm sorry I don't have a PHP machine now, can't test myself. This code is independent from your framework and everything else.

You can actually change your original function so that it uses this testable one. Just pass the rest of needed information to it.

function testableMoveTrains($trainData, $routeData) {

    $unitsToDestination = 0;
    switch ( $trainRow['direction'] ) {
     case '+':
      $unitsToDestination = $routeData['track_unit_count'] - $trainData['track_unit'];
      break;
     case '-':
      $unitsToDestination = $trainData['track_unit'];
      break;
         default:
      break;
    }

    $unitsToDestination = $unitsToDestination - $trainData['track_units_remaining'];

    $trackUnitsRemaining = 0;
    $trainArrived = false;

    // Note that I changed from < to <=
    if ( $unitsToDestination <= 0) {
     // Went too far or arrived. 
     $trackUnitsRemaining = abs($unitsToDestination);
     $trackUnit = $routeData['track_unit_count'];
     $trainArrived = true;
    } else {
     // Did not reach destination
     $trackUnitsRemaining = 0;
     switch ( $trainRow['direction'] ) {
      case '+':
       $trackUnit = $routeData['track_unit_count'] - $unitsToDestination;
       break;
      case '-':
       $trackUnit = $unitsToDestination;
       break;
      default:
       break;
     }
    }

    $trainRow = array();
    $trainRow['direction'] = $trainData['direction'];
    $trainRow['track_unit'] = $trackUnit;
    $trainRow['track_units_remaining'] = $trackUnitsRemaining;
    $trainRow['status'] = ($trainArrived) ? 'ARRIVED' : 'ENROUTE';

    return $trainRow;
}

// *******************************************

$trainData = array(
    'track_units_remaining' => 5,
    'track_unit' => 0,
    'direction' => '+',
    'status' => ''
);

$routeData = array(
    'track_units_count' => 8
);

$newTrainData = testableMoveTrains($trainData, $routeData);

if ( $newTrainData['track_unit']!=5 ) {
    error('Unexpected track_unit value:' . $newTrainData['track_unit']);
}
if ( $newTrainData['track_units_remaining']!=3 ) {
    error('Unexpected track_units_remaining value:' . $newTrainData['track_units_remaining']);
}
if ( $newTrainData['status']!='ENROUTE' ) {
    error('Unexpected status value:' . $newTrainData['status']);
}
zilupe
Zilupe: thank you! I will integrate this code and let you know how it goes.
Chris Barnhill
I have updated my answer below. Please check it out when you get the chance. Thanks.
Chris Barnhill
A: 

Zilupe:

This is the latest version of moveTrain(). It seems to work 90% of the time, but it still has its quirks. I couldn't have done it without your help. Thanks!

  public function moveTrain($destCityId) {
    require_once 'Train.php';
    $trainModel = new Train();

    require_once 'Route.php';
    $routeModel = new Route();

    $userNamespace = new Zend_Session_Namespace('User');
    $gamePlayerId = $userNamespace->gamePlayerId;

    $trainData = $trainModel->getTrain($gamePlayerId);

    $originCityId = $trainData['origin_city_id'];
    $destChanged = false;
    if ( $destCityId != $trainData['dest_city_id'] ) {
        $originCityId = $trainData['dest_city_id'];
        $destChanged = true;
    }
    $routeId = $routeModel->getPlayerRouteIdByCityIds($gamePlayerId, $originCityId, $destCityId);
    $trainRow = array();
    // if route is invalid OR if destination city has not changed and train has arrived,
    // bypass train movement
    if($routeId !== null) {
      if((!$destChanged && $trainData['status'] == 'ENROUTE') ||
        ($destChanged && $trainData['status'] == 'ARRIVED')) {
        $routeData = $routeModel->getRouteByCityIds($originCityId, $destCityId);
        $unitsToDestination = 0;
        $trainRow['direction'] = $routeModel->getRouteTravelDirection($originCityId, $destCityId); //+
        // if traveling to a new city destination and traveling negative then train track unit
        // is equal to route track unit count (because train is at the end of a new route)
        if($destChanged && $trainRow['direction'] == '-' && $trainData['status'] == 'ARRIVED') {
          $trainData['track_unit'] = $routeData['track_unit_count'];
        }
        switch ($trainRow['direction']) {
            case '+':
                $unitsToDestination = $routeData['track_unit_count'] - $trainData['track_unit'];
                break;
            case '-':
                $unitsToDestination = $trainData['track_unit'];
                break;
            default:
                break;
        }
        // 2. Move the train
        $unitsToDestination = $unitsToDestination - $trainData['track_units_remaining'];
        $trackUnitsRemaining = 0;
        $trainArrived = false;

        // Note that I changed from < to <=
        if ( $unitsToDestination <= 0) {
            // Went too far or arrived.
            $trackUnitsRemaining = abs($unitsToDestination);
            $trackUnit = $routeData['track_unit_count'];
            if($trainRow['direction'] == '-') {
              $trackUnit = 0;
            }
            $trainArrived = true;
        } else {
            // Did not reach destination
            $trackUnitsRemaining = 0;
            switch ( $trainRow['direction'] ) {
                    case '+':
                            $trackUnit = $routeData['track_unit_count'] - $unitsToDestination;
                            break;
                    case '-':
                            $trackUnit = $unitsToDestination;
                            break;
                    default:
                            break;
            }
        }
        // 3. Save changes carefully.
        $trainRow['route_id'] = $routeId;
        $trainRow['origin_city_id'] = $originCityId;
        $trainRow['dest_city_id'] = $destCityId;
        $trainRow['track_unit'] = $trackUnit; //5
        $trainRow['track_units_remaining'] = $trackUnitsRemaining; //2
        // $trainArrived = ($trackUnit == 0 || $trackUnit == $routeData['track_unit_count']);
        $trainRow['status'] = ($trainArrived) ? 'ARRIVED' : 'ENROUTE';

        //$trainId = $trainModel->getTrainId($gamePlayerId);
        $where = $trainModel->getAdapter()->quoteInto('id = ?', $trainData['id']);
        $trainModel->update($trainRow, $where);
      } else {
        $trainRow = $trainData;
      }
    }
    return $trainRow;
  }
Chris Barnhill
I noticed something else. The ending track unit was zero. It should have been 8 since the train was traveling in a positive direction.
Chris Barnhill
I edited my entry, so have a look at it.
zilupe