Monday, March 17, 2014

Demigod: How to avoid cheating when check-in using jQuery and Ruby

Natalie and I have worked on the challenges progress bar. The number of check-ins needed to success the challenge depends on two variables whose combination gives its duration (or number of total check-ins needed): the number of check-ins per week, and the number of weeks. So, if we select a challenge requires 2 check-ins per week during 4 weeks, the duration is going to be 8 check-ins. Taken into account the previous statement, the main problem is how to control user's cheating when check-in, because if we do not control it, a user could check-in continuously, succeeding the challenge in few seconds.

The idea was to simplify the problem and then, trying to apply to a real situation. To do this in a simple approach, we thought that the easiest way was to prevent check-ins in the first minute since the user picks a challenge. We needed to disable the "Success" and "Fail" buttons during one minute, and then, enable both of them. We focused on the user's challenge partial file, one of the files we have in the views/users/ folder (Ruby on Rails).

The first variable we need is the current progress for the selected challenge, which we retrieved from the instance variable in the users controller (*.rb). Note that we added Ruby code inside the jQuery function using the proper syntax: #{ruby_code}:

var progress = #{@current_progress};

We created a new function called isValidCheckin which receives the current progress and basically checks the time that has passed since the first time stamp in the database associated to that pair challenge-user (we use an history table to store this information). Therefore, the function either enables or disables the "Success"/"Fail buttons depending on the time time between the first time stamp and the current time. Because of testing reasons, we set this time to 1 minute delay:

    function isValidCheckin(progress){
          
        var startTimestamp = "#{@start_timestamp}";
        var difference = "#{(Time.now - @start_timestamp.to_time)/1.minute}";
        console.log("Difference: " + difference);
        
        if(difference > 1 && progress < 100){
          $('#successBtn').removeAttr("disabled");
          $('#failBtn').removeAttr("disabled");
        }
        else {
          $('#successBtn').attr("disabled","disabled");
          $('#failBtn').attr("disabled","disabled");
        } 

    } 

To get the number of minutes that have passed since the first time stamp, Ruby has a very useful function: number.minute/day/hour/year... In this case, we used 1.minute, that gets the number of minutes. Having this value stored in the "difference" variable, we checked if it is greater than 1 minute, and if the challenge is not completed yet, we enabled both buttons, otherwise, we disabled them.

Natalie and I started by initializing the buttons, because the user could refresh the page or even log out and log in again. Anyway, if the current challenge is not started yet (progress = 0) or it is done (progress = 100), we had to avoid check-in by disabling both buttons with the "attr" jQuery property. Else, if the challenge is in progress, the buttons must be enabled (jQuery property "removeAttr"):

if ((progress >= 100) || (progress == 0)){
        $('#successBtn').attr("disabled","disabled");
        $('#failBtn').attr("disabled","disabled");
      }
      else if (progress > 0 && progress < 100){
        //enable buttons
        $('#successBtn').removeAttr("disabled");
        $('#failBtn').removeAttr("disabled");
      }

After this piece of code, we called the function to check the time:

isValidCheckin(Progress);

We also added some "breakpoints" to check the variable values on the browser's console. This can be easily done by writing the console.log(variable_name), for example:

console.log("Challenge progress: " + progress);
console.log("Difference (min): " + difference);

In order to test the code, we picked a random challenge and then, we checked the Firefox's console, that showed these messages:

As the challenge was not started, the progress is 0, and the difference en minutes since the challenge started was just a few seconds., the outcome showed on our browser was a disabled "Success" button:


1 minute after we picked the challenge, we refreshed the browser, and again, we checked the console:


Now, the difference is more than 1 minute, so we were able to check in:


The next step is to convert this code to do the same function when the minimum time needed to wait between check-ins depends on the challenge's duration, as I told in the first paragraph.

No comments:

Post a Comment