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>