rubyonrails
Building a Space Strategy Game with Ruby on Rails - Part II
In the second part of this series (see: Part I) I will show you how to get started with Ruby on Rails for our space strategy game and easily integrate an authentication system.
Setting up a rails application
If you’re familiar with RoR you will already know how easy this can be. I’m developing on a Mac OSX platform which comes with Ruby installed and I will be hosting on standard Linux/Debian box with Ruby already installed. I will also be using mysql database both locally and in production.
First thing I will ensure I have the necessary gems to start developing. Starting with an empty gem repository I would issue the following commands into my terminal:
sudo gem install rails -v 2.0.2 sudo gem install mysql
This will fetch the necessary gems for running rails (I’ve specified the version here to avoid getting the bleeding edge version) and connecting to mysql. For a good article on setting up mysql for use with rails on Mac OSX see here.
With our rails environment set up, I can jump in and create our project:
rails -d mysql space_pbbg cd space_pbbg
The above will create the framework for our application and automatically configure it to connect to a local mysql database.
Now we use rake, ruby’s make tool, to create our database for us:
rake db:create
This will create our development database (space_pbbg_development) ready for us to add tables via our model definitions later.
Finally, to test our application we issue the following command:
ruby script/server
This starts our RoR app on http://localhost:3000 and we should see a nice generic welcome screen there that tells us everything has worked out alright so far.
Hooking up player authentication
As players need to register and login to play our game, and the player model object is going to be root of much of our application anyway, I like to do this part up-front. I also don’t like reinventing the wheel and prefer to use existing code (or code generators) as much as possible (it’s the RoR way!). However, the state of authentication with rails is a bit of a minefield at present: there are many possible solutions out there with some of them being clunky or difficult to work with.
That said I’ve had success in the past with a somewhat old plugin called Acts_as_authenticated which I’m going to use here. I’m going to install the plugin, run some of its generators to generate the necessary model and supporting code and fine tune it a bit so it fits my needs.
ruby script/plugin source HTTP://svn.techno-weenie.net/projects/plugins ruby script/plugin install acts_as_authenticated ruby script/generate authenticated player account ruby script/generate authenticated_mailer player
And there you have the basics needed all in 4 commands! The first lets our plugin manager know where to fetch the plugin. The second downloads the plugin. The third creates the necessary model, controller and helpers that facilitates authentication. And finally, we create a notifier model that uses ActionMailer and makes it easy to send emails out to players.
To get the new player model into our database we run the following command:
rake db:migrate
We’re almost ready to test our player sign up and authentication system but first we have to do our first bit of “coding”… well, a bit of cut and paste and writing a simple welcome screen.
First, lets open up the generated app/controllers/account_controller.rb file where we will find these two lines:
# Be sure to include AuthenticationSystem in Application Controller instead include AuthenticatedSystem # If you want "remember me" functionality, add this before_filter to Application Controller before_filter :login_from_cookie
Following the instructions we cut these lines and paste them inside the class definition for our controllers’ base class in app/controllers/application.rb.
Now we need to configure routing of the application so when players access http;//localhost:3000/signup they get the registration page, http;//localhost:3000/login they get the login page and finally http;//localhost:3000/logout will log them out.
We do this by opening up /config/routes.rb and adding the following lines after the first line which should be “ActionController::Routing::Routes.draw do |map|“:
map.home '', :controller => 'account' map.login '/login', :controller => 'account', :action => 'login' map.logout '/logout', :controller => 'account', :action => 'logout' map.signup '/signup', :controller => 'account', :action => 'signup'
A couple of things to note about the above:
- I’m using named routes so rails will automatically provide helper methods to reference the urls for these actions (e.g.
login_pathreturns ‘/login’). This is handy and keeps hard-coded URLS out of the code. - I’ve mapped the empty relative path to
homewhich means when someone accesses http://localhost:3000 they will be sent to theindexaction method of theaccountcontroller. However, this only works if you deletepublic/index.htmlso be sure to do that!.
One last thing before we can start our app again and see all this in action: I’m going to replace the “funny” blurb provided in app/views/account/index.rhtml with a welcome screen that looks like this:
<% if current_player %> <p>Welcome <%=current_player.login%>!</p> <p><%=link_to 'Logout', logout_path%></p> <% else %> <p>Hello stranger!</p> <p><%=link_to 'Sign Up', signup_path %><br/> <%=link_to 'Login', login_path %> </p> <% end %>
Right, with those changes done, lets restart the app (ruby script/server in case you forgot) and check it out. As there are no players currently, we should be directed automatically to the signup screen where we can register. And if thats working we have our basic PBBG with player sign up and authentication.
Feel free to leave comments and let me know if the above didn’t work for you.
Building a Space Strategy Game with Ruby on Rails - Part I
This article documents the first step in building a new strategy web game. The design phase is a critical phase which often gets overlooked by developers who want to get stuck into code. And truth be told, I’m one of those developers.
Its very easy to dive into the code before you have a solid design and whilst its probably a mistake to get too far into development without a clear idea of what it is you are developing, sometimes its fun and useful to get something working before having all the details worked out. Abstract ideas often fall apart when faced with the reality of a working model and often the finer details need to be left until after the foundation of the software has been laid. However, there’s been many a failed project that rushed to code without a clear vision of what it is to be achieved. And when working with a team of other developers or designers etc, it becomes incredibly important to have a clear understanding what it is you are trying to achieve so the importance of design, planning and documentation increases.
That said, what I’m concerned about is designing a game that I will develop by myself (at least for the foreseeable future) and what I need to start me off is a good idea of what the game is about and how it might work. So besides avoiding my usual shorthand in my outlines, I’m going to replicate that process here.
What kind of game?
The first question I’m going to answer is perhaps the most important. Whilst I’d love to one day try to develop something completely unheard of before, this project is going to be fairly well-defined by its genre: a space strategy tick-based web game.
Setting: Space
The setting is more than just colour, it is very important in the design of the game as it sets the tone, map and defines what kind of tokens the players will be dealing with.
The map will consist of galaxies, stars and planets. Galaxies contain stars. Stars contain planets (and similar entities). Planets are where players will be able to build bases and where most of the action takes place.
Galaxies will be a way to partition the play environment to allow different levels of players to co-exist. Basically, new players will be added to a galaxy until it reaches a certain population threshold after which a new galaxy will be used to house new players. Its not a fool-proof mechanism to avoid higher level players dominating lower level players (as galaxies are connected and not to be blocked off from expansion by established players) but it should help.
By tokens, I mean the pieces of the game players will have control over. This game will have effectively two types of tokens: starbases and fleets.
Starbases are your typical strategy game production / research / economic centers where the people live, things are learned and built. They are stationary being built on a planet.
Fleets will consist of units (starships) produced at starbases which can be moved from one planet to another. They may engage other fleets in combat or may try to capture starbases.
Category: Strategy
As a strategy game, players will be concerned with the macro-management of a number of resources, building bases and units, managing diplomatic relations and trade.
Every strategy game has a resource management aspect which is often the driving feedback response to motivate players. Collecting stuff, even virtual stuff, is fairly ingrained in our psyches and we seem to get quite a lot of pleasure in amassing things in games. There’s many ways to go on this from a single resource (representing say money or energy) to a multitude of items with their own attributes and uses.
We’re going to keep it simple but not too simple and have four resources: metals, carbon, minerals and crystals. The names just add colour but we’re going to also say that metals and carbon will be fairly common on all planets, minerals will be less common but still available in some quantity on all planets whilst crystals will be rare and only appear on some planets.
Metals and carbon are going to be our bread-and-butter resources and whilst there will be some diversity in their distribution, everybody should have plenty at their disposal. Therefore, almost everything that can be produced will use them in some quantity. We’re going to weigh starbase buildings to use more carbon than metal and we’re going to make units use more metals than carbon. That way depending on a player’s focus one or the other resource will probably be more important to them unless they take a balanced approach to building units and buildings.
The other two resources have specific purposes within the game ecosystem: Minerals are going to be useful in most of the expensive things players want to use and I can see it being the defacto currency (more on trade later). Crystals will be needed to unlock the advanced tech items within the game and will be a source of conflict within the game.
Resource collection will be done primarily through specialised buildings at starbases. Later on I will consider having units capable of harvesting resources outside of planets and what to do with destroyed units. Resources will be located at a particular starbase and will need to be moved before they can be used by another starbase.
Trading will be done in a fairly simple way. Each starbase will have a transport capacity based on the presence of a special type of building called a spaceport. This capacity can be used to offer resources on the galactic market for another player to buy or to transfer resources to another base.
Besides the four resources mentioned two other limited resources will come into play: colonists and energy. Colonists represent the population of starbases whilst energy is produced by power plant buildings and used by every other buildings. The purpose of these resources is simply to provide friction to growth as well as to add colour. Colonists will need housing which needs to be built and most buildings will consume energy which needs to be produced by power plants. Neither are harvested in the way the other resources are nor are they tradeable or moveable. They will be limited by the capability of the planet the starbase is located upon: a planet’s fertility rating determines the cost of building habitation for colonists whilst energy is collected via solar rays (which vary based on the closeness of the planet to the star) and gas emissions (with some planet types being better for this resource). There will be a few additional energy producing plants that don’t rely on the planet but they will be expensive.
So like a typical strategy game, players first objective is to maximise their harvesting of resources to allow them to expand their starbases. They may expand beyond their initial starbase using colony ships or trade for resources they may be short of.
But that’s all foreplay because what makes a game is conflict and for that we have fleets. They are effectively mobile tokens that are used to trigger combat which can reward resources (based on units destroyed) and experience. Experience will be used to train starbases or fleets to have enhanced abilities.
But free-for-all combat is not much fun long-term and the chaotic universe it represents gets very two-dimensional after a while so we’re going to have in place a fairly well developed diplomacy system. Players will be able to form and join alliances who may stake a claim to a particular star system, declare war on other alliances or agree non-aggression pacts and the like. Alliances will have a number of tools to help communicate between members and a command structure that should be reasonably flexible to allow different types of alliance leaderships to develop.
Process: Ticks
Turn-based web games work similarly to board games or single player computer games found on your desktop. Basically, you give orders to your tokens (in this case bases and fleets) and when its “your go” they carry out those orders as well as they can. In a board game / single player computer game, “your go” is literrally a round-robin mechanism wherein each player (including computer players) takes a turn and has a limited amount of actions they may perform with some actions taking more than one turn to complete.
This is usually accomplished in web games by giving each player a number of Action Points (or something similar) which gets replenished at a set time (usually once a day). This is actually very popular mechanism for roleplaying PBBGs.
This is in contrast to “real-time” games where actions are taken simultaneously with each order given to a player’s token being scheduled to complete at a set time.
The approach I’m going to take is tick-based: Each action takes a number of ticks to complete. The engine runs a tick at a predefined interval (probably 15 minutes in this case) and any orders (or other events) that are due are triggered during that tick. You can perhaps say this is a sort of automated turn-based system. It feels like a real-time game in that actions take time to occur but updates are done at set intervals which is very turn-like.
***
Now I’ve outlined the kind of game, here are a few “user stories” that we’re going to develop:
Joining the game
Players need to sign up to start playing, providing a minimum set of information about themselves such as a login name, email address, password etc. Once signed up to play, some initial assets are allocated to them to begin playing with. Namely, a new starbase and some resources with which to start constructing new facilities.
Manage Starbases
Players will be able to see their existing starbases, presented in a tabular format with some pertinent information such as resource gathering capacities, energy produced / used, population supported / available, construction / research / production / transport capacity and of course, location. From this screen, they will be able to go directly to the detailed starbase view and the construction, production and research screens.
Facility Construction
Players can order new facilities to be built at their starbases using a tabular form which will list existing facilities as well as unbuilt facilities they can build based on their current set of technologies. Clicking on either a “Build” or “Upgrade” link will begin the process of constructing a new facility. Constructing new facilities will consume resources (based on the facility type and level being built) and take a number of ticks (again based on the facility type and level). Both resource cost and time taken is adjusted by facilities present and technologies known. A number of construction orders may be queued for each starbase.
Scrap Facilties
Existing facility levels may be scrapped by the starbase owner, returning the majority of resources used in the construction of that level to the starbase.
Unit Production
Starbases can be ordered to build new units from the unit production screen which lists all available units for that starbase. What units can be built depends on the facilities present at the starbase and the technologies known to the player. Just like constructing a facilities, resources are used immediately in production and take an amount of ticks (time) dependent on the facilities and technologies available. A number of production orders may be queued for each starbase. Units are added to a new or an existing fleet in orbit of the planet of the starbase at which they are built.
Research Technology
Starbases can begin research projects to make new facilities, units, other technologies or gameplay elements available. Technologies may have both facility and technology prerequisites which must be met before a starbase can begin research into them. Further levels may be researched into a specific technology for enhancements to the efficiency of a variety of gameplay factors (e.g. construction speed, unit movement speed, weapon effectiveness etc) or to unlock units / facilities / etc. The time it takes to complete research depends on the technology, level being research and the research capacity of the starbase. A number of research orders may be queued for each starbase.
Transport Resources
Players may use a starbase’s transport capacity to shuttle resources from starbase to another. The amount of time this takes depends on the distance between the two starbases and may be adjusted by known technologies. Until the resources are delivered, a portion of the starbase’s transport capacity is used up.
Offer Resources for Trade
A starbase may offer an exchange of some resource for another with any starbase (of any player) willing to meet those demands (see Buy Resources). In most ways this works as a two-way transportation of resources between two starbases using transport capacity and taking time to deliver once accepted.
Buy Resources
Players can search for resources offered by other starbases and provided they have enough of the demanded resource and the transport capacity at the starbase to complete the transaction may buy the resource.
Manage Fleets
Players will be able to manage their fleets through a screen which lists all their fleets, their current location and size as well as any movement orders they are making. From this screen they will be able to go to a detailed view of the fleet where the exact composition is shown and menu options for various fleet actions are presented.
Move Fleet
Players may order a fleet to move to another planetary body using the planet’s coordinates or a bookmark saved earlier, with special bookmarks available to the player’s own starbases as well as any starbases of other players they occupy. Movement is restricted for certain units which are either incapable of moving without being carried by units capable of carrying them (ie. fighters need to be carried in hangars of carriers or other capitol ships) or may not be capable of warp movement (and thus cannot move from one galaxy to another without the use of a jumpgate facility at a friendly starbase).
Attack Fleet / Starbase
Fleets may be ordered to attack a fleet and / or starbase in their current location provided it doesn’t belong to the same alliance. This initiates the combat system which we will discuss in more detail at a later time. Successful attacks against enemy starbases results in them being occupied and pillaged (to be detailed later).
***
And that’s it for the design so far. In the next tutorial, I’ll begin putting together the Ruby on Rails project and show how you can easily setup an authentication system for your game.
A Simple Combat System (Ruby on Rails)
Introduction
This entry is based on a Building Browsergames blog entry: A Simple Combat System (PHP)
It pays to at least skim over the original entry before going over the Ruby on Rails version below.
Monsters and Hitpoints and Forest, Oh My!
The PHP version of this entry starts off by creating a way to store monsters within the game and adding some new stats (maxhp and currenthp). First off, we’ll use the generator to create the new model we want:
> ruby script/generate model Monster name:string attack:integer defense:integer magic:integer gold:integer max_hp:integer cur_hp:integer
All by itself the generator did a fine job with the creation of the migration for Monster. We provided enough information about the fields to put in each one when we called the generator from the command line. But the migration to add the new stats to the user and add example monsters isn’t just adding a table with some fields, it’s performing a whole set of operations so we’ll edit the migration file to add some more to it:
class CreateMonsters < ActiveRecord::Migration def self.up create_table :monsters do |t| t.string :name t.integer :attack t.integer :defense t.integer :magic t.integer :gold t.integer :max_hp t.integer :cur_hp t.timestamps end # Add the maximum hitpoints and current hitpoints stats to the user. # Note: Setting the defaults here will set default values for any existing # users already in the database as well as for any new ones created. So we # won't have to update the before_create method in the User model to set # defaults for these values. add_column :users, :max_hp, :integer, :default => 10 add_column :users, :cur_hp, :integer, :default => 10 # Create some example monsters. Monster.create(:name => 'Crazy Eric', :attack => 2, :defense => 2, :max_hp => 8, :cur_hp => 8, :gold => 5) Monster.create(:name => 'Lazy Russell', :attack => 1, :defense => 0, :max_hp => 4, :cur_hp => 4, :gold => 20) Monster.create(:name => 'Hard Hitting Louis', :attack => 4, :defense => 3, :max_hp => 10, :cur_hp => 10, :gold => 5) end def self.down remove_column :users, :cur_hp remove_column :users, :max_hp drop_table :monsters end end
This is one of our best examples of migration so far because you can see new tables being created, existing ones being revised, and additional code that exists just to give us a starting point in our application. Next we’ll add the two new stats to the welcome page so they are visible when a user is logged in.
<h1>Welcome To The Game</h1> <% if logged_in? %> <h2><%= @current_user.login %></h2> <ul> <li>Attack: <strong><%= @current_user.attack %></strong></li> <li>Defense: <strong><%= @current_user.defense %></strong></li> <li>Magic: <strong><%= @current_user.magic %></strong></li> <li>Gold in hand: <strong><%= @current_user.gold %></strong></li> <li>Current HP: <strong><%= @current_user.cur_hp %>/<%= @current_user.max_hp %></strong></li> </ul> <%= link_to "The Forest", :controller => "forest" %> <% end %>
Even though I haven’t created the controller to handle the forest yet, I’ve gone ahead and added a link to it above.
The Forest
> ruby script/generate controller Forest index
Now it’s time to fill in the code for the forest so we can actually encounter something there. First we’ll take a crack at the view in app/views/forest/index.html.erb:
<h1>The Forest</h1> <p>You've encountered a <%= @monster.name %>!</p> <%= link_to "Attack", :controller => "forest", :action => "attack" %> <%= link_to "Run Away", :controller => "forest", :action => "run_away" %>
One thing you might note in the upcoming code is that I’ve added a “before_filter” to the code. That’s a callback that Rails provides you in your controller, much like ActiveRecord had callbacks for certain points within the lifetime of a record, a controller has callbacks that can get called before and after each action. That makes it easy to have something called before each and every action within this controller. In this case we just tell it to run the login_required function which restful_authentication provided. It will check to see if the user is logged in before any action on the page and if not, redirect to the root page for the application.
class ForestController < ApplicationController # We don't want anyone other than logged in users going to the forest. before_filter :login_required def index current_user @monster = Monster.random_encounter end end
In addition we have to add the random_encounter method to the Monster model so we can call it to get a random monster. Note again that I’ve put game logic down in the model and kept the controller clean of all database interaction and logic. It’s just glue between the views which display things/take input and the models which handle all the data access and game logic.
def self.random_encounter # For a single random record I'm using the technique from here: # http://robzon.aenima.pl/2007/12/selecting-random-row-from-table.html random_monster = self.find(:first, :offset => (self.count * rand).to_i) # We'll set the number of hitpoints for the monster to the max before unleashing # the freshly retrieved creature out on the world. Note, this is just a copy # of the database record. There could be dozens of these in the system all at # the same time. As long as none of them is saved back to the database each one # can be changed independently (e.g. taking damage), because you're only changing # the in-memory copy. random_monster.cur_hp = random_monster.max_hp random_monster end
At this point we’ve added some new stuff to our app and we can try it out. Don’t forget, if you haven’t already run the migration we created earlier to add the monsters table to the database then we need to run it now before we hit the pages that require the new data and fields:
> rake db:migrate
Here’s what our welcome page looks like now when the user is logged in:
And here’s the new forest page we see after hitting the link to go there:
One thing worth trying if you want to see the restful_authentication system in action is logging out of the game and then trying to go directly to the http://localhost:3000/forest page. When you do you’ll immediately find yourself redirected to the home page for the application thanks to the before_filter we added earlier.
Since the links for user actions (i.e. attack, run away) aren’t hooked up yet, clicking on them just gives you a Rails error. We’ll fix that by expanding the Forest controller to handle the user’s actions and by adding the ability to conduct combat to the User model:
class ForestController < ApplicationController # We don't want anyone other than logged in users going to the forest or executing # actions inside the forest. before_filter :login_required def index current_user # Get a random monster for us to encounter in the forest. @monster = Monster.random_encounter # Save the monster in our session. session[:monster_id] = @monster.id end def attack current_user # Pull the monster we were fighting from the session. @monster = Monster.find(session[:monster_id]) # Fight the monster and get the transcript of the fight. @combat = @current_user.fight(@monster) end # Running away couldn't be much easier, just send the user back to the welcome page. def run_away session[:monster_id] = nil redirect_to :controller => "welcome" end end
You might notice that in addition to adding the attack and run_away actions, I also changed the index action. It still asks for a random monster for the encounter in the forest, but now it saves that monster in the session. That way we aren’t sending the monster ID to the user with the page (the PHP code sends it as a hidden field) and then using it after we get it back. Remember, you can never send something to the user and implicitly trust what you get back. It could have been modified (e.g. the user might have swapped out the monster you picked for an easier or harder one). By storing the monster in the session we avoid that security hole. But remember how long your session lasts, if the session times out, is it OK to lose the information in it or is it something that needs to be saved to the database instead?
We wrote the fight function into the controller without really knowing how it works, which is a good thing because details of things like combat shouldn’t be up at the level of a controller. Instead it belongs down in the model layer. Specifically, I’ve put it into the User model:
def alive? self.cur_hp > 0 end def fight(monster) combat = Array.new turn_number = 0 # This is a small workaround for the fact that we aren't getting the user's name yet # when they signup. All we have for the user is the login. if (self.name.empty?) self.name = self.login end while (self.alive? and monster.alive?) turn_number += 1 # Switch the roles of attacker and defender back and forth. if (turn_number % 2 != 0) attacker = monster defender = self else attacker = self defender = monster end if (attacker.attack > defender.defense) damage = attacker.attack - defender.defense # We allow damage to take you below zero hitpoints. Presumably the funeral is # closed casket in those cases. defender.cur_hp -= damage # We'll only make a turn record for those cases where something actually happens. turn = Hash.new turn["attacker"] = attacker.name turn["defender"] = defender.name turn["damage"] = damage # Save this turn in the combat transcript. combat << turn end end # Somebody is dead, let's see who and take appropriate action. if (self.alive?) # Yay, you lived. You get gold. self.gold += monster.gold else # There aren't any penalties for losing at the moment. end # We only save the user's record back to the database. The monster needs to # stay unchanged in the database no matter how much damage we did to it so # the next one we make will still be pristine. self.save # Return the results of combat. combat end
We’re done with the User model, but we need to tweak the Monster model one more time to add a function to determine if the monster is alive too:
def alive? self.cur_hp > 0 end
Finally we have to have a page that displays the results of combat. Rather than wedging it into the index page for the forest, I felt it was easier to just have another page for it (app/views/forest/attack.html.erb):
<h2>Combat Results</h2> <p> <% @combat.each do |t| %> <%= "#{t['attacker']} hit #{t['defender']} for #{t['damage']} damage." %><br/> <% end %> </p> <% if @current_user.alive? %> <p>You killed <strong><%= @monster.name %></strong>! You gained <strong><%= @monster.gold %></strong> gold.</p> <%= link_to "Explore Again", :controller => "forest" %> <%= link_to "Home", :controller => "welcome" %> <% else %> <p>You were killed by <strong><%= @monster.name %></strong>.</p> <% end %>
Now we can go ahead and click the “Attack” and “Run Away” links on the forest page to see what happens. Here’s an example of clicking the Attack link:
Extra Credit
-
Download it. If you’ve only been following along so far by just reading the text about how to build all the pieces, it’s time for you to go get the code from the Subversion repository on Google and download it so you can see what I’ve done. The thing I think you should notice in particular is just how few files we are talking about and how little is in each file. Here’s the contents of the app directory (where almost all of you code goes):
See the recent post Now On Google Code! for instructions on downloading the code.
-
I talked about “sessions” in the code above but I didn’t really go into what they are. For a little more info about sessions, read something like Understanding Sessions or for a more in depth look at what sessions are in general Session @ Wikipedia.
Displaying A User’s Stats (Ruby on Rails)
Introduction
This entry is based on the Building Browsergames blog entry: Displaying A User’s Stats (PHP)
It’s a pretty good idea to at least skim quickly through the original article before you go through this one, just to familiarize yourself with what we’re going to do.
Second Verse Same As The First
> ruby script/generate migration AddGoldToUser gold:integer
To add a new stat in the database for the amount of gold held we’ll create and edit a new migration. You saw something just like this a few entries back in this series:
class AddGoldToUser < ActiveRecord::Migration def self.up add_column :users, :gold, :integer, :default => 0 # Any existing users will get the new stat just like new users. If we had # to do any special setup for the value though, we could do it here. end def self.down remove_column :users, :gold end end
Note that a default value for the gold stat has been added above. The code as generated won’t have that. Be sure to run the new migration so we get our database schema updated:
> rake db:migrate
Now we’ll change the index page to display the various stats that we’ve assigned to our user whenever he/she logs in.
<h1>Welcome To The Game</h1> <% if logged_in? %> <h2><%= @current_user.login %></h2> <ul> <li>Attack: <strong><%= @current_user.attack %></strong></li> <li>Defense: <strong><%= @current_user.defense %></strong></li> <li>Magic: <strong><%= @current_user.magic %></strong></li> <li>Gold in hand: <strong><%= @current_user.gold %></strong></li> </ul> <% end %>
Note that neither the controller nor the view have ever accessed the database or set any values. Instead they get the user model which represents a particular user and call functions on it to get its values. This is exactly the kind of thing we want to see, controllers and views which are ignorant on details (like SQL) and depend upon a well written model to handle them. This will give us compartmentalized code that is easier to understand, to test, and to modify.
By this point, you should be starting to get a little more comfortable with how Rails lays out things. You’ll see HTML in the views with the occasional reference to variables we need to display (e.g. @current_user). Those variables are setup in a controller method which is called before we get the view to render (e.g. index). The controller will rely upon the model (e.g. User) to handle all the nitty gritty details of loading and storing data and also rules for that data (e.g. no more than two weapons per user). You’ll see this again in the next installment as we turn to adding combat to our game.
Extra Credit
- I’m told that I’m a pretty good teacher, in person, but this is not in person and there are probably a lot of times I pick the wrong things to touch lightly on and to belabor. So, a few good books, a very few, is a good idea both for their reference value and because they tend to cover material I don’t cover or give you another chance to see the same things in a different way. Here are two I recommend: The Rails Way (Addison-Wesley Professional Ruby Series)
(the best reference text by far) and RailsSpace: Building a Social Networking Website with Ruby on Rails (Addison-Wesley Professional Ruby Series)
(a complete social network site built in Rails 1.2, a little bit of the code is older but still one of the best all around books out there).
Adding Stats (Ruby on Rails)
Introduction
This entry is based on the Building Browsergames blog entry: Adding Stats (PHP)
It doesn’t hurt to have at least skimmed through the original blog entry about the PHP version before proceeding to the the Ruby on Rails version below.
Setting The User’s Defaults
Now, having skimmed through the PHP version of this, you can ignore most of what happened there. We don’t have to do anything special to use our templating system, it just works. We don’t need to go get the ID of the user after creating a new one, ActiveRecord handles that for us. As soon as we’ve done a User.create or User.new plus a save of the record, the ID will already be filled in for you to use. So where does that leave us?
Rails subscribes to a pattern for developing applications known as Model-View-Controller, I’ll link it to the Wikipedia entry for the topic because frankly I couldn’t find any short simple explanation of it. So hopefully as you see me use it through this series of tutorials you’ll pick up the basics by osmosis. The way MVC is relevant to this is that there is no way we are going to add new stats to a user or assign default values within the registration page or any other page. In Rails, those are views and they exist not to contain game logic but rather to simply display information and take input from the user. That’s it. Code to change the user belongs in the User model, so that’s where we’ll put it.
In this case what we want to do is assign a new set of default values (since the defaults we set in the migration that added the stats isn’t what we wanted). We could add another migration to alter the default values but that’s only necessary if we’ve got existing users we need to fix in the database and the only user’s we’ve got are test ones. If you only need to fix new users, we can just add a before_create function to the User model (app/models/user.rb):
def before_create self.attack = 5 self.defense = 5 self.magic = 5 end
Here’s the User model with our new code in place. Note that virtually all the code here came from restful_authentication when it created the User model in the first place. We just added the code above to it:
require 'digest/sha1' class User < ActiveRecord::Base include Authentication include Authentication::ByPassword include Authentication::ByCookieToken validates_presence_of :login validates_length_of :login, :within => 3..40 validates_uniqueness_of :login, :case_sensitive => false validates_format_of :login, :with => RE_LOGIN_OK, :message => MSG_LOGIN_BAD validates_format_of :name, :with => RE_NAME_OK, :message => MSG_NAME_BAD, :allow_nil => true validates_length_of :name, :maximum => 100 validates_presence_of :email validates_length_of :email, :within => 6..100 #r@a.wk validates_uniqueness_of :email, :case_sensitive => false validates_format_of :email, :with => RE_EMAIL_OK, :message => MSG_EMAIL_BAD # HACK HACK HACK -- how to do attr_accessible from here? # prevents a user from submitting a crafted form that bypasses activation # anything else you want your user to change should be added here. attr_accessible :login, :email, :name, :password, :password_confirmation # Authenticates a user by their login name and unencrypted password. Returns the user or nil. # # uff. this is really an authorization, not authentication routine. # We really need a Dispatch Chain here or something. # This will also let us return a human error message. # def self.authenticate(login, password) u = find_by_login(login) # need to get the salt u && u.authenticated?(password) ? u : nil end protected def before_create self.attack = 5 self.defense = 5 self.magic = 5 end end
Extra Credit
- In this entry you saw one of the hooks ActiveRecord provides for us to perform actions at certain points within the lifecycle of an object (i.e. before_create). There are actually eight hooks in total for you to use. Visit one of several public repositories of Rails documentation (e.g. http://api.rubyonrails.org/) and read the documentation for ActiveRecord::Callbacks to see them all.
- As was mentioned in the previous extra credit item, http://api.rubyonrails.org/ is only one of several repositories of Rails and/or Ruby documentation. Here are a few others: RailsBrain.com, GotApi.com (this site offers documentation for many other APIs as well), and Noobkit Docs.
Putting It All Together (Ruby on Rails)
Introduction
This entry is based upon this entry from the Building Browsergames blog: Putting It All Together (PHP)
A Quick Recap
In past installments we provided a way for users to authenticate who they were (login, logout, signup) and have users with stats. The original article shows the SQL that was used to create the tables but we used migrations from our Rails code to do the same thing.
The example code we’ve already built is basically equivalent to what is built in the PHP entry so all we’re going to do is a few minor tweaks to match the version outlined in the article about the PHP version:
- Our pages don’t have titles - We’ll use the layout feature of Rails to fix that.
- Our pages don’t have any style - The PHP examples don’t have much style either, but at least they’ve got messages in green and red depending upon the type of message.
Note: One other difference is that restful_authentication logs you in immediately after registration. If the plan is not to require email confirmation, I prefer that we do it that way.
Let’s go through the changes required to make our code match the PHP version feature for feature so we’re up with the latest version.
Add Titles
We’re going to use another part of the templating engine in Rails to make it easy to have a title on every page. Rails has a concept called layouts. A layout takes the [something].html.erb files we’ve been creating and wraps them with another [something].html.erb file. It’s handy where you want a group of pages to all have the same headers, footers, navigation, etc. which, if you think about it, is most every website you go to. In order to wrap all the pages you create a layout called application.html.erb. A layout with that name in the layouts directory will be used as a default layout for the entire application. You can override the default easily if you need to though, one of the extra credit learning opportunities this week is about that. Here’s the app/views/layouts/application.html.erb I created:
<html> <head> <title><%= @title %></title> </head> <body> <%= yield %> </body> </html>
Now we can edit the various controllers (welcome_controller.rb, users_controller.rb, and sessions_controller.rb) adding assignments to @title in the methods that get called before we go to the view. For example:
class WelcomeController < ApplicationController def index # Call the function current_user to get the user assigned to the # @current_user variable (if anyone is logged in). The # function comes with restful_authentication. current_user @title = "Welcome" end end
Note that all we added here was an assignment to the @title variable in the controller action. Once the action is finished Rails will render the app/views/welcome/index.html.erb (with our new layout surrounding it) and the @title will go into the title of the page.
Add A Little Style
Styling message display is pretty easy, we just put a div tag with a class around the flash[:notice] output and we’re set. The class can then be styled any way we like:
<% if flash[:notice] %> <div class="flashNotice"><%= h flash[:notice] %></div> <% end %>
To handle styling our entire game we’ll create a new file (public/stylesheets/pbbg.css). To get the stylesheet loaded we can add the following line within the head section of app/views/layouts/application.html.erb:
<%= stylesheet_link_tag "pbbg" %>Styling the errors that come out in the form whenever a user’s input fails validation isn’t much more complicated. If we take our registration form as an example, we can botch the password confirmation field and submit to see what we get:
<div class="errorExplanation" id="errorExplanation"> <h2>3 errors prohibited this user from being saved</h2> <p>There were problems with the following fields:</p> <ul> <li>Password confirmation can't be blank</li> <li>Password can't be blank</li> <li>Password is too short (minimum is 6 characters)</li> </ul> </div> <form action="/users" method="post"> <div style="margin:0;padding:0"> <input name="authenticity_token" type="hidden" value="31249bde4aede2633a4391a44eafff49ec27ae17" /> </div> <p><label for="login">Login</label><br/> <input id="user_login" name="user[login]" size="30" type="text" value="Testing" /></p> <p><label for="email">Email</label><br/> <input id="user_email" name="user[email]" size="30" type="text" value="sample@email.com" /></p> <p><label for="password">Password</label><br/> <div class="fieldWithErrors"> <input id="user_password" name="user[password]" size="30" type="password" /> </div></p> <p><label for="password_confirmation">Confirm Password</label><br/> <div class="fieldWithErrors"> <input id="user_password_confirmation" name="user[password_confirmation]" size="30" type="password" /> </div></p> <p><input name="commit" type="submit" value="Sign up" /></p> </form>
That’s the error output and the form (with some of the error output wrapped through it) generated automatically by Rails. The thing to notice there is that Rails has inserted a couple of div tags automatically. One wraps the description of any and all errors (i.e. class=”errorExplanation”) and the other wraps individual fields which had errors (i.e. class=”fieldWithErrors”). By styling those two classes and even tags within each one (e.g. the h2 and ul tags in the explanation section) we can make the error output from Rails a little more appealing.
It probably wouldn’t take you a lot of effort to come up with better CSS to style the tags than what I present here, but at least it will give you a starting point (public/stylesheets/pbbg.css):
.errorExplanation { background-color:#ffcccc; border:1px solid red; vertical-align:top; padding: 10px; } .errorExplanation h2 { color: maroon; } .errorExplanation ul li { list-style: square; } .fieldWithErrors input[type="text"], .fieldWithErrors input[type="password"] { border-color: red; } .flashNotice { border: 1px solid green; background-color: #ccffcc; }
Here’s what the error page above looks like with that crude CSS applied to it. Note that not only is the error explanation styled, but that the fields that are in error are outlined in red:
Extra Credit
- You’ve already seen that you can easily have a default layout for the entire application. Watch Railscasts #7 - All About Layouts to learn about default layouts for any view in a particular controller and about how to override the default layout when you want something different for certain actions or even dynamically decide which layout to use.
Making Your Forms Auto-Focus (Ruby on Rails)
Introduction
This covers the same material as this entry from Building Browsergames: Making Your Forms Auto-Focus
Why Do We Have To Do Anything Different?
The advice is excellent and it works perfectly, but it has to be tweaked ever so slightly for Rails. Rails actually generates your form fields for you and names them itself in order to easily copy an object into the fields when populating a form and then make a new object out of the form inputs afterward. As a result, the form_for command you see in a page like app/views/users/new.html.erb:
<% form_for :user, :url => users_path do |f| -%> <p><%= label_tag 'login' %><br/> <%= f.text_field :login %></p> <p><%= label_tag 'email' %><br/> <%= f.text_field :email %></p> <p><%= label_tag 'password' %><br/> <%= f.password_field :password %></p> <p><%= label_tag 'password_confirmation', 'Confirm Password' %><br/> <%= f.password_field :password_confirmation %></p> <p><%= submit_tag 'Sign up' %></p> <% end -%>
Generates HTML that looks like this:
<form action="/users" method="post"> <div style="margin:0;padding:0"><input name="authenticity_token" type="hidden" value="41adf13eec9e99506a55c427ce59052a5543cf70" /></div> <p><label for="login">Login</label><br/> <input id="user_login" name="user[login]" size="30" type="text" /></p> <p><label for="email">Email</label><br/> <input id="user_email" name="user[email]" size="30" type="text" /></p> <p><label for="password">Password</label><br/> <input id="user_password" name="user[password]" size="30" type="password" /></p> <p><label for="password_confirmation">Confirm Password</label><br/> <input i





