SPRKY Chess

Full-Stack Development With Ruby on Rails

Check Out The App

Case Study

For the capstone of The Firehose Project, our team was given the task of building an online chess app using Ruby on Rails.

The Problem

As four developers learning how to code, we were committed to building a great chess app with high quality source code and a great experience, but none of us had ever worked in a development team before and had little experience writing production-ready server-side code.

The Team

I had the pleasure of working with these three awesome teammates for this project:

Our Process

Step 1. Project Management Tools

Before diving into deliverables, I insisted that we use Trello to manage tasks and Slack for team communication. I had used these two tools together on the Cloudegy project and was amazed at how well they worked together. There was no way we were going to manage this project with email.

Step 2. Delegating Tasks

We started off our first meeting with a quick overview of what we'd be building. Led by the enthusiastic Ken Mazaika (co-founder of The Firehose Project), we were buzzing with excitement.

I took the data modeling task since that was something I had never done before. It seemed like the thought process behind data modeling and information architecture was quite similar and I wanted to get my hands dirty to see what correlations I would find.

Step 3. Data Modeling

the data model I created for our chess app

Step 4. Wireframing

With a few initial wireframes by teammate Michael Warren, we knew what we were building and were on our way towrards Rubyland, but first we needed to setup our deployment pipeline.

a wireframe for our sprky chess app ruby on rails firehose projecta second wireframe for our sprky chess app ruby on rails firehose projecta third wireframe for our sprky chess app ruby on rails firehose project

Step 5. Setting Up Deployment

Aaron Washburn took on the job of setting up a new Heroku app, Github repo, and configuring continuous integration with Codeship.

Step 6. Structuring The Application

Based upon our data model and wireframes, we took to setting up the basic application structure in Rails.

file structure for our chess app ruby on rails the firehose project

We ran migrations and setup our database.

schema.rb
ActiveRecord::Schema.define(version: 20150430224635) do

  enable_extension "plpgsql"

  create_table "games", force: true do |t|
    t.string   "name"
    t.string   "state"
    t.integer  "white_player_id"
    t.integer  "black_player_id"
    t.integer  "winning_player_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "turn"
  end

  create_table "invitations", force: true do |t|
    t.integer  "game_id"
    t.integer  "player_id"
    t.string   "guest_player_email"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "pieces", force: true do |t|
    t.integer  "x_position"
    t.integer  "y_position"
    t.string   "symbol"
    t.boolean  "color"
    t.integer  "player_id"
    t.integer  "game_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "type"
    t.string   "state"
  end

  create_table "players", force: true do |t|
    t.string   "name"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.string   "email",                  default: "", null: false
    t.string   "encrypted_password",     default: "", null: false
    t.string   "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer  "sign_in_count",          default: 0,  null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.inet     "current_sign_in_ip"
    t.inet     "last_sign_in_ip"
  end

  add_index "players", ["email"], name: "index_players_on_email", unique: true, using: :btree
  add_index "players", ["reset_password_token"], name: "index_players_on_reset_password_token", unique: true, using: :btree

end

We got the chess board marked up:

- if game
  %p= raw show_opponent
  %table#gameboard{"data-game-id" => game.id, "data-game-state" => game.state, "data-your-turn" => your_turn?}
    - row_range.each do |row|
      %tr{:class => "y-position-"}
        - row_range.reverse.each do |column|
          - query_result = game.pieces.find { |p| p["x_position"] == column && p["y_position"] == row }
          = raw gameboard_td(query_result, column, row)
  %p= raw show_player

We styled it to look like an actual chessboard instead of a table:

#gameboard {
  border-collapse: collapse;
  margin: 0 auto .5em auto;
  padding-top: 40px;
  width: auto;
  border: 2px solid black;

  tr:nth-child(even) td:nth-child(even), tr:nth-child(odd) td:nth-child(odd) {
    background-image: url('/assets/white-square.jpg');
  }

  tr:nth-child(even) td:nth-child(odd), tr:nth-child(odd) td:nth-child(even) {
    background-image: url('/assets/black-square.jpg');
  }

  tr, td {
    border: 1px solid black;
  }

  td {
    width: 60px;
    height: 60px;
    padding: 0;
    &:hover {
      border: 2px solid $primary-color;
    }
    &.selected {
      border: 2px solid $primary-color;
      &.select-destination {
        border: none;
      }
    }
  }
}

We setup the game model to initialize the chess board with the right pieces when a game is created:

class Game < ActiveRecord::Base
  after_create :initialize_board!
  def initialize_board!
    # White Pieces
    (0..7).each do |i|
      Pawn.create(
        game_id: id,
        x_position: i,
        y_position: 1,
        color: true
        )
    end

    Rook.create(game_id: id, x_position: 0, y_position: 0, color: true)
    Rook.create(game_id: id, x_position: 7, y_position: 0, color: true)

    Knight.create(game_id: id, x_position: 1, y_position: 0, color: true)
    Knight.create(game_id: id, x_position: 6, y_position: 0, color: true)

    Bishop.create(game_id: id, x_position: 2, y_position: 0, color: true)
    Bishop.create(game_id: id, x_position: 5, y_position: 0, color: true)

    Queen.create(game_id: id, x_position: 3, y_position: 0, color: true)
    King.create(game_id: id, x_position: 4, y_position: 0, color: true)

    # Black Pieces
    (0..7).each do |i|
      Pawn.create(
        game_id: id,
        x_position: i,
        y_position: 6,
        color: false
        )
    end

    Rook.create(game_id: id, x_position: 0, y_position: 7, color: false)
    Rook.create(game_id: id, x_position: 7, y_position: 7, color: false)

    Knight.create(game_id: id, x_position: 1, y_position: 7, color: false)
    Knight.create(game_id: id, x_position: 6, y_position: 7, color: false)

    Bishop.create(game_id: id, x_position: 2, y_position: 7, color: false)
    Bishop.create(game_id: id, x_position: 5, y_position: 7, color: false)

    Queen.create(game_id: id, x_position: 3, y_position: 7, color: false)
    King.create(game_id: id, x_position: 4, y_position: 7, color: false)
  end
end

Step 6. Legal Move Logic For Each Piece

We added each piece type to our trello board. I volunteered to start with the pawn's legal move logic so we would have a good set of design patterns and logic for crafting the other pieces moving forward.

This is where I really started to grow as a developer. I pair programmed with team mates, talked best practices and reviewed code with my mentor. I finally got into a flow with the TDD 'red-green-refactor' cycle.
pawn.rb excerpt:
class Pawn < Piece
  def legal_move?(x, y)
    return false if backwards_move? y
    return false if horizontal_move? x

    proper_length? y
  end

  private

  def horizontal_move?(x)
    x_diff = (x_position - x).abs
    x_diff != 0
  end

  def backwards_move?(y)
    color ? y_position > y : y_position < y
  end

  def first_move?(_y)
    (y_position == 1 && color) || (y_position == 6 && !color)
  end

  def proper_length?(y)
    (y - y_position).abs == 1
  end
end
In refactoring the code I wrote with my mentor Bob Breznak, I really started to appreciate the nature of the Ruby language. The implicit return of the last statement within a method, the terse and human-readable syntax, the simple variable declarations— an appreciation for Ruby was growing.

Step 7. Games Controller Logic

While I was working on laying out the legal move logic for the pawn, Aaron was digging in the trenches of our games controller. We often worked together to review and refactor each other's work. There's nothing better than a second pair of eyes while you're trying to create something in code.

Here are a few examples of some controller code we worked out. There's nothing fancy here but we're proud of the quality of the code that we wrote:

games_controller.rb
class GamesController < ApplicationController
  before_action :authenticate_player!
  helper_method :game

  def new
    @game = Game.new
  end

  def create
    @game = Game.create(game_params)
    redirect_to game_path(@game)
  end

  def show
    unless game.present?
      return redirect_to dashboard_path
    end
  end

  def index
    redirect_to dashboard_path
  end

  def update
    if game.valid? && unique_players?
      game.update_attributes game_params
      game.assign_pieces
      return redirect_to game_path game
    end

    render :new, status: :unprocessable_entity
  end

  private

  def game
    @game ||= Game.where(id: params[:id]).last
  end

  def game_params
    params.require(:game).permit(
      :name,
      :white_player_id,
      :black_player_id)
  end

  def unique_players?
    @game.white_player_id != game_params[:black_player_id].to_i
  end
end

Step 8. Front-end Javascript

Aaron created the initial front-end javascript for allowing a player to select and move the piece. We incorporated some basic logic checks on the front-end to reduce database queries, but most of the logic was done after rails received the request.

I went through and refactored the javascript as a learning practice since I had never approached this level of complexity in Javascript.

gameboard.js excerpt:
$(document).ready(function () {

  // Selecting & Moving Pieces
  $('#gameboard td').click(function() {
    $this = $(this);
    // Returns boolean to determine piece is selected or not
    var pieceIsSelected = $('#gameboard td').hasClass('selected');

    if (pieceIsSelected){
      // Returns jQuery object of selectedPiece if a piece is selected
      var $piece = selectedPiece();

      if ($piece.data('piece-id') == $this.data('piece-id')) {
        deselectPiece( $this );
      } else {
          sendMove ($piece, $this);
        }
    } else {
      selectPiece($this);
    }

  });

  function selectedPiece() {
    return $('#gameboard td.selected');
  }

  function sendMove ($piece, $destination) {
    var piece = {
      id: $piece.data('piece-id'),
      x_position: $destination.data('x-position'),
      y_position: $destination.data('y-position')
    }

    if ( isPawnPromotion($piece, piece.y_position)) {
      openModal('#promo-modal', function(pieceType) {
        piece.type = pieceType;
        submitPieceUpdate(piece);
      });

    } else {
      submitPieceUpdate(piece);
    }
  }

  function submitPieceUpdate(piece) {
    $.ajax({
      type: 'PATCH',
      url: '/pieces/' + piece.id,
      dataType: 'json',
      data: { 
        piece: piece
      },
      success: function(data) {
        $(location).attr('href', data.update_url);
      }
    });
  }

  function selectPiece($piece) {
    var isPlayersTurn = $('#gameboard').data('your-turn');

    if (isPlayersTurn) {
      $piece.addClass('selected');
    }
  }

  function deselectPiece($piece) {
    $piece.removeClass('selected');
  }
});

Step 9. Allowing Pieces to Capture Opponents

piece.rb excerpt:
class Piece < ActiveRecord::Base
  def capture_move?(x, y)
    captured_piece = game.obstruction(x, y)
    captured_piece && captured_piece.color != color
  end

  def can_be_captured?
    opponents = game.pieces_remaining(!color)
    opponents.each do |opponent|
      if opponent.valid_move?(x_position, y_position)
        return true
      end
    end
    false
  end
end

Step 10. Implementing Check and Checkmate

Now that pieces could move and capture opponents according to the rules of chess, we needed to implement the most complex logic: check and checkmate.

game.rb excerpt:
def check?(color)
  king = pieces.find_by(type: 'King', color: color)
  opponents = pieces_remaining(!color)

  opponents.each do |piece|
    if piece.valid_move?(king.x_position, king.y_position)
      @piece_causing_check = piece
      return true
    end
  end
  false
end

def checkmate?(color)
  checked_king = pieces.find_by(type: 'King', color: color)

  return false unless check? color
  return false if @piece_causing_check.can_be_captured?
  return false if checked_king.can_move_out_of_check?
  return false if @piece_causing_check.can_be_blocked? checked_king

  true
end

Step 11. Branding & Building A Landing Page

While we were finishing up the check and checkmate methods, I went ahead and built the landing page to showcase our code samples and show off the app we built. After playing with many different typeface combinations I settled with Adelle Sans 700 uppercase for headings and Brandon Grotesque 300 for subheadings, paragraphs and UI elements.

landing page I coded for our chess app on the firehose project

I decided to use Flexbox to create equal-height boxes for our code samples:

.code-box-container {
  @include display(flex);
  @include justify-content(center);
  @include align-items(stretch);
  @include flex-wrap(wrap);
  clear: both;
}

.code-example, .code-description {
  @include transition (all 0.2s ease-in-out);
  margin: 0;
  flex: 2 2 36.25em;
  @include align-self(stretch);
}
code samples from our chess app for the firehose project

Step 12. Styling The UI

Using SVG's and some minimal styling, I polished the UI to give it a bit of a 'real app' look and feel.

the gameboard I styled for sprky — the firehose project

Step 13. Enabling Pawn Promotion

Last on our Trello board was pawn promotion and En Passant. The pawn promotion consisted of back-end logic to verify whether the conditions for pawn promotion was met, and a front-end to enable the player to choose their piece type (queen, rook, knight, bishop).

pawn.rb excerpt:
class Pawn < Piece
  def can_promote?(y)
    y == 7 && color || y == 0 && !color
  end

  def promotion(params)
    x = params[:x_position].to_i
    y = params[:y_position].to_i
    type = params[:type]

    update_attributes(
      x_position: nil,
      y_position: nil,
      state: 'off-board')
    game.pieces.create(
      x_position: x,
      y_position: y,
      type: type,
      state: 'moved',
      color: color,
      player_id: player_id
      )
  end
end
gameboard.js excerpt:
function sendMove ($piece, $destination) {
  var piece = {
    id: $piece.data('piece-id'),
    x_position: $destination.data('x-position'),
    y_position: $destination.data('y-position')
  }

  if ( isPawnPromotion($piece, piece.y_position)) {
    openModal('#promo-modal', function(pieceType) {
      piece.type = pieceType;
      submitPieceUpdate(piece);
    });

  } else {
    submitPieceUpdate(piece);
  }
}

Step 14. Production Testing

We found in playing a few lives games that there were a few bugs that got through our test suite—most of which were centered around the checkmate logic. Aaron was a champ and dug through the bugs and fixed checkmate such that our chess game was fully functional.

The Outcome

I learned how to craft logic in code, how to write good tests that covered edge cases, and most importantly how to collaborate with a remote team. While I enjoyed writing Ruby and learning how to solve problems in code, I realized that I don't enjoy the programming-side of product development as much as I enjoy the human-side.

The Firehose Project was an awesome experience and I can't recommend their bootcamp more highly.

Press

The Huffington Post picked up an article where Firehose Project founder Ken Mazaika featured our chess app: huffingtonpost.com/ken-mazaika/when-the-heck-did-learning-to-code-become-cool

Want to Work Together?

Contact Me