Sunday, April 19, 2009

Save scores from your JavaScript game in your database

This is a request from a friend of mine. So I thought since I have no time to write my blog, since I am always helping people, maybe I should just write their help in a blog format. It is a great system as it ensures I will have at least one guaranteed reader. (Hello Gabriel.)

Anyways this is my first real blog, and I am not sure how to format everything correctly. Bear with me folks.

First off I had to write a JavaScript game in which to store the result for my test. Since I did not want to spend a lot of time doing the prototype I made it a simple guess the number from 1-100 game.

I am using prototype here, if you can not tell, and of course it get's it's own file.
guessing_game.js

var num_guesses = 0;
var magic_num = 0;

function playGame() {
  $('guess_count').value = 0;
  $('guess_count_display').innerHTML = "0";
  magic_num = newRandomNum();
}

function testForWin() {
  num_guesses++;
  $('guess_count').value = num_guesses;
  $('guess_count_display').innerHTML = num_guesses;
  guess = parseInt($('guess').value)
  if( guess <> 100 )
    $('flash_area').innerHTML = 'You are guessing out of range... Pick a number 1-100.';
  else if(magic_num > guess )
    $('flash_area').innerHTML = "Nope. Guess higher than " + guess + ".";
  else if(magic_num < guess )
    $('flash_area').innerHTML = "Nope. Guess lower than " + guess + ".";
  else if(magic_num == guess)
    $('flash_area').innerHTML = 'You won in only ' + num_guesses + ' guesses!';
  else
    $('flash_area').innerHTML = 'I am confused how you got to this place :).';
}

function newRandomNum() {
  return Math.floor(Math.random()*100) + 1;
}

magic_num = newRandomNum();

I made the random number after load so that the answer is not in the source code. That is important if you want no cheaters. :)

The Guessing game HTML is very simple also:
guessing_game.html.erb

<script type="text/javascript" src="/javascripts/guessing_game.js"></script>
<h1>Generic Guessing Game</h1>
guess a number 1-100
<div id="flash_area"></div>

<div id="game_form">
<%= hidden_field_tag "player_id", @player_id %>
<%= hidden_field_tag "guess_count", 0 %>
Number of Guesses so far: <span id="guess_count_display">0</span><br/>
Guess: <%= text_field_tag "guess", "", :size => 4 %>
</div>

<input type="button" value="Guess" onclick="testForWin();"/>

Not too much fancy in this code either. Just a div with form elements. including my javascript file. I have a hidden field named guess count (this is the score), and I am storing the player_id in an hidden field to make it easy to find the player when I save the game result.

So now we have a working, although rather boring, guessing game. (you would get an error of course if you do not set the @player_id variable.)

So let's set up the controller.
game_controller.rb

class GameController < ApplicationController

  def guessing_game
    # Get your player_id from Player or User Model etc.
    # Just using 100 for now.
    @player_id = 100
  end

  def save_guessing_game_result
    if params[:player_id] && params[:guess_count] && params[:guess_count].to_i > 0
      GameResult.create(:player_id => params[:player_id], :score => params[:guess_count].to_i)
      render :text => "Saved Game Result. Player ID: #{params[:player_id]}; guesses: #{params[:guess_count]}"
    else
      render :text => "Error Saving Results"
    end
  end

end

Hopefully you had a GameResult model. This example assumes that much. Now our game has the tools to save. We just have to tell it when and how to save. That will mean having a couple changes in the JavaScript file.

We have to change the method checkForWin(), and I am adding my AJAX call in a second method to make it easy to read and understand.
guessing_game.js (changes)

...
function testForWin() {
  num_guesses++;
  $('guess_count').value = num_guesses;
  $('guess_count_display').innerHTML = num_guesses;
  guess = parseInt($('guess').value)
  if( guess < 1 || guess > 100 )
    $('flash_area').innerHTML = 'You are guessing out of range... Pick a number 1-100.';
  else if(magic_num > guess )
    $('flash_area').innerHTML = "Nope. Guess higher than " + guess + ".";
  else if(magic_num < guess )
    $('flash_area').innerHTML = "Nope. Guess lower than " + guess + ".";
  else if(magic_num == guess) {
    $('flash_area').innerHTML = 'You won! sending result to the server.';
    sendResultsToServer(); // this is where I call it (after a win)
  }
  else
    $('flash_area').innerHTML = 'I am confused how you got to this place :).';
}

// This is the new method to save the results
function sendResultsToServer() {
  new Ajax.Updater('flash_area', '/game/save_guessing_game_result', {asynchronous:true, evalScripts:true, parameters:Form.serialize('game_form')});
}
...

Do not forget to add the {twiddles} since the else if statement is multi-line now, in the testForWin() function. Let me explain the sendResultsToServer() function a little better.

sendResultsToServer() indepth

// 'flash_area' is where your response will be displayed (our message box)
// you can use '' also to not show any response, but I decided the user might
// want to know, in this example.

// '/game/save_guessing_game_result' is where the JavaScript is sending the message.
// i.e. out game controller and the save_guessing_game_result action.

// next is the properties hash
// Just ignore asynchronous:true, evalScripts:true for now.
// I should find out exactly what they do in the future and will let you know.
// They are just always used for me.

// last item in the properties hash is the parameters:
// Form.serialize('game_form')
// this is a great way to turn any div into a form (as far as the server cares)
// 'game_form' is the div with all my data in it.
// And will come to me in the params hash.

function sendResultsToServer() {
  new Ajax.Updater('flash_area', '/game/save_guessing_game_result', {asynchronous:true, evalScripts:true, parameters:Form.serialize('game_form')});
}


And we are done... Well we should be done. But if you are using Rails 2, we are not done yet. You would get an error: ActionController::InvalidAuthenticityToken. My quick work around was to just turn off the check in my AJAX action.

game_controller.rb (changes)

class GameController < ApplicationController
  protect_from_forgery :except => ["save_guessing_game_result"]
...


And now we have a JavaScript game that saves the results to the server upon completed games. Congratulations.

Wow, my first real blog. Seemed like a simple subject. I hope to get some more blog posts going soon.

No comments: