Archive for the ‘Programación’ Category

Capturar texto en mayúsculas en un objeto Memo en C++ Builder

septiembre 30, 2017

En C++ Builder, los objetos “Edit” tienen una propiedad llamada CharCase, esta propiedad tiene por defualt el valor ecNormal. Si queremos que todo lo que se capture en ese Edit esté en mayúsculas, basta conque cambies el valor de su propiedad CharCase de ecNormal a ecUpperCase.

Los objetos “Memo” no tienen la propiedad CharCase (no entiendo porque); así que si deseamos que todo lo que se capture en ese Memo esté en mayúsculas, debemos poner el siguiente código en el evento OnKeyPress:

if (Key >= 97 && Key <= 122)
    Key = Key - 32;

Sabemos que el código ASCII de la letra “a” minúscula es 97, el de la “b” es 98 y así sucesivamente hasta llegar a la letra “z” minúscula que es 122. El código ASCII de la letra “A” mayúscula es 65, el de la “B” es 66 y así sucesivamente hasta llegar a la letra “Z” mayúscula que es 90.

Esto significa que si restamos al código ASCII de cualquier letra minúscula el número 32, obtenemos la misma letra pero en mayúsculas. Por eso si la tecla pulsada está entre 97 y 122, la convertimos al código ASCII resultado de restarle 32. Esto no funciona para las vocales con acento, pero la solución es agregar un switch para cada uno de esos casos. La función completa del evento OnKeyPress del objeto Memo en el cual queremos que automáticamente se capture texto en mayúsculas incluidas las vocales con acento queda de la siguiente forma (asumimos que el objeto se llama Memo1)

void __fastcall TForm1::Memo1KeyPress(TObject *Sender, char &Key)
{
 if (Key >= 97 && Key <= 122)
     Key = Key - 32;
 else
     switch(Key)
           {
            case 'á': Key = 'Á'; break;
            case 'é': Key = 'É'; break;
            case 'í': Key = 'Í'; break;
            case 'ó': Key = 'Ó'; break;
            case 'ú': Key = 'Ú';
           }
}

 

Anuncios

Diferencia entre un contador y un acumulador

agosto 19, 2017

En programación es común el uso de contadores y acumuladores, en este post explico la diferencia porque frecuentemente quienes están aprendiendo a programar confunden unos con otros.

Un contador es una variable que se utiliza para contar algo. Normalmente usamos un contador dentro de un ciclo y cambiamos su valor sumándole o restándole una constante, es decir, siempre se le suma o resta la misma cantidad. El caso más utilizado es incrementar la variable en uno.

En el siguiente programa en C se tiene un arreglo de 10 números enteros y se utiliza un ciclo con un contador para ver cuántas veces aparece el número 3.

Programa contador.c
#include <stdio.h>

int main()
{
 int numero = 3;
 int x=0, contador=0;
 int arreglo[10]={3,7,1,2,7,3,5,6,-2,7};
 
 // Recorre el arreglo y cuenta cuántas veces aparece el valor que contiene la variable numero 
 for (x=0; x<10; ++x)
     {
      if (arreglo[x] == numero)
          /* El nuevo valor de la variable contador va a ser igual a su valor actual más uno
             Se puede escribir como preincremento ( ++contador ) o como postincremento ( contador++ )
             pero para que sea bastante evidente, lo escribí de forma explícita ( contador = contador+1 )
          */
          contador = contador+1; 
     }
 
 printf("\n");
 printf("El número %d aparece %d veces en el arreglo\n", numero, contador);
 
 return 0;
}

El siguiente programa en C es un ejemplo de un contador que se va decrementando en uno; simula una cuenta regresiva.

Programa cuenta_regresiva.c
#include <stdio.h>

int main()
{
 int contador=10;
 
 // Imprime los números del 10 al cero
 while (contador >= 0)
       {
        printf("Despegue en %d\n", contador);
        contador = contador-1;
       }
 
 return 0;
}

Un acumulador es una variable que se utiliza para sumar valores. Al igual que el contador, se utiliza normalmente dentro de un ciclo pero cambiamos su valor sumándole una variable, es decir, no siempre se le suma la misma cantidad.

En el siguiente programa, utilizamos el mismo arreglo del programa contador.c, pero ahora no vamos a contar cuántas veces aparece un número x, sino que vamos a sumar todos los valores que aparezcan en el arreglo y que sean mayores a un número determinado.

Programa acumulador.c
#include <stdio.h>

int main()
{
 int mayores_que = 3;
 int x=0, suma=0;
 int arreglo[10]={3,7,1,2,7,3,5,6,-2,7};
 
 // Recorre el arreglo y suma todos los números mayores al valor que contiene la variable mayores_que 
 for (x=0; x<10; ++x)
     {
      if (arreglo[x] > mayores_que)
          /* El nuevo valor de la variable suma va a ser igual a su valor actual más el número 
             que se encuentra en la posición actual del arreglo
          */
          suma = suma+arreglo[x]; 
     }
 
 printf("Todos los números mayores a %d en el arreglo suman %d\n", mayores_que, suma);
 
 return 0;
}

¿Cómo diferenciar un buen desarrollador de software en Ruby del resto?

abril 11, 2017

Rails != Ruby

La primer diferencia entre un buen desarrollador de software en Ruby de uno malo no tan bueno es que el primero sabe que una cosa es Ruby y otra cosa es Rails. Ruby es un lenguaje de programación y Rails es sólo un framework para desarrollo web con Ruby.

Si alguien usa indistintamente (como si fueran sinónimos) Ruby y Rails (o RoR) ya sabemos que no es un buen desarrollador de software en Ruby y desafortunadamente hay muchos. Si tú quieres ser un buen desarrollador de software en Ruby lo primero que debes tener claro es que Ruby existe sin Rails pero Rails no existe sin Ruby, es decir, puedes desarrollar software usando Ruby sin usar Rails (incluso puedes desarrollar una aplicación web usando Ruby sin usar Rails) pero no puedes usar Rails con un lenguaje de programación que no sea Ruby.

Sólo usa Rails para desarrollo de aplicaciones web si te ves obligado

Rails es sin duda el framework más famoso para desarrollo de aplicaciones web con Ruby pero eso no significa que sea el mejor, de hecho es un framework que definitivamente yo no recomiendo si se trata de hacer una aplicación web profesional y robusta

Si alguien quiere aprender Ruby, lo peor que puede hacer es empezar con Rails. ¿Por qué opino que Rails es tan malo?; voy a exponer sólo algunos puntos y se que a quienes son fans de Rails estos puntos (ni cualquier otro argumento) los va a convencer, pero este post no está dirigido a ellos, como dice el dicho “No hay peor ciego que el que no quiere ver”.

Muchos de los que usan Rails creen saber Ruby y en la mayoría de los casos no es así, ejemplo: en muchas aplicaciones desarrolladas con Rails encontrarán en el código el uso de blank?. Abran una sesión de Ruby y tecleen algo como “prueba”.blank? y verán algo como NoMethodError: undefined method `blank’ for “prueba”:String. Eso es porque blank? no existe en Ruby, es una extensión de Rails. Pero muchos de los que usan Rails no saben eso, porque creen que blank? es una instrucción válida en Ruby.

Otra de las cosas por las que Rails no es un framework recomendable es porque está muy ligado a un ORM llamado Active Record y es uno de los peores ORM que existen para Ruby, el mejor ORM para Ruby que yo he usado es Sequel, seguido de DataMapper.

Active Record no soporta llaves primarias compuestas, el default es que la llave primaria de las tablas en la base de datos este formada por un solo campo llamado id, de tipo entero y además autoincrementable. Si en la Universidad tuviste un profesor(a) de Bases de Datos que más o menos supiera de lo que estaba hablando sabrás que una base de datos en la cual la llave primaria de todas sus tablas esté formada por un solo campo que además se llame igual en todas y tenga que ser de tipo entero es una base de datos que no te encontrarás en producción en el mundo real; a menos que esa base de datos la haya creado alguien que no tiene idea de lo que es el diseño de base de datos, o que sea una base de datos que usa una aplicación hecha en Rails.

Supongamos que tienes que desarrollar una aplicación web para el área de control escolar de una institución educativa. Tienes una tabla de asignaturas en donde cada asignatura tiene una clave que es un campo alfanumérico, tienes otra tabla de estudiantes en donde cada estudiante tiene una matrícula que también es un campo alfanumérico. Lo lógico es que no existan dos asignaturas con la misma clave y tampoco existan dos estudiantes con la misma matrícula. ¿No sería razonable que la llave primaria en la tabla de asignaturas fuera la clave de asignatura y en la tabla estudiantes la llave primaria fuera la matrícula?. Aquí hay un post que ejemplifica lo que hay que hacer en Rails para usar una llave primaria que no sea de tipo entero Non-Integer primary keys in Rails

Y dado que la relación entre estudiantes y asignaturas es del tipo muchos a muchos, tendremos una tabla para saber qué estudiantes están inscritos en una asignatura o visto de otra forma, todas las asignaturas en las que está inscrito un estudiante; ¿no sería lo mejor que la llave primaria de esta tabla estuviera formada por los campos clave de asignatura y matrícula? y además esos campos serían también llaves foráneas ya que son heredados de las tablas de asignaturas y estudiantes respectivamente. Para cualquiera que sepa lo básico de bases de datos relacionales esto es obvio.

Aquí están los diagramas Entidad-Relación (obvio las tablas tendrían más campos además de los que se muestran en el ejemplo y la base de datos contendría otras tablas además de estas)

Base de datos bien diseñada

Base de datos estilo Rails

Hay una gema llamada composite_primary_keys cuyo propósito es solucionar esta situación, pero no funciona 100% como debería.

El argumento de los fans de Rails es que no “luches” en contra de las “convenciones” de Rails, el problema aquí es que esas “convenciones” están mal diseñadas, si para desarrollar una aplicación web usando rails tengo que seguir sus “convenciones”, lo que implica que haga un mal diseño de base de datos (entre otras malas prácticas) o si intento hacer un diseño de base de datos decente tengo que estarme peleando con la herramienta y “parches” para hacer que las cosas funcionen como deben entonces lo mejor es no usar Rails.

Existen otros frameworks para desarrollo web en Ruby, Sinatra por ejemplo es un framework que a diferencia de Rails, no se interpone en tu camino. Eso si, Rails puede hacer algunas cosas automáticamente por ti y en Sinatra tu tienes que programar todo, así que podemos elegir entre usar un framework con el que desarrollemos una aplicación en la que escribamos menos código pero muy probablemente esté mal hecha (si seguimos sus “convenciones”) o usar un framework en el que todo el código lo hayamos tecleado nosotros pero las cosas pueden estar bien hechas (si nosotros las hacemos bien).

Si eres flojo, no te gusta mucho programar, o si no te importa que la aplicación que vas a desarrollar utilice una base de datos mal diseñada por ejemplo, puedes usar Rails, o si en tu trabajo te obligan a usarlo porque son de los que se van con lo que la mayoría usa sin conocer realmente las implicaciones de esa decisión.

Otra muestra de porque en mi opinión Rails no es un buen framework: Estos son los archivos y directorios que tendrías si usas Rails para desarrollar una aplicación web que lo único que haga sea mostrar la hora actual

Para desarrollar la misma aplicación web usando Sinatra sólo necesitamos un archivo con el siguiente código

require 'sinatra'

get '/' do
“La hora acutal es: #{Time.now}”
end

Si usas Sinatra es más fácil que sepas exactamente qué código es Ruby y qué codigo pertenece a instrucciones del ORM (sin importar si el ORM que usas es Active Record, DataMapper, Sequel, etc.), un buen desarrollador de software en Ruby jamás intentará usar blank? si no está desarrollando en Rails; pero ningún buen desarrollador de software en Ruby usaría Rails, a menos que en su trabajo se vea obligado desde luego.

Si tú eres un buen desarrollador de software en Ruby pero te obligan a usar Rails en tu trabajo te aconsejo que busques otro trabajo o que trates de convencer a tus jefes de que usen otro framework, aunque eso muchas veces es más difícil.

Si tú eres un buen desarrollador de software en Ruby y por lo tanto no usas Rails, te felicito, no conozco a muchos

Si programas en Ruby y hasta ahora has estado usando Rails cuando necesitas desarrollar una aplicación web, espero que esta información te ayude a empezar a usar otro framework, yo uso Sinatra pero existen otros como Cuba, Padrino, etc. Te invito a que investigues y encontrarás muchas más buenas razones para no seguir usando Rails

Para algunos existen buenas razones para usar Rails, aunque esas razones para mi no son suficientes, algunas de estas razones son que hay mucha más documentación para Rails que para otros frameworks, la mayoría de las ofertas de trabajo para desarrolladores de software en Ruby son usando Rails, etc. Estos argumentos son ciertos, y el último de ellos de hecho es lamentable, pero si tu objetivo es aprender y ser un buen desarrollador de software en Ruby y no tanto usar x framework porque es el que la mayoría usa o porque con eso te será más fácil encontrar un trabajo, entónces probablemte tampoco para ti esos argumentos sean suficientes.

Procesamiento de archivos con Ruby

febrero 1, 2017

En este post voy a mostrar un ejemplo de procesamiento de archivos con Ruby haciendo referencia a un caso real que se me presentó.

Se tiene un archivo csv llamado datos_clientes.csv y un archivo txt llamado solo_correos.txt; el archivo csv contiene una lista de clientes con datos como nombre, correo electrónico, teléfono, etc. y el archivo txt sólo contiene direcciones de correo electrónico.

El contenido del archivo datos_clientes.csv puede ser algo como esto:

NAME,E-MAIL
SALOMON RINCON TORRES,rtmex@yahoo.com
TOP SYSTEMS S.A DE C.V.,ventas@topsystems.com.mx
SALOMON RINCON TORRES,salomonrincon@softwarelibrepuebla.org
JOHN DOE,ejemplo@gmail.com

y el contenido del archivo solo_correos.txt podría parecerse a esto:

ventas@topsystems.com.mx
ejemplo@gmail.com
rtmex@yahoo.com

Imaginemos que ambos registros tienen cientos o miles de líneas

El objetivo es leer cada uno de los correos del archivo solo_correos.txt y buscarlo en el archivo datos_clientes.csv, si se encuentra, guardar el correo y el nombre del cliente al que pertenece dicho correo en un nuevo archivo csv (en este caso el archivo se llamará resultado.csv).

Aquí el código

require 'csv'

# Declara el encabezado para el archivo que se creará
encabezado = ['correo', 'nombre']
correo = nil
nombre = nil
num_linea = 0

CSV.open('resultado.csv', 'w', write_headers: true, headers: encabezado) do |archivo_nuevo|
    # Abre el archivo que contiene cada una de las líneas que se van a buscar en otro archivo
    File.open("solo_correos.txt") do |archivo|
         archivo.each do |linea|
             num_linea += 1
             puts "Linea #{num_linea} del archivo solo_correoa.txt = "+linea.strip
             # Abre un archivo delimitado por comas
             # En la segunda columna de este archivo, se busca la línea que se leyó del primer archivo
             CSV.foreach('datos_clientes.csv', headers: true) do |linea2|
                 if linea2[1].strip == linea.strip
                    puts "El correo "+linea.strip+" pertenece a "+linea2[0].strip
                    correo = linea.strip
                    nombre = linea2[0].strip
                    archivo_nuevo << [correo, nombre]
                    break
                 end
             end
         end
    end
end

El programa va mostrando el número de línea y su contenido conforme va procesando el archivo solo_correos.txt. Cada vez que en el archivo datos_clientes.csv encuentra el correo que leyó del archivo solo_correos.txt, muestra a que cliente pertenece dicho correo.

Lo que iría mostrando el programa mientras va procesando el archivo solo_correos.txt sería algo como esto:

Linea 3 del archivo solo_correos.txt = rtmex@yahoo.com
El correo rtmex@yahoo.com pertenece a SALOMON RINCON TORRES

Cuando el programa termina, tenemos un archivo csv llamado resultado.csv con 2 columnas, la primera contiene el correo electrónico y la segunda el nombre del cliente al que pertenece dicho correo.

Software para music scheduling

agosto 12, 2016

Hace ya varios años, cuando estudiaba los últimos semestres de la licenciatura, recibí en la noche (como a las 11:00 p.m., yo ya me encontraba durmiendo) una llamada de un amigo mio que estudiaba los últimos semestres de ciencias de la comunicación y era el responsable de la programación de una estación de radio; la llamada era para pedirme ayuda porque tenía problemas con el software que tenían en esa estación para hacer el music scheduling. Pasó por mi y fuimos a la estación de radio, recuerdo que si tenía un bug el software, pero no tenían el código fuente, así que no me era posible arreglarlo.

Recuerdo que le comenté que además estaba muy mal diseñado, la interfaz de usuario era pésima y en tono de broma le dije que era mejor hacer uno nuevo que arreglar ese. No se si el lo tomó como broma o no pero me dijo que no era mala idea, a lo que yo contesté que el problema era que yo no sabía acerca de cómo programar una estación de radio; inmediatamente el replicó “pero yo si”. Así que el me explicó que es lo que hacía el software que tenían en esa estación, pero también me dijo qué cosas consideraba que le hacían falta o funciones que serían de gran ayuda.

Para cuando terminé de desarrollar la aplicación, aunque sólo fueron unos cuantos meses por las clases, etc. el ya no trabajaba en esa estación de radio. El sistema estaba terminado y el pensó que sería muy útil para cualquiera que estuviera a cargo de la programación de una estación de radio, así que empezó a ofrecerlo entre sus colegas. El software tuvo buena aceptación; incluso una estación en Nueva York compró una licencia.

Fui mejorando el software con la retroalimentación de los usuarios. Veinte años después, he desarrollado otro software para music scheduling que supera desde luego al de aquel entonces.

Es una aplicación web que desarrollé utilizando Ruby y Sinatra. Aquí les dejo una captura de pantalla, pero para más información pueden ir a la página http://www.topsystems.com.mx/SGP

playlist

La aplicación permite registrar varios usuarios para que hagan uso del sistema, cada uno con su propia contraseña. Si el servidor en el que se instala la aplicación tiene acceso a internet, cualquier usuario puede usar la aplicación desde cualquier lugar en donde se encuentre si cuenta con una computadora y conexión a internet.

Las máquinas cliente no requieren tener muchos recursos, puse un servidor de prueba (con 600 registros) y un amigo me hizo favor de probar remotamente la aplicación utilizando una raspberry pi y me comentó que funcionaba bastante rápido.

Uso de Knockout para crear aplicaciones de internet enriquecidas

agosto 18, 2014

Knockout es una biblioteca JavaScript que sirve muy bien para crear aplicaciones de internet enriquecidas (Rich Internet Applications) ya que permite el desarrollo de interfaces de usuario utilizando programación dirigida por eventos en aplicaciones Web.

A continuación pongo un ejemplo sencillo que podría ser parte de un sistema más grande como una aplicación de control escolar, la página muestra una lista desplegable con los nombres de los alumnos y al seleccionar uno, se muestra su matrícula en el campo del lado izquierdo; también funciona en sentido inverso, es decir, si el usuario teclea la matrícula correspondiente a un alumno, la lista desplegable muestra el nombre del alumno al que le corresponde esa matrícula.

ejemplo_knockout

Adicionalmente, el usuario tiene opción para agregar más alumnos a la lista, indicando su matricula y su nombre. Para el ejemplo utilizo JQuery 1.11 y Knockout 3.0.

El archivo html quedaría de la siguiente forma

Archivo ejemplo_ko.html

<!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>Sistema de control escolar KO</title>

<script type='text/javascript' src='jquery-1.11.0.min.js'></script>
<script type='text/javascript' src='knockout-3.0.0.js'></script>
<script type='text/javascript' src="ejemplo.js"></script>

</head>
<body>
     <input maxlength=5 size=5 data-bind="value: busca_matricula">
     <select maxlength=30 data-bind="options: alumnos, optionsText: 'nombre', optionsValue: 'matricula', value: busca_matricula, optionsCaption: 'Seleccione alumno'">
     </select>
     <br><br>
     Matricula <input maxlength=5 size=5 data-bind="value: nueva_matricula">
     Nombre <input maxlength=30 size=30 data-bind="value: nuevo_nombre">
     <input type="button" value="Agregar Alumno" data-bind="click: agregaAlumno">
</body>
</html>]

En el archivo ejemplo.js pondremos el código correspondiente a la función agregaAlumno, que deseamos que se ejecute cuando el usuario haga click en el botón con la leyenda Agregar Alumno, así como un arreglo con una lista de alumnos inicial.
Deseamos que el código de JavaScript se ejecute hasta que el documento html esté cargado por completo, por lo que todo nuestro código irá dentro de la función $(document).ready
Vamos a utilizar “objetos” de un tipo llamado Alumno, cada objeto de este tipo, tendrá dos atributos: matrícula y nombre. Los objetos los crearemos mediante una función que recibirá como parámetros la matrícula y el nombre del alumno.

function Alumno(matricula, nombre) {
    this.matricula = matricula;
    this.nombre = nombre;
}

Después definimos nuestro modelo de vista (ViewModel) en donde declaramos un arreglo que utilizaremos para almacenar los objetos del tipo Alumno; de entrada agregamos 3 alummnos al arreglo

self.alumnos = ko.observableArray([
            new Alumno(100, "FERNANDA"),
            new Alumno(175, "ANTONIO"),
            new Alumno(203, "JOSE LUIS")
        ]);

Después declaramos en nuestro modelo una propiedad de tipo observable, y la ligaremos al campo que el usuario utilizará en nuestro archivo html para buscar un alumno por su matrícula.
self.busca_matricula = ko.observable();

El que una propiedad de nuestro modelo de vista sea observable significa que knockout estará “monitoreando” esta propiedad y actualizará nuestro archivo html cada vez que el valor de dicha propiedad cambie.

Declaramos una propiedad más, que utilizaremos para indicar qué matrícula tendrá un alumno nuevo que deseemos agregar al arreglo alumnos
self.nueva_matricula = ko.observable();

La última propiedad que declararemos es para indicar el nombre de un alumno nuevo que deseemos agregar al arreglo alumnos
self.nuevo_nombre = ko.observable();

Por último, creamos la función agregaAlumno que nos sirve para agregar alumnos al arreglo alumnos.

self.agregaAlumno = function() {
         nuevoAlumno = new Alumno(
                                  parseFloat(this.nueva_matricula()),
                                  this.nuevo_nombre()
                                 );
         self.alumnos.push(nuevoAlumno);
    };

El archivo ejemplo.js completo queda así

$(document).ready(function() {

function Alumno(matricula, nombre) {
    this.matricula = matricula;
    this.nombre = nombre;
}

function AlumnosViewModel() {
    var self = this;
    self.alumnos = ko.observableArray([
            new Alumno(100, "FERNANDA"),
            new Alumno(175, "ANTONIO"),
            new Alumno(203, "JOSE LUIS")
        ]);
    self.busca_matricula = ko.observable();
    self.nueva_matricula = ko.observable();
    self.nuevo_nombre = ko.observable();
    
    self.agregaAlumno = function() {
         nuevoAlumno = new Alumno(
                                  parseFloat(this.nueva_matricula()),
                                   this.nuevo_nombre()
                                 );
         self.alumnos.push(nuevoAlumno);
    };
}

ko.applyBindings(new AlumnosViewModel());

});

Obviamente en una aplicación real los datos para el arreglo se llenarían desde una tabla de la base de datos de la aplicación, faltaría hacer varias validaciones como no dejar que agregue un alumno si no ha indicado la matrícula y el nombre, etc. este es un ejemplo para que se den una idea de lo que se puede hacer con Knockout

Menú en la terminal de GNU/Linux con C++ y códigos de escape

enero 4, 2013

El hacer un programa en modo texto para GNU/Linux en C++ que muestre un menú con una barra que se controle con las flechas del teclado implica más trabajo del que se requiere para lograr lo mismo si nuestro programa fuera a ejecutarse en un sistema operativo basado en DOS (ya sea MS-DOS, Windows, etc.)

A continuación muestro una posible solución de como lograrlo sin utilizar ncurses, únicamente códigos de escape, esto tiene la pequeña desventaja de que no funciona para cualquier terminal, pero para xterm y algunas otras no debe haber problema

Crearemos una fución que espere a que el usuario pulse una tecla y regrese el valor correspondiente a esa tecla, la función debe tomar en cuenta las teclas que al ser pulsadas regresan tres valores, siendo el primer valor el que corresponde a la tecla ESC (que es el 27)

int lee_caracter()
{
 struct termios config_ant;
 struct termios config_nueva;
 int caracter;

 tcgetattr(STDIN_FILENO, &config_ant);
 config_nueva = config_ant;                /* Guarda los valores actuales de configuración de la terminal */
 config_nueva.c_lflag &= ~(ICANON | ECHO); /* resetea las banderas canonical y echo para que no se muestre en pantalla lo que se teclea*/
 tcsetattr(STDIN_FILENO, TCSANOW, &config_nueva); /* Establece los nuevos valores para la terminal */
 caracter = getchar();

 // si se pulsa una tecla que empieza con un código de escape
 if (caracter==27)
    {
     caracter=getchar();
     if (caracter==91)
         caracter=getchar(); //caracter ahora tiene el valor de la tecla pulsada
    }

 tcsetattr(STDIN_FILENO, TCSANOW, &config_ant); /* Vuelve a poner los valores originales de configuración de la terminal */

 return caracter;
}

Los atributos y colores que podemos manejar son los siguientes:

#define RESET        0
#define BRIGHT       1
#define DIM          2
#define UNDERLINE    3
#define BLINK        4
#define REVERSE      7
#define HIDDEN       8
#define BLACK        0
#define RED          1
#define GREEN        2
#define YELLOW       3
#define BLUE         4
#define MAGENTA      5
#define CYAN         6
#define WHITE        7

Necesitamos también una función que nos permita establecer el atributo, color de la fuente y color de fondo que vamos a utilizar antes de escribir un texto

void ColorTexto(int atributo, int texto, int fondo)
{
 std::string color = "\033["+toString(atributo)+";"+toString(texto+30)+";"+toString(fondo+40)+"m";
 cout<<color;
}

Este programa utiliza las dos funciones descritas anteriores para mostrar un menú vertical, se asume una terminal de 80 columnas

/*
Debido a que este programa usa el archivo de cabecera termios.h,
sólo funciona en sistemas operativos que cumplan con el estándar POSIX
Es decir, cualquier distribución de GNU/Linux o cualquier Unix.
*/

#include <iostream>
#include <sstream>    /* Para poder usar istringstream */
#include <string>
#include <termios.h>  /* Para poder usar las estructuras termios */
#include <stdio.h>    /* Para poder usar getchar() */
#include <iomanip>    /* Para poder usar setw */

#define RESET        0
#define BRIGHT       1
#define DIM          2
#define UNDERLINE    3
#define BLINK        4
#define REVERSE      7
#define HIDDEN       8

#define BLACK        0
#define RED          1
#define GREEN        2
#define YELLOW       3
#define BLUE         4
#define MAGENTA      5
#define CYAN         6
#define WHITE        7

const std::string default_console = "\033[0m";

using std::cout;
using std::endl;
using std::setw;

template <typename T>
static std::string toString (T numero)
{
 std::ostringstream ss;
 ss << numero;
 return ss.str();
}

void ColorTexto(int atributo, int texto, int fondo);
int lee_caracter();
void ImprimeMenu(std::string menu[], int tamanio, int op);

int main()
{
 std::string str_menu[4] = {"1. Registrar", "2. Consultar", "3. Modificar", "4. Salir    "};
 int tecla, opcion=0, seleccion=1;

do
 {
  ImprimeMenu(str_menu,4,seleccion);
  tecla = lee_caracter();

  switch (tecla)
         {
          case 10:
                  // Enter
                  opcion = seleccion;
                  break;
          case 65:
                  // Flecha arriba
                  seleccion = (seleccion==1 ? 4 : seleccion-1);
                  opcion = 0;
                  break;
          case 66:
                  // Flecha abajo
                  seleccion = (seleccion==4 ? 1 : seleccion+1);
                  opcion = 0;
                  break;
          default:
                  opcion = tecla-48;
                  if (opcion>=1 && opcion<=4)
                     {
                      seleccion = opcion;
                      ImprimeMenu(str_menu,4,seleccion);
                     }
         }
  if (opcion>=1 && opcion<=4)
     {
      cout<<"Opcion "<<opcion<<endl;
      lee_caracter();
     }
  }
 while (opcion!=4);

 return 0;
}

void ColorTexto(int atributo, int texto, int fondo)
{
 std::string color = "\033["+toString(atributo)+";"+toString(texto+30)+";"+toString(fondo+40)+"m";
 cout<<color;
}

int lee_caracter()
{
 struct termios config_ant;
 struct termios config_nueva;
 int caracter;

 tcgetattr(STDIN_FILENO, &config_ant);
 config_nueva = config_ant;                /* Guarda los valores actuales de configuración de la terminal */
 config_nueva.c_lflag &= ~(ICANON | ECHO); /* resetea las banderas canonical y echo para que no se muestre en pantalla lo que se teclea*/
 tcsetattr(STDIN_FILENO, TCSANOW, &config_nueva); /* Establece los nuevos valores para la terminal */
 caracter = getchar();

 // si se pulsa una tecla que empieza con un código de escape
 if (caracter==27)
    {
     caracter=getchar();
     if (caracter==91)
         caracter=getchar(); //caracter ahora tiene el valor de la tecla pulsada
    }

 tcsetattr(STDIN_FILENO, TCSANOW, &config_ant); /* Vuelve a poner los valores originales de configuración de la terminal */

 return caracter;
}

void ImprimeMenu(std::string menu[], int tamanio, int op)
{
 int x=0, pos;
 unsigned int longitud=0;

 // Obtiene la longitud de la opción del menú con más caracteres
 for (x=0; x<tamanio; ++x)
     {
      if (menu[x].length() > longitud)
          longitud = menu[x].length();
     }
 pos = (80 - longitud) / 2;

 // Limpia la pantalla
 cout << "\033[2J\033[1;1H";

 for (x=0; x<tamanio; ++x)
     {
      // Deja el número de espacios adecuado para que el menú quede centrado (se asume una pantalla de 80 caracteres)
      cout<<default_console;
      cout<<setw(pos)<<" ";

      if (x+1==op)
          ColorTexto(BRIGHT,YELLOW,MAGENTA);
      else
          ColorTexto(BRIGHT,WHITE,BLUE);
      cout<<menu[x]<<endl;
     }
 cout<<default_console<<endl;
}

menu

Ahora la versión para mostrar el menú en forma horizontal

/*
 Debido a que este programa usa el archivo de cabecera termios.h,
 sólo funciona en sistemas operativos que cumplan con el estándar POSIX
 Es decir, cualquier distribución de GNU/Linux o cualquier Unix.
*/

#include <iostream>
#include <sstream>    /* Para poder usar istringstream */
#include <string>
#include <termios.h>  /* Para poder usar las estructuras termios */
#include <stdio.h>    /* Para poder usar getchar() */
#include <iomanip>    /* Para poder usar setw */

#define RESET        0
#define BRIGHT       1
#define DIM          2
#define UNDERLINE    3
#define BLINK        4
#define REVERSE      7
#define HIDDEN       8

#define BLACK        0
#define RED          1
#define GREEN        2
#define YELLOW       3
#define BLUE         4
#define MAGENTA      5
#define CYAN         6
#define WHITE        7

const std::string default_console = "\033[0m";

using std::cout;
using std::endl;
using std::setw;

template <typename T>
static std::string toString (T numero)
{
 std::ostringstream ss;
 ss << numero;
 return ss.str();
}

void ColorTexto(int atributo, int texto, int fondo);
int lee_caracter();
void ImprimeMenu(std::string menu[], int tamanio, int op);

int main()
{
 std::string str_menu[4] = {"1. Registrar", "2. Consultar", "3. Modificar", "4. Salir"};
 int tecla, opcion=0, seleccion=1;

 do
   {
    ImprimeMenu(str_menu,4,seleccion);
    tecla = lee_caracter();

    switch (tecla)
           {
            case 10:
                    // Enter
                    opcion = seleccion;
                    break;
            case 67:
                    // Flecha derecha
                    seleccion = (seleccion==4 ? 1 : seleccion+1);
                    opcion = 0;
                    break;
            case 68:
                    // Flecha izquierda
                    seleccion = (seleccion==1 ? 4 : seleccion-1);
                    opcion = 0;
                    break;
            default:
                    opcion = tecla-48;
                    if (opcion>=1 && opcion<=4)
                       {
                        seleccion = opcion;
                        ImprimeMenu(str_menu,4,seleccion);
                       }
           }
    if (opcion>=1 && opcion<=4)
       {
        cout<<"Opcion "<<opcion<<endl;
        lee_caracter();
       }
   }
 while (opcion!=4);

 return 0;
}

void ColorTexto(int atributo, int texto, int fondo)
{
 std::string color = "\033["+toString(atributo)+";"+toString(texto+30)+";"+toString(fondo+40)+"m";
 cout<<color;
}

int lee_caracter()
{
 struct termios config_ant;
 struct termios config_nueva;
 int caracter;

 tcgetattr(STDIN_FILENO, &config_ant);
 config_nueva = config_ant;                /* Guarda los valores actuales de configuración de la terminal */
 config_nueva.c_lflag &= ~(ICANON | ECHO); /* resetea las banderas canonical y echo para que no se muestre en pantalla lo que se teclea*/
 tcsetattr(STDIN_FILENO, TCSANOW, &config_nueva); /* Establece los nuevos valores para la terminal */
 caracter = getchar();

 // si se pulsa una tecla que empieza con un código de escape
 if (caracter==27)
    {
     caracter=getchar();
     if (caracter==91)
         caracter=getchar(); //caracter ahora tiene el valor de la tecla pulsada
    }

 tcsetattr(STDIN_FILENO, TCSANOW, &config_ant); /* Vuelve a poner los valores originales de configuración de la terminal */

 return caracter;
}

void ImprimeMenu(std::string menu[], int tamanio, int op)
{
 int x=0, pos;
 unsigned int longitud=0;

 // Obtiene la longitud total del menú dejando dos espacios entre cada opción
 for (x=0; x<tamanio; ++x)
     {
      longitud += menu[x].length();
      if (x!=tamanio-1)
          longitud +=2;
     }

 pos = (80 - longitud) / 2;

 // Limpia la pantalla
 cout << "\033[2J\033[1;1H";

 // Deja el número de espacios adecuado para que el menú quede centrado (se asume una pantalla de 80 caracteres)
 cout<<setw(pos)<<" ";
 for (x=0; x<tamanio; ++x)
     {
      if (x+1==op)
          ColorTexto(BRIGHT,YELLOW,MAGENTA);
      else
          ColorTexto(BRIGHT,WHITE,BLUE);
      cout<<menu[x];
      if (x!=tamanio-1)
        {
         ColorTexto(BRIGHT,WHITE,BLUE);
         cout<<"  ";
        }
     }
 cout<<default_console<<endl;
}

menuh

Pasar arreglos a una función en C

noviembre 29, 2012

Por default, los arrreglos en C se pasan a una función como referencia y no como valor. Esto significa que todas las modificaciones que hagamos dentro de la función en C al arreglo que recibimos como parámetro, realmente se realizan en el arreglo original que se utilizó como argumento al momento de llamar a la función.

Al escribir una función en C, la forma de indicar que uno de los parámetros que se va a recibir es un arreglo de una dimensión, es decir de que tipo va a ser el arreglo y el nombre con el cual vamos a manipular dicho arreglo dentro de nuestra función seguido de corchetes que abren y cierran; nuestra función también debe recibir un segundo parámetro que nos indique el tamaño del arreglo, o dicho de otra forma, el número de elementos de los que consta nuestro arreglo, recordemos que como el arreglo se pasa a la función como referencia, lo que esta recibiendo la función en realidad es un apuntador al primer elemento del arreglo, pero no sabe en donde termina el arreglo, por eso es necesario que la función también reciba como parámetro el número de elementos del arreglo.

Por ejemplo, el prototipo de una función en C que va a regresar un entero y va a recibir un arreglo de 10 elementos de tipo entero sería algo asi:

int MiFuncion(int mi_arreglo[], int num_elemetos);

Para llamar a la función anterior, se pone como primer argumento el nombre del arreglo (sin corcehetes) y como segundo argumento el número de elementos del arreglo. Supongamos que tenemos un arreglo de 10 elementos de tipo entero llamado numeros y queremos guardar en una variable llamada “x” el valor que nos regresa la función “MiFuncion” al llamarla pasandole como argumentos el arreglo “numeros”, haríamos algo como esto

x = MiFuncion(numeros, 10);

El siguiente programa solicita al usuario que ingrese 10 números de tipo entero y los almacena en un arreglo; después le pide que introduzca un número para que busque su posición dentro del arreglo..

El programa utiliza una función llamada BuscaNumero que recibe como parámetros el arreglo con los 10 números capturados, el número de elementos del arreglo (en este caso 10) y el número del cual se desea saber su posición dentro del arreglo.. La función regresa -1 si el número que se busca no se encuentra en el arreglo y en caso contrario, regresa la primera posición del arreglo que contiene dicho número.

El programa también utiliza una función llamada MuestraArreglo que no regresa valor alguno, sólo recibe como parámetros el arreglo y el número de elementos. Esta función imprime en pantalla los elementos del arreglo separados por un tabulador.

#include <stdio.h>

int BuscaNumero(int valores[], int tamanio, int busca)
{
int i=0, posicion=-1;

do
  {
   if (valores[i]==busca)
       posicion=i;
   else
       ++i;
  }
while(i<tamanio && posicion<0);

return posicion;
}

void MuestraArreglo(int valores[], int tamanio)
{
 int i=0;

 for (i=0; i<tamanio; ++i)
      printf("%d\t",valores[i]);
 printf("\n");
}

int main()
{
 int x=0, numero=0, posicion=0;
 int ar_numeros[10] = {0};

 printf("Introduzca los 10 numeros enteros que se almacenaran en el arreglo\n");
 for (x=0; x<10; ++x)
     {
      printf("Valor para el elemento [%d]: ", x);
      scanf("%d",&ar_numeros[x]);
     }
 printf("\n");

 printf("Introduzca el número que desea buscar en el arreglo\n");
 scanf("%d",&numero);
 printf("\n");
 MuestraArreglo(ar_numeros,10);

 posicion=BuscaNumero(ar_numeros,10,numero);
 if (posicion != -1)
     printf("El número %d está en la posición %d del arreglo\n",numero, posicion);
 else
     printf("El número %d no está en el arreglo\n",numero);

 return 0;
}

Para pasar a una función un arreglo de dos dimensiones, si debemos indicar el tamaño de la segunda dimensión del arreglo

Ejemplo:int MiFuncion(int mi_arreglo[][5], int num_elementos);

El siguiente programa le pide al usuario que introduzca 9 números y los almacena en un arreglo de dos dimensiones, en este caso una matriz de 3×3; posteriormente utiliza una función llamada ImprimeMatriz para mostrar como quedaron almacenados los números en la matriz. Dicha función recibe como parámetros, la matriz de 3×3 y el tamaño de la primera dimensión (normalmente la primera dimensión son filas y la segunda dimensión columnas).

#include <stdio.h>

void ImprimeMatriz(int m[][3], int filas)
{
 int i=0,j=0;

 for (i=0; i<filas; ++i)
     {
      for (j=0; j<3; ++j)
          {
           printf("%d ",m[i][j]);
          }
      printf("\n");
     }
}

int main()
{
 int x=0,y=0;
 int matriz[3][3] = {{0},{0},{0}};

 printf("Introduzca los valores para la matriz\n");
 for (x=0; x<3; ++x)
     {
      for (y=0; y<3; ++y)
          {
           printf("Valor para el elemento [%d][%d]: ", x, y);
           scanf("%d",&matriz[x][y]);
          }
      printf("\n");
     }

 printf("Matriz\n");
 ImprimeMatriz(matriz, 3);

 return 0;
}

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>

Gráfica de barras horizontales en la terminal con C++

diciembre 14, 2011

Ya en un post anterior había escrito sobre la STL de C++; el código siguiente simula una gráfica de barras horizontales en modo texto y aprovecho para ejemplificar el uso de algunas funciones y algoritmos de la STL.
El programa consiste en pedirle al usuario que capture números positivos y los va almacenando en un vector, para terminar la captura el usuario debe introducir un número negativo. Una vez terminada la captura, se copian los valores almacenados en el vector a otro vector y después se ordenan para que en el vector original los números queden en el mismo orden en que el usuario los fue capturando. Esta parte de copiar los valores de un vector a otro y ordenar los valores en el segundo vector no es necesaria para el funcionamiento del programa, sólo lo pongo para ejemplificar com se haría esto usando la STL.

En el programa utilizo funciones de la STL para encontrar el valor menor y el valor mayor en el vector original sin tener que ordenarlo.

Aquí está el código
Archivo grafica_barrash.cpp

#include <iostream>
#include <vector>
#include <algorithm>
#include <math.h>

using namespace std;

int main()
{
 int i_x=0, i_y=0;
 double dbl_valor=0, dbl_escala=0;
 vector<double> v_valores, v_ordenados;
 vector<double>::iterator v_valores_it, v_ordenados_it;
 vector<double>::const_iterator menor_it, mayor_it, valor_it;

 cout << "Teclee un número, (cualquier número negativo para terminar):" << endl;
 /* Mientras el usuario teclee números positivos (incluido el cero), guardamos en el vector
    v_valores los números que el usuario va tecleando
 */
 while (dbl_valor >= 0)
       {
        cin >> dbl_valor;
        if (dbl_valor >= 0)
            v_valores.push_back(dbl_valor);
       }

 cout << endl << "Estos son los números que Ud. introdujo:" << endl;
 // Muestra los valores almacenados en el vector v_valores
 for ( v_valores_it = v_valores.begin(); v_valores_it != v_valores.end(); v_valores_it++)
      cout << *v_valores_it << endl;

 // Hace el vector v_ordenados del mismo tamaño que el vector v_valores
 v_ordenados.resize (v_valores.size());
 // Copia los valores del vector v_valores al vector v_ordenados
 copy (v_valores.begin(), v_valores.end(), v_ordenados.begin());
 // Ordena el vector v_ordenados
 std::sort(v_ordenados.begin(), v_ordenados.end());

 cout << endl << "Estos son los números ordenados:" << endl;
 // Muestra los valores almacenados en el vector v_ordenados
 for ( v_ordenados_it = v_ordenados.begin(); v_ordenados_it != v_ordenados.end(); v_ordenados_it++)
      cout << *v_ordenados_it << endl;

 menor_it = min_element(v_valores.begin(), v_valores.end());
 mayor_it = max_element(v_valores.begin(), v_valores.end());

 cout << endl << "El valor menor es: " << *menor_it << endl;
 cout << "El valor mayor es: " << *mayor_it << endl;

 // Obtiene la escala
 dbl_escala = *mayor_it - *menor_it;
 cout << "La escala es: " << dbl_escala << endl << endl;

 for (i_x=0; i_x<8; i_x++)
      cout << " ";

 cout << ceil(*menor_it);
 for (i_x=0; i_x<=34; i_x++)
      cout << " ";
 cout << ceil((dbl_escala/2)+*menor_it);
 for (i_x=0; i_x<=34; i_x++)
      cout << " ";
 cout << ceil(*mayor_it);
 cout << endl;
 for (i_x=0; i_x<8; i_x++)
      cout << " ";
 cout << "|";
 for (i_x=0; i_x<=34; i_x++)
      cout << " ";
 cout << "|";
 for (i_x=0; i_x<=34; i_x++)
      cout << " ";
 cout << "|" << endl;

 // Dibuja las barras
 i_x=1;
 for ( v_valores_it = v_valores.begin(); v_valores_it != v_valores.end(); v_valores_it++)
     {
      cout << "Valor"<< i_x;
      /* Si el valor de x es de un sólo dígito, deja 2 espacios antes de empezar a dibujar la barra, de lo contrario
         deja sólo uno
      */
      cout << (i_x < 10 ? "  " : " ");
      valor_it = v_valores_it;
      for (i_y=1; i_y<= ((double)(*valor_it - *menor_it)/dbl_escala)*72+1; i_y++)
           cout << "*";
      cout << endl;
      i_x++;
     }

 return 0;
}

A continuación pongo una captura de pantalla
Como se puede ver, la gráfica muestra los valores como fueron introducidos por el usuario, así que:
valor1 = 100, valor2 = 40, valor3 = 75, valor4 = 50,valor5 = 25, valor6 = 30, valor7 = 10

Esto porque recibí un correo de una persona que dijo ser estudiante y me reclamaba que “estaba todo mal” y le habían puesto 1 de calificación.
En primer lugar, como la captura de pantalla demuestra, el programa funciona bien; en segundo lugar, este blog no es para que eviten hacer su tarea si son estudiantes; y en tercer lugar, las críticas constructivas a cualquier post que publique, son bienvenidas, pero si como en este caso, sólo escribe tonterías como “esta todo mal” y no aporta argumentos de en que se basa para hacer tal afirmación, es obvio que voy a ignorarlo.
Por último, mis post generalmente serán para personas con conocimientos mínimos de programación, por lo menos que sepan deducir un algoritmo a partir del código fuente que leen.