Práctica: TicTacToe

El código que sigue implanta un jugador de tres-en-raya.

  1. Mejore el estilo actual usando SAAS: utilice variables, extensiones, mixins ...
  2. Despliegue su versión en Heroku

Referencias

  1. http://sytw-tresenraya.herokuapp.com/
  2. https://github.com/crguezl/tictactoe-1
  3. Sass (Syntactically Awesome StyleSheets): Sass Basics
  4. Un TicTacToe Simple (No una webapp)

Estructura

[~/sinatra/sinatra-tictactoe/sinatra-tictactoe-ajax(master)]$ tree
.
|--- Gemfile
|--- Gemfile.lock
|--- Procfile
|--- Rakefile
|--- Readme.md
|--- app.rb
|--- public
|   |--- css
|   |   |--- app.css
|   |   `--- style.css
|   |--- images
|   |   |--- blackboard.jpg
|   |   |--- circle.gif
|   |   `--- cross.gif
|   `--- js
|       `--- app.js
`--- views
    |--- final.erb
    |--- final.haml
    |--- game.erb
    |--- game.haml
    |--- layout.erb
    |--- layout.haml
    `--- styles.scss

5 directories, 19 files

Rakefile

[~/sinatra/sinatra-tictactoe/sinatra-tictactoe-ajax(master)]$ cat Rakefile 
desc "run server"
task :default do
  sh "bundle exec ruby app.rb"
end

desc "install dependencies"
task :install do
  sh "bundle install"
end

###
desc 'build css'
task :css do
  sh "sass views/styles.scss public/css/style.css"
end

HAML

  1. game.haml en GitHub
  2. layout.haml

[~/sinatra/sinatra-tictactoe/sinatra-tictactoe-ajax(master)]$  cat views/game.haml 
.screen
  .gameboard
    - HORIZONTALS.each do |row| 
      .gamerow
        - row.each do |p|
          %a(href=p)
            %div{:id => "#{p}", :class => "cell #{b[p]}"}
    .message
      %h1= m

[~/sinatra/sinatra-tictactoe/sinatra-tictactoe-ajax(master)]$ cat views/layout.haml 
!!!
%html
  %head
    %title tic tac toe
    -#%link{:rel=>"stylesheet", :href=>"/css/app.css", :type=>"text/css"}
    -# dynamically accessed
    -#%link{:rel=>"stylesheet", :href=>"/styles.css", :type=>"text/css"}    
    -# statically compiled
    %link{:rel=>"stylesheet", :href=>"css/style.css", :type=>"text/css"} 
    %script{:type=>"text/javascript", :src=>"http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"}
    %script{:type=>"text/javascript", :src=>"/js/app.js"}
  %body
    = yield

  1. El fuente styles.scss puede compilarse dinámicamente. Véase el fragmento de código que empieza por get '/styles.css' do en app.rb
  2. O puede compilarse estáticamente. Véase el Rakefile

HTML generado

<!DOCTYPE html>
<html>
  <head>
    <title>tic tac toe</title>
    <link href='css/style.css' rel='stylesheet' type='text/css'>
    <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js' type='text/javascript'></script>
    <script src='/js/app.js' type='text/javascript'></script>
  </head>
  <body>
    <div class='screen'>
      <div class='gameboard'>
        <div class='gamerow'>
          <a href='a1'>
            <div class='cell ' id='a1'></div>
          </a>
          <a href='a2'>
            <div class='cell ' id='a2'></div>
          </a>
          <a href='a3'>
            <div class='cell ' id='a3'></div>
          </a>
        </div>
        <div class='gamerow'>
          <a href='b1'>
            <div class='cell ' id='b1'></div>
          </a>
          <a href='b2'>
            <div class='cell circle' id='b2'></div>
          </a>
          <a href='b3'>
            <div class='cell ' id='b3'></div>
          </a>
        </div>
        <div class='gamerow'>
          <a href='c1'>
            <div class='cell ' id='c1'></div>
          </a>
          <a href='c2'>
            <div class='cell ' id='c2'></div>
          </a>
          <a href='c3'>
            <div class='cell cross' id='c3'></div>
          </a>
        </div>
        <div class='message'>
          <h1></h1>
        </div>
      </div>
    </div>
  </body>
</html>

SASS

  1. styles.scss
  2. Sass (Syntactically Awesome StyleSheets): Sass Basics
  3. SASS documentación
  4. sass man page
  5. SASS (Syntactically Awesome StyleSheets) 96

~/sinatra/sinatra-tictactoe/sinatra-tictactoe-ajax(master)]$ cat views/styles.scss 
$red:   #903;
$black: #444;
$white: #fff;
$ull:   #9900FF;
$pink:  #F9A7B0;

$main-font: Helvetica, Arial, sans-serif;
$message-font: 22px/1;

$board-left: 300px;
$board-margin: 0 auto;
$board-size: 500px;

$opacity:  0.8;

$cell-width:    $board-size/8.5;
$cell-height:   $board-size/8.5;
$cell-margin:  $cell-width/12;
$cell-padding:  $cell-width/1.3;

$background: "/images/blackboard.jpg";
$cross:      "/images/cross.gif";
$circle:     "/images/circle.gif";

body       { 
             // background-color: lightgrey; 
             font-family: $main-font;
             background: url($background) repeat; background-size: cover; 
           }
.gameboard { //margin-left: $board-left; 
             width: $board-size;
             margin: $board-margin;
             text-align:center;
           }
.gamerow   { clear: both; }
.cell      { color: blue; 
             background-color: white; 
             opacity: $opacity;
             width: $cell-width; 
             height: $cell-height; 
             margin: $cell-margin; 
             padding: $cell-padding; 
             &:hover {
               color: black ;
               background-color: $ull;
             }
             float: left; 
           }

@mixin game-piece($image) {
  background: url($image)  no-repeat; background-size: cover; 
}

.cross     { @include game-piece($cross); }
.circle    { @include game-piece($circle); }

.base-font { color: $pink; font: $message-font $main-font; }

.message   { 
             @extend .base-font;
             display: inline;
             background-color:transparent;
           }

Procfile

Procfile en GitHub

In order to declare the processes that make our app, and scale them individually, we need to be able to tell Heroku what these processes are.

The Procfile is a simple YAML file which sits in the root of your application code and is pushed to your application when you deploy. This file contains a definition of every process you require in your application, and how that process should be started.

[~/sinatra/sinatra-tictactoe/sinatra-tictactoe-ajax(master)]$ cat Procfile 
#web: bundle exec unicorn -p $PORT -E $RACK_ENV
#web: bundle exec ruby app.rb -p $PORT
web: bundle exec ruby app.rb 
#web: bundle exec thin start
Véase The Procfile is your friend

  1. Heroku, Thin and everything in between en StackOverflow
  2. Process Types and the Procfile en Heroku

Gemfile

[~/sinatra/sinatra-tictactoe/sinatra-tictactoe-ajax(master)]$ cat Gemfile
source "https://rubygems.org"

gem "sinatra"
gem 'haml'
gem "sass", :require => 'sass'
gem 'thin'

La Aplicación

[~/sinatra/sinatra-tictactoe/sinatra-tictactoe-ajax(master)]$  cat app.rb 
require 'sinatra'
require 'sass'
require 'pp'

settings.port = ENV['PORT'] || 4567
enable :sessions
#use Rack::Session::Pool, :expire_after => 2592000
#set :session_secret, 'super secret'

#configure :development, :test do
#  set :sessions, :domain => 'example.com'
#end

#configure :production do
#  set :sessions, :domain => 'herokuapp.com'
#end

module TicTacToe
  HUMAN = CIRCLE = "circle" # human
  COMPUTER = CROSS  = "cross"  # computer
  BLANK  = ""

  HORIZONTALS = [ %w{a1 a2 a3},  %w{b1 b2 b3}, %w{c1 c2 c3} ]
  COLUMNS     = [ %w{a1 b1 c1},  %w{a2 b2 c2}, %w{a3 b3 c3} ]
  DIAGONALS   = [ %w{a1 b2 c3},  %w{a3 b2 c1} ]
  ROWS = HORIZONTALS + COLUMNS + DIAGONALS
  MOVES       = %w{a1    a2   a3   b1   b2   b3   c1   c2   c3}

  def number_of(symbol, row)
    row.find_all{ |s| session["bs"][s] == symbol }.size 
  end

  def inicializa
    @board = {}
    MOVES.each do |k|
      @board[k] = BLANK
    end
    @board
  end

  def board
    session["bs"]
  end

  def [] key
    board[key]
  end

  def []= key, value
    board[key] = value
  end

  def each 
    MOVES.each do |move|
      yield move
    end
  end

  def legal_moves
    m = []
    MOVES.each do |key|
      m << key if board[key] == BLANK
    end
    puts "legal_moves: Tablero:  #{board.inspect}"
    puts "legal_moves: m:  #{m}"
    m # returns the set of feasible moves [ "b3", "c2", ... ]
  end

  def winner
    ROWS.each do |row|
      circles = number_of(CIRCLE, row)  
      puts "winner: circles=#{circles}"
      return CIRCLE if circles == 3  # "circle" wins
      crosses = number_of(CROSS, row)   
      puts "winner: crosses=#{crosses}"
      return CROSS  if crosses == 3
    end
    false
  end

  def smart_move
    moves = legal_moves

    ROWS.each do |row|
      if (number_of(BLANK, row) == 1) then
        if (number_of(CROSS, row) == 2) then # If I have a win, take it.  
          row.each do |e|
            return e if board[e] == BLANK
          end
        end
      end
    end
    ROWS.each do |row|
      if (number_of(BLANK, row) == 1) then
        if (number_of(CIRCLE,row) == 2) then # If he is threatening to win, stop it.
          row.each do |e|
            return e if board[e] == BLANK
          end
        end
      end
    end

    # Take the center if open.
    return "b2" if moves.include? "b2"

    # Defend opposite corners.
    if    self["a1"] != COMPUTER and self["a1"] != BLANK and self["c3"] == BLANK
      return "c3"
    elsif self["c3"] != COMPUTER and self["c3"] != BLANK and self["a1"] == BLANK
      return "a1"
    elsif self["a3"] != COMPUTER and self["a3"] != BLANK and self["c1"] == BLANK
      return "c1"
    elsif self["c1"] != COMPUTER and self["c3"] != BLANK and self["a3"] == BLANK
      return "a3"
    end
    
    # Or make a random move.
    moves[rand(moves.size)]
  end

  def human_wins?
    winner == HUMAN
  end

  def computer_wins?
    winner == COMPUTER
  end
end

helpers TicTacToe

get %r{^/([abc][123])?$} do |human|
  if human then
    puts "You played: #{human}!"
    puts "session: "
    pp session
    if legal_moves.include? human
      board[human] = TicTacToe::CIRCLE
      # computer = board.legal_moves.sample
      computer = smart_move
      redirect to ('/humanwins') if human_wins?
      redirect to('/') unless computer
      board[computer] = TicTacToe::CROSS
      puts "I played: #{computer}!"
      puts "Tablero:  #{board.inspect}"
      redirect to ('/computerwins') if computer_wins?
    end
  else
    session["bs"] = inicializa()
    puts "session = "
    pp session
  end
  haml :game, :locals => { :b => board, :m => ''  }
end

get '/humanwins' do
  puts "/humanwins session="
  pp session
  begin
    m = if human_wins? then
          'Human wins'
        else 
          redirect '/'
        end
    haml :final, :locals => { :b => board, :m => m }
  rescue
    redirect '/'
  end
end

get '/computerwins' do
  puts "/computerwins"
  pp session
  begin
    m = if computer_wins? then
          'Computer wins'
        else 
          redirect '/'
        end
    haml :final, :locals => { :b => board, :m => m }
  rescue
    redirect '/'
  end
end

not_found do
  puts "not found!!!!!!!!!!!"
  session["bs"] = inicializa()
  haml :game, :locals => { :b => board, :m => 'Let us start a new game'  }
end

get '/styles.css' do
  scss :styles
end



Subsecciones
Casiano Rodriguez León 2015-01-07