Archive for marzo 2012

El juego de gato en Ruby ahora para Web

marzo 20, 2012

En un post anterior (https://salomonrt.wordpress.com/2010/04/03/juego-de-gato-%C2%BFinvensible-en-ruby/) puse el código fuente del juego de gato en Ruby, dicho programa hay que ejecutarlo desde la terminal. Decidí hacer una versión modificada de ese código para que funcionara desde un navegador web (para lo cual utilicé Sinatra).

Aquí les dejo el código.

Programa principal

Archivo gatoweb.rb

#       Copyright 2012 SALOMON RINCON TORRES <rtmex@yahoo.com>
#
#       This program is free software; you can redistribute it and/or modify
#       it under the terms of the GNU General Public License as published by
#       the Free Software Foundation; either version 2 of the License, or
#       (at your option) any later version.
#       
#       This program is distributed in the hope that it will be useful,
#       but WITHOUT ANY WARRANTY; without even the implied warranty of
#       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#       GNU General Public License for more details.
#       
#       You should have received a copy of the GNU General Public License
#       along with this program; if not, write to the Free Software
#       Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#       MA 02110-1301, USA.

#encoding: utf-8
require 'sinatra'

opcion_programa = 0

# Inicializa las variablee globales
$mensaje = nil
$encabezado = "Juego de gato hecho en Ruby con Sinatra"
$tablero = Array.new(3){Array.new(3)}
$casillas_disponibles = (1..9).to_a
$gana_programa = false
$caracter_usuario = nil
$caracter_prog = nil

def inicializa
    $gana_programa = false
    # Inicializa el tablero
    for i in 0..2 do
        for j in 0..2 do
            $tablero[j][i]=nil
        end
    end
    $casillas_disponibles.clear
    for i in 1..9 do
        $casillas_disponibles.push(i)
    end
end

# Tira al azar en una esquina disponible
def tira_esquina
    opcion = 0
    esquina_seleccionada = 0
    # Tira al azar en una esquina, se basa en cuatro opciones:
    # opcion 1 será la esquina superior izquierda
    # opcion 2 será la esquina superior derecha
    # opcion 3 será la esquina inferior izquierda
    # opcion 4 será la esquina inferior derecha
    while not $casillas_disponibles.include?(esquina_seleccionada)
          opcion = rand(4)+1
          case opcion
               when 1 then esquina_seleccionada = 1
               when 2 then esquina_seleccionada = 3
               when 3 then esquina_seleccionada = 7
               when 4 then esquina_seleccionada = 9
          end
    end

    return esquina_seleccionada
end

# Verifica en que casilla debe tirar quién este jugando con el caracter que recibe como parámetro
# para ganar horizontalmente
def gana_horizontal(caracter)
    tiros_hechos = 0
    contador = 0
    columna = -1
    fila = 0
    ganador_en = 0

    # El siguiente ciclo termina cuando se hayan recorrido todos los renglónes o se haya encontrado
    # un renglón en donde se han hecho dos tiros
    while contador <= 2 and tiros_hechos < 2
        for j in 0..2 do
            if $tablero[j][contador]==caracter then
               tiros_hechos = tiros_hechos+1
            elsif $tablero[j][contador]==nil
                  columna = j
            end
        end
        # Si en el renglón actual había dos tiros y una casilla vacía, ahi se debe tirar para ganar
        if tiros_hechos == 2 and columna >= 0 then
           fila = contador
        else
            tiros_hechos = 0
            columna = -1
            contador = contador + 1
        end
    end

    if tiros_hechos == 2 then
       case fila
            when 0 then
                   # Significa que se encontró un tiro ganador en el primer renglón
                   ganador_en = columna + 1
            when 1 then
                   # Significa que se encontró un tiro ganador en el segundo renglón
                   ganador_en = columna + 4
            when 2 then
                   # Significa que se encontró un tiro ganador en el tercer renglón
                   ganador_en = columna + 7
       end
    end

    return ganador_en
end

# Verifica en que casilla debe tirar quién este jugando con el caracter que recibe como parámetro
# para ganar verticalmente
def gana_vertical(caracter)
    tiros_hechos = 0
    contador = 0
    columna = 0
    fila = -1
    ganador_en = 0

    # El siguiente ciclo termina cuando se hayan recorrido todos los renglónes o se haya encontrado
    # un renglón en donde se han hecho dos tiros
    while contador <= 2 and tiros_hechos < 2
        for j in 0..2 do
            if $tablero[contador][j]==caracter then
               tiros_hechos = tiros_hechos+1
            elsif $tablero[contador][j]==nil
                  fila = j
            end
        end
        # Si en la columna actual había dos tiros y una casilla vacía, ahi se debe tirar para ganar
        if tiros_hechos == 2 and fila >= 0 then
           columna = contador
        else
            tiros_hechos = 0
            fila = -1
            contador = contador + 1
        end
    end

    if tiros_hechos == 2 then
       case fila
            when 0 then
                   # Significa que se encontró un tiro ganador en el primer renglón
                   ganador_en = columna + 1
            when 1 then
                   # Significa que se encontró un tiro ganador en el segundo renglón
                   ganador_en = columna + 4
            when 2 then
                   # Significa que se encontró un tiro ganador en el tercer renglón
                   ganador_en = columna + 7
       end
    end

    return ganador_en
end

# Verifica en que casilla debe tirar quién este jugando con el caracter que recibe como parámetro
# para ganar diagonalmente
def gana_diagonal(caracter)
    tirosd1 = 0
    tirosd2 = 0
    contador = 0
    fila1 = -1
    fila2 = -1
    ganador_en = 0
    diagonal_1 = [0,1,2]
    diagonal_2 = [2,1,0]

    # El siguiente ciclo termina cuando se hayan recorrido todos los renglónes o se haya encontrado
    # que se han hecho dos tiros en una de las 2 diagonales diagonal
    while contador <= 2
          # Checa si hay un tiro a la casilla correspondiente en la diagonal 1
          if $tablero[diagonal_1.at(contador)][contador]==caracter then
             tirosd1 = tirosd1+1
          elsif $tablero[diagonal_1.at(contador)][contador]==nil
                fila1 = contador
          end

          # Checa si hay un tiro a la casilla correspondiente en la diagonal 2
          if $tablero[diagonal_2.at(contador)][contador]==caracter then
             tirosd2 = tirosd2+1
          elsif $tablero[diagonal_2.at(contador)][contador]==nil
                fila2 = contador
          end

          contador = contador+1
    end

    if tirosd1 == 2 and fila1>=0 then
       # Significa que se encontró el tiro ganador en la diagonal 1
       case fila1
            when 0 then
                   # Significa que se encontró el tiro ganador en el primer renglón
                   ganador_en = 1
            when 1 then
                   # Significa que se encontró el tiro ganador en el segundo renglón
                   ganador_en = 5
            when 2 then
                   # Significa que se encontró el tiro ganador en el tercer renglón
                   ganador_en = 9
       end
    elsif tirosd2 == 2 and fila2>=0 then
          # Significa que se encontró el tiro ganador en la diagonal 2
          case fila2
               when 0 then
                    # Significa que se encontró el tiro ganador en el primer renglón
                    ganador_en = 3
               when 1 then
                    # Significa que se encontró el tiro ganador en el segundo renglón
                    ganador_en = 5
               when 2 then
                    # Significa que se encontró el tiro ganador en el tercer renglón
                    ganador_en = 7
          end
    end

    return ganador_en
end

def renglon_tirou
    conta = 0
    renglones = Array.new

    for i in 0..2 do
        conta = 0
        for j in 0..2 do
            if $tablero[j][i]==$caracter_usuario then
               conta = conta+1
            end
        end
        if conta > 0 then
           renglones << i
        end
    end

    return renglones
end

# Ejecuta el tiro del programa
def tiro_programa
    veces_tiradas = 0
    espacios = 0
    renglon_seleccionado = -1
    columna = -1
    tiro = 0
    tiros_en_renglon = 0
    tiros_en_columna = 0
    tiros_usario = 0
    esquina_opuesta_ocupada = 1
    # Crea un array con las 4 esquinas en que puede intentar tirar
    esquinas_intentadas = [1,3,7,9]
    # Elimina del array las esquinas que no están disponibles
    esquinas_intentadas.delete_if {|elemento| not $casillas_disponibles.include?(elemento)}

    # Verifica cuántas veces ha tirado el programa
    for i in 0..2 do
        for j in 0..2 do
            if $tablero[j][i]==$caracter_prog then
               veces_tiradas = veces_tiradas+1
            end
        end
    end

    if veces_tiradas == 0 then
       # Primero intenta tirar al centro
       if $casillas_disponibles.include?(5) then
          tiro = 5
       else
           # Tira al azar en una esquina
           tiro = tira_esquina
       end
    else
        # Verifica si hay una casilla en la cual el programa gana horizontalmente y si es asi tira en ella
        tiro = gana_horizontal($caracter_prog)
        if tiro == 0 then
           # Significa que el programa no gana horizontalmente tirando en alguna casilla
           # Verifica si hay una casilla en la cual el programa gana verticalmente y si es asi tira en ella
           tiro = gana_vertical($caracter_prog)
           if tiro == 0 then
              # Significa que el programa no gana verticalmente tirando en alguna casilla
              # Verifica si hay una casilla en la cual el programa gana diagonalmente y si es asi tira en ella
              tiro = gana_diagonal($caracter_prog)
              if tiro == 0 then
                 # Significa que el programa no gana diagonalmente tirando en alguna casilla
                 # Entónces checa si el usuario gana diagonalmente tirando en alguna casilla y si es asi lo bloquea
                 tiro = gana_diagonal($caracter_usuario)
                 if tiro == 0
                    # Significa que el usuario no gana diagonalmente tirando en alguna casilla
                    # Entónces checa si el usuario gana horizontalmente tirando en alguna casilla y si es asi lo bloquea
                    tiro = gana_horizontal($caracter_usuario)
                    if tiro == 0 then
                       # Significa que el usuario no gana horizontalmente tirando en alguna casilla
                       # Entónces checa si el usuario gana verticalmente tirando en alguna casilla y si es asi lo bloquea
                       tiro = gana_vertical($caracter_usuario)
                       if tiro == 0 then
                          # Significa que el usuario no gana verticalmente tirando en alguna casilla
                          # Si todas las esquinas están disponibles, tira en alguna de ellas
                          if esquinas_intentadas.length==4 then
                             tiro = tira_esquina
                          end
                          # Si hay 3 esquinas disponibles,
                          # busca tirar en una esquina correspondiente a un renglón y columna
                          # en donde el usuario ya había tirado y que la esquina opuesta esté vacía
                          if esquinas_intentadas.length==3 and $casillas_disponibles.count<7 then
                             begin
                                  tiro = tira_esquina
                                  case tiro
                                       when 1,3 then
                                            if renglon_tirou.include?(0) then
                                               if tiro == 1 then
                                                  if $casillas_disponibles.include?(9) and $tablero[0].index($caracter_usuario) then
                                                     renglon_seleccionado=0
                                                  end
                                               else
                                                   if $casillas_disponibles.include?(7) and $tablero[2].index($caracter_usuario) then
                                                      renglon_seleccionado=0
                                                   end
                                               end
                                            end
                                       when 7,9 then
                                            if renglon_tirou.include?(2) then
                                               if tiro == 7 then
                                                  if $casillas_disponibles.include?(3) and $tablero[0].index($caracter_usuario) then
                                                     renglon_seleccionado=2
                                                  end
                                               else
                                                   if $casillas_disponibles.include?(1) and $tablero[2].index($caracter_usuario) then
                                                      renglon_seleccionado=2
                                                   end
                                               end
                                            end
                                  end
                             end until renglon_seleccionado>=0 
                          elsif tiro==0 then
                                # Entónces busca tirar en el renglón en donde el programa haya tirado el mayor número de veces
                                # o el renglón con el mayor número de casillas disponibles
                                casillas_disponibles = []
                                tiros_programa = []
                                max_disponibles = nil
                                max_tiros_programa = nil
                                 
                                # Guarda en el arreglo casillas_disponibles, el número de casillas disponibles
                                # para cada renglón, primero el número de casillas disponibles para el renglón 0
                                # después el número de casillas disponibles para el renglón 1 y al final
                                # el número de casillas disponibles para el renglón 2
                                # Guarda en el arreglo tiros_programa, el número de veces que el programa ha tirado
                                # en cada renglón, primero el número de veces en el renglón 0
                                # después el número de veces en el renglón 1 y al final
                                # el número de veces en el renglón 2
                                for i in 0..2 do
                                    espacios = 0
                                    veces_tiradas = 0
                                    for j in 0..2 do
                                        case $tablero[j][i]
                                             when nil then espacios = espacios+1
                                             when $caracter_prog then veces_tiradas = veces_tiradas+1
                                        end
                                    end
                                    casillas_disponibles.push(espacios)
                                    tiros_programa.push(veces_tiradas)
                                end
                                
                                # Guarda en la variable nmax_casillas_disp el número máximo de casillas disponibles
                                nmax_casillas_disp = casillas_disponibles.max
                                # Guarda en la variable max_disponibles el renglón en donde hay más casillas disponibles
                                max_disponibles = casillas_disponibles.index(casillas_disponibles.max)
                                # Busca si hay otro renglón que tenga el mismo número de casillas disponibles
                                # y que además el programa ya haya tirado en ese renglón previamente
                                max_disponibles_preferido = nil
                                for i in 0..2 do
                                    if casillas_disponibles[i] == nmax_casillas_disp then
                                       for j in 0..2 do
                                           if $tablero[j][i] == $caracter_prog then
                                              max_disponibles_preferido = i
                                              break
                                           end
                                       end
                                    end
                                end
                                
                                # Guarda en la variable max_tiros_programa el renglón en donde el programa ha tirado más veces
                                max_tiros_programa = tiros_programa.index(tiros_programa.max)
                                
                                if max_disponibles_preferido then
                                   renglon_seleccionado = max_disponibles_preferido
                                   for i in 0..2 do
                                       if $tablero[i][renglon_seleccionado] == nil then
                                          columna = i
                                          break
                                       end
                                   end
                                else
                                    # Entónces busca tirar en la columna en donde el programa haya tirado el mayor número de veces
                                    # o la columna con el mayor número de casillas disponibles
                                    casillas_disponibles = []
                                    tiros_programa = []
                                    max_disponibles = nil
                                    max_tiros_programa = nil
                                    
                                    # Guarda en el arreglo casillas_disponibles, el número de casillas disponibles
                                    # para cada columna, primero el número de casillas disponibles para la columna 0
                                    # después el número de casillas disponibles para la columna 1 y al final
                                    # el número de casillas disponibles para la columna 2
                                    for i in 0..2 do
                                        casillas_disponibles.push($tablero[i].count(nil))
                                    end
                                    # Guarda en la variable max_disponibles la columna en donde hay más casillas disponibles
                                    max_disponibles = casillas_disponibles.index(casillas_disponibles.max)
                                    
                                    # Guarda en el arreglo tiros_programa, el número de veces que el programa ha tirado
                                    # en cada columna, primero el número de veces en la columna 0
                                    # después el número de veces en la columna 1 y al final
                                    # el número de veces en la columna 2
                                    for i in 0..2 do
                                        tiros_programa.push($tablero[i].count($caracter_prog))
                                    end
                                    # Guarda en la variable max_tiros_programa la columna en donde el programa ha tirado más veces
                                    max_tiros_programa = tiros_programa.index(tiros_programa.max)
                                    
                                    if $tablero[max_tiros_programa].index(nil) and tiros_programa.uniq.count>1 then
                                       # Selecciona la columna en donde el programa ha tirado más veces
                                       columna = max_tiros_programa
                                    else
                                        # Como en todas las columnas el programa ha tirado igual número de veces
                                        # selecciona la columna con mas casillas disponibles
                                        columna = max_disponibles
                                    end                                 
                                    
                                    renglon_seleccionado = $tablero[columna].index(nil)
                                end
                                
                                case renglon_seleccionado
                                     when 0 then tiro = columna+1
                                     when 1 then tiro = columna+4
                                     when 2 then tiro = columna+7
                                end
                          end
                       end
                    end
                 end
              else
                  $gana_programa = true
              end
           else
               $gana_programa = true
           end
        else
            $gana_programa = true
        end
    end
    return tiro
end

# Procesa el tiro del programa
def procesa_tiro
    opcion_programa = tiro_programa
    case opcion_programa
         when 1 then $tablero[0][0] = $caracter_prog
         when 2 then $tablero[1][0] = $caracter_prog
         when 3 then $tablero[2][0] = $caracter_prog
         when 4 then $tablero[0][1] = $caracter_prog
         when 5 then $tablero[1][1] = $caracter_prog
         when 6 then $tablero[2][1] = $caracter_prog
         when 7 then $tablero[0][2] = $caracter_prog
         when 8 then $tablero[1][2] = $caracter_prog
         when 9 then $tablero[2][2] = $caracter_prog
    end
    # Elimina del array casillas_disponibles la posición en donde tiró el programa
    $casillas_disponibles.delete(opcion_programa)
    redirect '/imprime_tablero'
end

# menú principal
get '/' do
  inicializa
  erb :index
end

# permite al usuario seleccionar el caracter con el que va a tirar
# e indicar quién tira primero
post '/configura' do
  case params[:CaracterUsuario]
       when "X"
            $caracter_usuario = "X"
            $caracter_prog = "O"
       when "O"
            $caracter_usuario = "O"
            $caracter_prog = "X"
  end
  if params[:IniciaUsuario]
     redirect '/imprime_tablero'
  else
      if not $casillas_disponibles.empty? then
         procesa_tiro
      end
  end
end

# muestra el tablero
get '/imprime_tablero' do
  erb :tablero
end

# procesa el tiro del usuario
get '/casilla/:numero' do
  case params[:numero].to_i
       when 1 then $tablero[0][0] = $caracter_usuario
       when 2 then $tablero[1][0] = $caracter_usuario
       when 3 then $tablero[2][0] = $caracter_usuario
       when 4 then $tablero[0][1] = $caracter_usuario
       when 5 then $tablero[1][1] = $caracter_usuario
       when 6 then $tablero[2][1] = $caracter_usuario
       when 7 then $tablero[0][2] = $caracter_usuario
       when 8 then $tablero[1][2] = $caracter_usuario
       when 9 then $tablero[2][2] = $caracter_usuario
  end
  # Elimina del array casillas_disponibles la posición en donde tiró el usuario
  $casillas_disponibles.delete(params[:numero].to_i)
  if $casillas_disponibles.empty?
     redirect '/imprime_tablero'
  else
      procesa_tiro
  end
end

Hoja de estilos

Archivo public/stylesheets/style.css


html
{
background: #fff;
font: normal 18px Arial, Tahoma, Helvetica, sans-serif;
margin: 0;
padding: 0;
height: 100%;
}

body
{
margin: 0;
min-height: 100%;
position: relative;
}

#principal
{
width: 800px;
margin-left: auto;
margin-right: auto;
margin-top: 0px;
margin-bottom: 0px;
padding: 0px;
padding-bottom: 90px;
}

#encabezado
{
width: 100%;
padding: 15px 0 0 45px;
height: 80px;
}

#contenido
{
width: 900px;
background-color: #FFFFFF;
margin-left: auto;
margin-right: auto;
margin-top: 25px;
margin-bottom: 0px;
}

#contenido table
{
font: normal 80px Arial, Tahoma, Helvetica, sans-serif;
margin: auto;
}

#contenido a
{
text-decoration:  none;
}

td.colcentro
{
border-left: 3px solid black;
border-right: 3px solid black;
}

td.rowcentro
{
border-top: 3px solid black;
border-bottom: 3px solid black;
}

.numero
{
text-align: center;
color: white;
}

h1, h2, h3, p
{
padding: 0;
margin: 0;
}

.division
{
margin: 25px 0;
height: 1px;
width: 900px;
border: 0;
background: black;
background: -webkit-gradient(linear, 0 0, 100% 0, from(white), to(white), color-stop(50%, black));
text-align: left;
}

Vistas

Archivo views/layout.erb


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Juego de gato</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="Juego de gato" />
<meta name="keywords" content="Gato, Sinatra, ruby, tic tac toe" />
<link href="/stylesheets/style.css" rel="stylesheet" type="text/css" />

</head>
<body>
<%= yield %>
</body>
</html>

Archivo views/index.erb

<div id="principal">
	<div id="encabezado"><h1><%= $encabezado %></h1></div>
		<p><h2>Inicio</h2>
	    <hr class="division" />
	    <div id="contenido">
	    <form action="/configura" method="post" accept-charset="utf-8">
		    <lable>Caracter con el que tira el usuario</label>
		    <input type="radio" name="CaracterUsuario" value="X" checked>X
		    <input type="radio" name="CaracterUsuario" value="O">O
		    <br>
		    <label>Tira primero el usuario</label><input type="checkbox" name="IniciaUsuario" id="IniciaUsuario" value="true"/>
			<br><br>
			<p>
			   <input type="submit" value="Aceptar"/>
			</p>  
		</form>
		</div>
</div>

Archivo views/tablero.erb

<div id="principal">
	<div id="encabezado"><h1><%= $encabezado %></h1></div>
	    <p><h2>Tablero</h2>
	    <hr class="division" />
	    <div id="contenido">
		    <table class="tablero">
                  <% num = 1
                     for i in 0..2 do %>
                         <tr>
                            <% for j in 0..2 do %>
                                <% case num 
                                        when 2, 8 %>
                                             <td class="colcentro">
                                     <% when 4, 6 %> 
                                             <td class="rowcentro">
                                     <% when 5 %>
                                             <td class="colcentro rowcentro">
                                     <% else %>
                                             <td>
                                <% end 
                                   if $tablero[j][i]==nil then
                                      if !$gana_programa then %>
                                         <a href="casilla/<%= num %>" class="numero"><%= num %></a>
                                   <% else %>
                                       <p class="numero"><%= num %></p>
                                   <% end
                                   else %>
                                       <%= $tablero[j][i] %>
                                <% end
                                   num = num+1 %>
                                   </td>
                            <% end %>
                         </tr>
                  <% end %>
		    </table>
		    <br>
		    <a href="/">Inicio</a>
		 <% if $casillas_disponibles.empty? or $gana_programa then
                       if $gana_programa then %>
                          <p>Gana el programa!</p>
                    <% else %>
                          <p>No hay ganador!</p>
                    <% end
               end %>
	</div>
</div>