Garden simulator

HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.js"></script>
<div ng-app="myApp">
  <h1>
 The garden
</h1>
  <div ng-controller="myCtrl">
    <table>
      <tr ng-repeat="row in world.grid track by $index">
        <td ng-repeat="cell in row track by $index">
          <img src="http://www.freepngimg.com/download/grass/5-grass-png-image-green-grass-png-picture.png" height="{{cell.grass}}" width="10">
          <img src="http://www.outworldz.com/SeamlessTextures/master/Grass%20textures,CC0/3td_Africa_Grass01.png" height="{{cell.thorns}}" width="10">
        </td>
      </tr>

    </table>
    <button ng-click="cycle(rows)">
      cycle
    </button>
    <button ng-click="cycle(world, {rain: 0})">
      drought
    </button>
  </div>

JavaScript

(function(ng, _) {
  'use strict';

  var
    lodashModule = ng.module('angular-lodash', []),
    utilsModule = ng.module('angular-lodash/utils', []),
    filtersModule = ng.module('angular-lodash/filters', []);

  // begin custom _

  function propGetterFactory(prop) {
    return function(obj) {
      return obj[prop];
    };
  }

  _._ = _;

  // Shiv "min", "max" ,"sortedIndex" to accept property predicate.
  _.each(['min', 'max', 'sortedIndex'], function(fnName) {
    _[fnName] = _.wrap(_[fnName], function(fn) {
      var args = _.toArray(arguments).slice(1);

      if (_.isString(args[2])) {
        // for "sortedIndex", transmuting str to property getter
        args[2] = propGetterFactory(args[2]);
      } else if (_.isString(args[1])) {
        // for "min" or "max", transmuting str to property getter
        args[1] = propGetterFactory(args[1]);
      }

      return fn.apply(_, args);
    });
  });

  // Shiv "filter", "reject" to angular's built-in,
  // and reserve lodash's feature(works on obj).
  ng.injector(['ng']).invoke(['$filter', function($filter) {
    _.filter = _.select = _.wrap($filter('filter'), function(filter, obj, exp) {
      if (!(_.isArray(obj))) {
        obj = _.toArray(obj);
      }

      return filter(obj, exp);
    });

    _.reject = function(obj, exp) {
      // use angular built-in negated predicate
      if (_.isString(exp)) {
        return _.filter(obj, '!' + exp);
      }

      var diff = _.bind(_.difference, _, obj);

      return diff(_.filter(obj, exp));
    };
  }]);

  // end custom _


  // begin register angular-lodash/utils

  _.each(_.methods(_), function(methodName) {
    function register($rootScope) {
      $rootScope[methodName] = _.bind(_[methodName], _);
    }

    _.each([
      lodashModule,
      utilsModule,
      ng.module('angular-lodash/utils/' + methodName, [])
    ], function(module) {
      module.run(['$rootScope', register]);
    });
  });

  // end register angular-lodash/utils


  // begin...