Asymmetrical View

Narcissism, or don’t try this at home

I’m a frequent reader of Hacker News and when I saw the post about HackerRank and challenge to be invited into the beta if you were on the leader board I had a hard time resisting.

I played a few games manually to make sure I understood the game. Since you can take only 1-5 candies, and to win you must force your opponent to leave you with less than 6, then at the last move you must leave your opponent with 6 candies. So at each move you must leave them with a number of candies divisible by 6, meaning my next move should always be: numCandies % 6

The top entries already had hundreds of games solved so there was no way I was going to get anywhere unless I automated the game playing.

I opened up Chrome’s developer tools and watched the network as I played a game. Things looked straightforward enough based on the POST and PUT requests used to start a game and update it (make the next move) and detection of a completed game was right in the response from performing your next move: {game: {... solved: true}}

I wanted to optimize for my time, not necessarily run-time. I thought of using curl (too much work), of using Ruby’s Mechanize, or Clojure, but all of these solutions would require authenticating to the site first. I wanted to start with automating the game so I opted to write up some javascript and paste it into the console so it could be called.

I started by writing a basic skeleton, and a startGame function to initiate a game, then I cut and pasted it into the javascript console so I could try it interactively.

Foo = (function () {
  var self = {autoContinue: true};

  self.startGame = function (numCandies) {
    $.ajax({
      type: 'POST',
      url: '/splash/challenge.json',
      data: {
        n: numCandies,
        remote: true,
        utf8: '✓'
      },
      success: self.playRound
    });
  };

  self.playRound = function (data) {
    console.log('playRound');
    console.dir(data);
  };

  return self;
}());

I used some bash commands to make the iteration go faster on my Mac:

$ vim hrank.js
# background vim, and press up:
$ pbcopy < hrank.js && fg

This let me make changes and paste them back into chrome with just a few keystrokes. Calling startGame indeed worked – I could see the response from the server that a new game was begun. From there I worked out the rest of the implementation. The only thing to really point out is autoContinue – I used that as a flag and also used a callback (self.continue) to make it easier to step through the process while I was debugging my code and could then set autoContinue to true when I thought it was ready to go.

Full Program

The full JavaScript module that I used is below.

Foo = (function () {
  var self = {autoContinue: true};

  self.startGame = function (numCandies) {
    $.ajax({
      type: 'POST',
      url: '/splash/challenge.json',
      data: {
        n: numCandies,
        remote: true,
        utf8: '✓'
      },
      success: self.playRound
    });
  };

  self.nextMove = function (current) {
    return current % 6;
  };

  self.playRound = function (data) {
    var nextMove;
    console.log('play round: ' + JSON.stringify(data));
    if (data.message) {
      console.log(data.message);
    }

    //if (data.exit) {
    //  console.log('Game is over!');
    //  return;
    //}

    if (data.game) {
      data = data.game;
    }

    if (data.solved) {
      console.log('Game is solved!');
      self.continue = null;
      return self.gameWon();
    }

    // n the starting numCandies
    // current the number of candies left
    // limit: not sure wha thtis is
    // moves array of moves taken?
    nextMove = self.nextMove(data.current);
    console.log('the next move is: ' + nextMove);
    if (nextMove < 1 ) {
      console.error('Whoops something went wrong, the next move is zero? ');
      console.dir(data);
      return;
    }

    self.continue = function () {
      $.ajax({
        type: 'PUT',
        url: '/splash/challenge.json',
        data: {
          move: nextMove,
        remote: true,
        utf8: '✓'
        },
        success: self.playRound
      });
    };


    if (self.autoContinue) {
      self.continue();
    }
  };

  self.playGames = function ( startingValue, numGames ) {
    var currentValue = startingValue - (startingValue % 6) + 1,
        numGames = numGames;

    self.gameWon = function () {
      if (numGames < 1) {
        console.log('games played, currentValue: ' + currentValue);
        return;
      }
      numGames = numGames - 1;
      currentValue += 6;
      console.log('playing next game[' + numGames + ']: ' + currentValue);
      return self.startGame(currentValue);
    };

    numGames = numGames - 1;
    console.log('playing next game[' + numGames + ']: ' + currentValue);
    return self.startGame(currentValue);
  };

  return self;
}());

Glitch?

I think that there is a glitch in the server-side code, during some of the games the current count of candies would regress (see in the log where current jumps from 734 to 870?), so I added a conditional to detect that and halt the automated play (see the check for nextMove < 1).

Setting it off and watching it run I got my ego boost and my name started climbing the board.

Conclusion

I strongly recommend against you taking and running this code as playing the game uses resources on the server side. After I had gotten up towards the top I stopped running the script. It also uses quite a bit of CPU on your machine when running inside Chrome and as the starting count gets larger the number of moves increases linearly so each subsequent game takes longer and longer to play (you don’t get credit for replaying the same game). Also, I’m not giving everything away, the maximum starting value at the time of this writing is 2048, which results in about 341 possible games – but if you look at the leaderboard there are players with higher scores.

I hope by the time you’re reading this Hacker Rank will have either come up wiht a new challenge, or prevented (hopefully) this method from being used to ‘game’ the game.

Tags: javascript,automation,fun