javiercejudo/javiercejudo.com

View on GitHub
js/controllers/SecretaryProblemCtrl.js

Summary

Maintainability
B
6 hrs
Test Coverage
/*global angular:true, browser:true */

(function (angular) {
  'use strict';

  angular.module('SecretaryProblem', ['ngStorage'])

    .controller(
      'SecretaryProblemCtrl', [
        '$rootScope', '$scope', '$routeParams', '$location', '$filter', '$log', '$timeout', '$localStorage',
        function ($rootScope, $scope, $routeParams, $location, $filter, $log, $timeout, $localStorage) {
          $scope.game = {
            info: {
              name: 'Secretary Problem',
              author: 'Javier Cejudo'
            },
            numDigits: {
              min: 3,
              max: 8
            },
            numDigitsThis: {
              min: null,
              max: null
            },
            base: {
              min: 6,
              max: 12
            },
            baseThis: null,
            n: 10,
            items: [],
            unknownValue: '?',
            numItemsShown: null,
            lastItemShown: null,
            itemSelected: null,
            won: null,
            record: {},
            $storage: $localStorage
          };

          $scope.initSecretaryProblem = function () {
            var game = $scope.game;

            if (game.lastItemShown === -1) {
              return;
            }

            $scope.processN();
            $scope.initContext();
          };

          $scope.initContext = function () {
            var game = $scope.game;
            var items = game.items;
            var numDigits = game.numDigits;
            var numDigitsThis = game.numDigitsThis;
            var randomNumDigitsArray;
            var i;

            game.numItemsShown = 0;
            game.lastItemShown = -1;
            game.itemSelected = -1;
            game.won = null;
            $scope.setRecord();

            randomNumDigitsArray = [
              $scope.getRandomInt(numDigits.min, numDigits.max),
              $scope.getRandomInt(numDigits.min, numDigits.max)
            ];

            numDigitsThis.min = Math.min.apply(null, randomNumDigitsArray);
            numDigitsThis.max = Math.max.apply(null, randomNumDigitsArray);

            game.baseThis = $scope.getRandomInt(game.base.min, game.base.max);

            for (i = 0; i < game.n; i++) {
              items[i] = -1;
            }
          };

          $scope.processN = function () {
            var game = $scope.game;
            var candidateN;

            if (!$routeParams.hasOwnProperty('n')) {
              return false;
            }

            candidateN = parseInt($routeParams.n, 10);

            if (isNaN(candidateN)) {
              $location.path('/game');
              $location.replace();
              return false;
            }

            if (candidateN < 2) {
              candidateN = 2;
            } else if (candidateN > 500) {
              candidateN = 500;
            }

            if (candidateN.toString() === $routeParams.n) {
              game.n = candidateN;

              return candidateN;
            }

            $location.path('/game/' + candidateN);
            $location.replace();

            return candidateN;
          };

          $scope.generateItemValue = function (index) {
            var game = $scope.game;
            var items = game.items;
            var numDigits = game.numDigitsThis;
            var willBeMax;
            var value;
            var gamesPlayed;

            if (items[index] !== -1) {
              if (game.itemSelected !== -1) {
                $scope.initSecretaryProblem();
                $scope.generateItemValue(index);
              } else if (!$scope.isRejected(index)) {
                $scope.selectItem(index);
              }

              return;
            }

            if (game.numItemsShown > 0) {
              willBeMax = (1 === $scope.getRandomInt(1, game.numItemsShown + 1) / (game.numItemsShown + 1));

              if (willBeMax) {
                value = $scope.getRandomInt(
                  $scope.getMax(),
                  Math.pow(game.baseThis, numDigits.max)
                );
              } else {
                value = $scope.getRandomInt(
                  Math.pow(game.baseThis, numDigits.min - 1),
                  $scope.getMax()
                );
              }
            } else {
              value = $scope.getRandomInt(
                Math.pow(game.baseThis, numDigits.min - 1),
                Math.pow(game.baseThis, numDigits.max)
              );
            }

            items[index] = value;

            game.lastItemShown = index;
            game.numItemsShown += 1;

            if (game.numItemsShown === game.n) {
              $scope.endGame();
            }

            if (game.numItemsShown > 1) {
              return;
            }

            gamesPlayed = $localStorage['sp-games-played-' + game.n] || 0;
            $localStorage['sp-games-played-' + game.n] = parseInt(gamesPlayed, 10) + 1;
          };

          $scope.generateRestOfItemValues = function () {
            var game = $scope.game;
            var items = game.items;
            var n = game.n;
            var i;

            for (i = 0; i < n; i++) {
              if (items[i] === -1) {
                $scope.generateItemValue(i);
              }
            }
          };

          $scope.showNext = function () {
            if (!$scope.canShowNext()) {
              return;
            }

            var game = $scope.game;

            $scope.generateItemValue(game.items.indexOf(-1));
          };

          $scope.canShowNext = function () {
            var game = $scope.game;

            return (game.itemSelected === -1);
          };

          $scope.selectItem = function (index) {
            if (!$scope.canSelectItem()) {
              return;
            }

            var game = $scope.game;

            game.itemSelected = index;

            if (game.numItemsShown < game.n) {
              $scope.generateRestOfItemValues();
            }
          };

          $scope.canSelectItem = function () {
            var game = $scope.game;

            return (game.itemSelected === -1 && game.numItemsShown !== 0);
          };

          $scope.isCurrent = function (index) {
            var game = $scope.game;

            return (game.lastItemShown === index && game.itemSelected === -1);
          };

          $scope.isRejected = function (index) {
            var game = $scope.game;

            return (game.items[index] !== -1 && !$scope.isCurrent(index) && game.itemSelected === -1);
          };

          $scope.isMax = function (index) {
            var game = $scope.game;

            if (game.itemSelected === -1) {
              return false;
            }

            return (game.items[index] >= $scope.getMax());
          };

          $scope.isLosingBet = function (index) {
            var game = $scope.game;

            if (game.itemSelected !== index) {
              return false;
            }

            return !($scope.isMax(index));
          };

          $scope.getCurrentValue = function () {
            var
              game = $scope.game,
              currentValue = -1;

            if (game.itemSelected !== -1) {
              currentValue = game.items[game.itemSelected];
            }

            if (game.lastItemShown !== -1) {
              currentValue = game.items[game.lastItemShown];
            }

            return currentValue;
          };

          $scope.getMax = function () {
            var game = $scope.game;

            return Math.max.apply(null, game.items);
          };

          $scope.endGame = function () {
            var game = $scope.game;
            var gamesWon;

            if (game.itemSelected === -1) {
              $scope.selectItem(game.lastItemShown);
            }

            if (!$localStorage['sp-games-played-' + game.n]) {
              $localStorage['sp-games-played-' + game.n] = 1;
            }

            if ($scope.isMax(game.itemSelected)) {
              game.won = true;

              gamesWon = $localStorage['sp-games-won-' + game.n] || 0;
              $localStorage['sp-games-won-' + game.n] = parseInt(gamesWon, 10) + 1;
            } else {
              game.won = false;
            }

            $scope.setRecord();
          };

          $scope.setRecord = function () {
            var game = $scope.game;
            var gamesPlayed = $localStorage['sp-games-played-' + game.n] || 0;
            var gamesWon = $localStorage['sp-games-won-' + game.n] || 0;
            var record = $scope.game.record;

            gamesWon = parseInt(gamesWon, 10);
            gamesPlayed = parseInt(gamesPlayed, 10);

            // don't account for the ongoing game
            if (game.numItemsShown > 1 && game.itemSelected === -1) {
              gamesPlayed -= 1;
            }

            if (gamesPlayed > 0) {
              record.gamesWon = gamesWon;
              record.gamesPlayed = gamesPlayed;
            }
          };

          $scope.resetRecord = function () {
            var game = $scope.game;

            delete $localStorage['sp-games-won-' + game.n];
            delete $localStorage['sp-games-played-' + game.n];
            $scope.game.record = {};
          };

          $scope.getRandomInt = function (min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
          };

          $scope.findBestStrategy = function (n) {
            var strategyCandidate = Math.floor(n / Math.E),
              sum = 0,
              i;

            for(i = strategyCandidate + 2; i <= n; i += 1) {
              sum += 1 / (i - 1);
            }

            return (sum > 1) ? strategyCandidate + 1 : strategyCandidate;
          };

          $scope.automaticGame = function (n, numberOfGames) {
            var game = $scope.game;
            var strategy;
            var itemIndex;
            var gameIndex;
            var currentValue;
            var maxValue;
            var wonGamesCount = 0;

            game.n = parseInt(n, 10) || game.n;
            numberOfGames = parseInt(numberOfGames, 10) || 1;

            if (numberOfGames < 1) {
              $log.warn('The parameter "numberOfGames" must be an integer number');
              return null;
            }

            $routeParams.n = game.n;

            strategy = $scope.findBestStrategy(game.n);

            for(gameIndex = 0; gameIndex < numberOfGames; gameIndex += 1) {
              if ((gameIndex + 1) % 10 === 0) {
                $log.log('Game ' + (gameIndex + 1) + ' completed.');
              }

              $scope.initSecretaryProblem();

              $scope.$digest();

              for(itemIndex = 0; itemIndex < strategy; itemIndex += 1) {
                $scope.showNext();
              }

              for(itemIndex = strategy; itemIndex < game.n; itemIndex += 1) {
                $scope.showNext();

                currentValue = $scope.getCurrentValue();
                maxValue = $scope.getMax();

                if (currentValue === maxValue && itemIndex + 1 !== game.n) {
                  $scope.selectItem(itemIndex);
                }
              }

              $scope.$digest();

              if (game.won) {
                wonGamesCount += 1;
              }
            }

            return wonGamesCount / numberOfGames;
          };
        }
      ]
    );
}(angular));