24 de marzo de 2017

Formación en competencias

Desde hace algunos años el Tecnológico de Monterrey, la institución en la que laboro, ha buscado enfatizar en sus programas de estudio universitarios el desarrollo de competencias. Pero, ¿qué es exactamente una competencia? Esta es la definición que el Tecnológico está actualmente manejando[1]:
Una competencia es la integración consciente de conocimientos, habilidades, actitudes y valores que permite enfrentar con éxito situaciones tanto estructuradas como de incertidumbre y que puede implicar procesos mentales de orden superior.
Alternativamente, se puede decir de manera más sencilla que una competencia consiste en “saber hacer algo con los conocimientos aprendidos”[2].


Las siguientes son algunas características de una educación basada en competencias[3]:
  • Es un enfoque alternativo al enfoque predominante actual, el cual está basado en objetivos.
  • En lugar de enfatizar conocimientos enfatiza el desarrollo de competencias.
  • El aprendizaje deja de ser relevante exclusivamente en un marco meramente hipotético, sino que su relevancia se extiende también al mundo real.
  • Busca formar al estudiante de manera integral dentro de su contexto personal y social, y no únicamente en el aspecto profesional.
  • Va más allá de la comprensión de conceptos, abarcando también el dominio de procedimientos y el desarrollo de actitudes.
  • Requiere de una instrucción intencional y bien planeada por parte de los maestros, con el fin de brindar la mejor experiencia de aprendizaje.
En resumen, la educación basada en competencias está orientada a la formación del alumno y no solo a la transmisión de conocimientos.


Es importante no confundir el concepto de competencia con el de objetivo de aprendizaje. Por un lado, un objetivo indica qué queremos que nuestros alumnos sepan. Por otro lado, las competencias establecen la forma en que podemos tener la certeza de que efectivamente lo saben. Las competencias definen los conocimientos y habilidades que un futuro egresado podrá desempeñar de manera exitosa en el mundo real, mientras que los objetivos de aprendizaje son específicos a la instrucción de un curso en particular[4].


Según Brooks, la educación basada en competencias ofrece múltiples ventajas para los estudiantes[5]. En principio cualquier alumno puede:
  • Trabajar a su propio ritmo y en el horario que más le convenga.
  • Construir nuevos aprendizajes a partir de sus conocimientos previos.
  • Graduarse de manera más rápida y por tanto ahorrar dinero.
  • Obtener experiencia práctica del mundo real.
Por otro lado, existen algunas posibles desventajas[6]:
  • Se enfoca más a las necesidades inmediatas de los empleadores y menos en la preparación de los estudiantes para que puedan ser flexibles en un futuro incierto.
  • No es adecuado para aquellas áreas en las que resulta difícil prescribir competencias específicas o donde se requieran incorporar nuevas habilidades o conocimientos de manera extremadamente rápida.
  • Hace que el aprendizaje tome un enfoque objetivista.
  • Esta manera de aprender pudiera resultarle inadecuada a algunas personas.
Es importante también comprender la manera en que se clasifican las competencias:
  • De acuerdo a su alcance: pueden ser transversales o disciplinares.
  • De acuerdo a su ámbito o dimensión: pueden ser personales, interpersonales, sociales o profesionales.
  • De acuerdo a su ubicación en el currículo: pueden ser básicas, intermedias o terminales.
Finalmente, vale la pena comentar sobre la forma de documentar las competencias. Se sugiere redactarlas utilizando la siguiente estructura[1]:

Estructura de una competencia

Cada elemento del diagrama anterior se describe a continuación:
  • Verbo: ¿Qué hace?
  • Objeto: ¿Qué transmite?
  • Contexto o condición: ¿En qué contexto o bajo qué condiciones?
Por ejemplo, para un curso de introducción a la programación podríamos tener la siguiente competencia:
Resuelve un problema de programación a partir de un enunciado textual el cual requiera emplear una instrucción condicional if-else.
Ahora nos queda a los profesores del Tec de Monterrey el reto de aplicar efectivamente estas ideas a nuestra práctica docente cotidiana. Aún queda mucho por aprender, pero no deja de ser emocionante la expectativa de poder brindar a nuestros alumnos cada vez mejores experiencias de aprendizaje.

Profesores del área de TI del Tecnológico de Monterrey.
Reunión nacional de grupos colegiados por disciplina,
Monterrey, Nuevo León. Julio de 2015.


Referencias

  1. Vicerrectoría Académica del ITESM, Dirección de Programas Académicos. Definición y desarrollo de competencias. México, febrero, 2017.
  2. Instituto Tecnológico de Estudios Superiores de Monterrey. Diseño y desarrollo de competencias. México, 2016.
  3. Instituto Tecnológico de Estudios Superiores de Monterrey. Educación basada en competencias. México, 2015.
  4. Vasquez, Caroline. Competencies and Learning Objectives. Estados Unidos, mayo, 2011.
  5. Brooks, Ashley. 6 Benefits of Competency-Based Education that will Excite Adult Learners. Estados Unidos, abril, 2016.
  6. Bates, Tony. The strengths and weaknesses of competency-based learning in a digital age. Canadá, septiembre, 2014.

29 de diciembre de 2016

Errare humanum est


Durante el semestre académico que recién terminó (agosto-diciembre de 2016) tuve la oportunidad de dar el curso de Fundamentos de programación. Esta materia la he impartido pocas veces, sin embargo los aprendizajes que me llevo como educador siempre son muy valiosos. Es muy interesante trabajar con alumnos que recién comienzan su carrera profesional y que también están iniciando su inmersión al mundo de la programación de computadoras.

Grupo 4 de la materia
Fundamentos de programación del ITESM CEM,
semestre agosto-diciembre de 2016.

Cuando se comienza a aprender algo nuevo es de esperarse que se cometerán errores. Como dijo alguna vez el renombrado escritor irlandés Bram Stoker:
Aprendemos de los fracasos, no de los éxitos.
Errar es humano, y en esta entrada del blog de EduPython comentaré, a partir de mi experiencia, sobre los cuatro errores más frecuentes que cometen los alumnos cuando están aprendiendo a programar usando el lenguaje Python, así como algunas recomendaciones de cómo evitarlos.

Paréntesis mal balanceados

Este es el error más común que he observado al inicio del curso. Se tiene una instrucción la cual requiere del uso de paréntesis anidados, por ejemplo:
print(sqrt(x ** 2 + y ** 2)
En el código anterior se puede observar que está faltando cerrar un paréntesis al final. Al intentar ejecutarlo, el intérprete de Python marca usualmente un error de sintaxis. La cuestión es que dicho error se marca en la siguiente línea, no en la línea donde está realmente el problema.

Aquí recomiendo dos cosas:
  1. Acostumbrar a los alumnos a buscar los errores de sintaxis no solo en la línea donde se señala el problema, sino también una o varias líneas antes.
  2. Enseñar a los alumnos a comprender y utilizar las pistas visuales que brindan los editores al momento de cerrar paréntesis, llaves y corchetes. En el caso del IDLE, cuando se cierra alguno de estos elementos (paréntesis, llaves y corchetes) se ensombrece toda la expresión desde el elemento correspondiente de apertura. En la siguiente imagen puede uno notar que falta cerrar el paréntesis de la función print():

    IDLE

    Otros editores, como Spyder, parpadean o usan un color de fondo diferente para destacar los paréntesis que se están abriendo y/o cerrando: 

  3. Spyder

Indentación incorrecta

En Python la indentación sirve para indicar que un grupo de instrucciones conforman un mismo bloque. Este efecto se logra con llaves ({ y }) o las palabras reservadas begin y end en otros lenguajes.

La indentación obligatoria de Python es algo positivo, ya que obliga a los alumnos a adquirir prácticas de escritura de código legible. Normalmente los editores para código de Python ayudan a producir programas con la indentación correcta sin requerir mucho esfuerzo. Sin embargo se pueden producir errores cuando se copia y pega código de otro lado o cuando se insertan o borran espacios de manera no intencional, por ejemplo:

Programa con error de indentación

En el programa de arriba, la línea 5 está indentada con un espacio más hacia la derecha con respecto a las otras líneas del mismo bloque (las líneas 3 y 8). El editor, Spyder en este caso, nos echa la mano al señalar que hay un problema en el programa colocando un signo de admiración en el margen izquierdo en la línea infractora.

Mis recomendaciones para evitar este error:
  1. Enfatizar a los alumnos sobre la importancia que tiene la indentación y la correcta alineación de las instrucciones para establecer la estructura lógica de sus programas. Si van a copiar y pegar código, se deben asegurar que todas las instrucciones queden alineadas en el lugar debido.
  2. Nunca mezclar en un mismo programa caracteres de espacio (código ASCII 32) con caracteres de tabulador (código ASCII 9). Esto no debe ser problema en la mayoría de los editores que soportan Python. Cuando el usuario presiona la tecla «Tab», usualmente los editores insertan cuatro espacios en lugar de insertar el carácter de tabulador. Sin embargo este comportamiento se puede cambiar en la configuración de la mayoría de los editores. Yo recomiendo no alterar la configuración en este sentido ya que no es fácil distinguir a primera vista un tabulador de una secuencia de varios espacios.

Uso incorrecto de return

Las funciones son uno de los mecanismos de abstracción más poderosos que tenemos disponibles en Python y en casi cualquier otro lenguaje de programación. Yo generalmente cubro este tema desde muy temprano en el curso de Fundamentos de programación. Cuando los alumnos comienzan a escribir sus propias funciones el principal error que observo es el uso incorrecto de la instrucción return. Los errores caen en alguno de los siguientes dos casos:
  • return inexistente. Esto sucede cuando una función implementa correctamente un cierto algoritmo, pero carece de la instrucción return necesaria para devolver el resultado correspondiente. Por ejemplo, la siguiente función cuenta la cantidad de números negativos contenidos en una lista:
    def cuenta_negativos(a):
        resultado = 0
        for x in a:
            if x < 0:
                resultado += 1
    
    En Python todas las funciones siempre devuelven un valor. Por omisión devuelven None, a menos que explícitamente se indique otro valor dentro de una instrucción return. En el ejemplo anterior no hay un return explícito, así que la función siempre devuelve None. Por tanto, la forma correcta de escribir cuenta_negativos es:
    def cuenta_negativos(a):
        resultado = 0
        for x in a:
            if x < 0:
                resultado += 1
        return resultado
    
    Una variación de este problema es cuando una función tiene varias condiciones que utilizan instrucciones return pero no son exhaustivas. Por ejemplo, analicemos la siguiente función:
    def positivo_o_negativo(x):
        if x < 0:
            return 'negativo'
        if x > 0:
            return 'positivo'
    
    Esta función devuelve las cadenas 'negativo' o 'positivo' si el parámetro x es menor o mayor a cero, respectivamente. Sin embargo, ¿qué pasa si x es igual a cero? Tenemos aquí un código en donde las condiciones no son exhaustivas produciendo un bug muy sutil. Al no cumplirse alguna de las dos condiciones la función termina sin ejecutar un return explícito, por tanto devuelve None. Hay varias formas de corregir el problema. Una de ellas consiste en suponer que si un número no es negativo debe ser entonces positivo. Agregando una cláusula else al if tenemos:
    def positivo_o_negativo(x):
        if x < 0:
            return 'negativo'
        else:
            return 'positivo'
    
    Con este cambio la función jamás devolverá None siempre que x sea un valor numérico.
  • return fuera de lugar. En este caso el error consiste en incluir la instrucción return dentro de la función pero en un lugar que hace que el flujo de ejecución termine de manera prematura. Continuando con el ejemplo de la función cuenta_negativos, a menudo veo que algunos alumnos escriben erróneamente el código así:
    def cuenta_negativos(a):
        resultado = 0
        for x in a:
            if x < 0:
                resultado += 1
                return resultado
    
    La instrucción return se ejecutará cuando la condición del if resulte verdadera. El return provoca que la función termine inmediatamente, por lo que las iteraciones pendientes del ciclo for ya no se ejecutarán. En otras palabras, la función devuelve el valor 1 en cuanto encuentra la primer número negativo en la lista a. Si a no tiene números negativos el for concluye de manera normal, y como no hay posteriormente un return explícito entonces devuelve None. Una variación, igualmente incorrecta, es ésta:
    def cuenta_negativos(a):
        resultado = 0
        for x in a:
            if x < 0:
                resultado += 1
            return resultado
    
    Aquí el return es parte del bloque del for. Esto quiere decir que la función siempre termina en la primera iteración del ciclo, devolviendo uno si el primer número de a es negativo, o cero en caso contrario. Como caso especial, si la lista a está vacía devuelve None, ya que el ciclo for termina de manera normal y después no hay return explícito. Podemos observar que la única diferencia sintáctica entre la versión correcta y los dos códigos erróneos es el nivel de indentación que tiene la instrucción return.
Para evitar errores asociados al uso incorrecto del return recomiendo:
  1. Resaltarle a los alumnos la manera en que trabaja la instrucción return. Es muy importante que entiendan que cuando se ejecuta esta instrucción la función termina de manera inmediata sin importar que estuviera en medio de un ciclo o que existieran otras instrucciones posteriores pendientes a ser ejecutadas. Si una función está devolviendo None seguramente está haciendo falta un return explícito en algún lado.
  2. Promover que los alumnos realicen pruebas de escritorio de sus funciones, utilizando diferentes valores de entrada y procurando revisar de manera exhaustiva los casos normales y extremos.

Variables fuera de alcance

Para definir una variable en Python basta con asignarle un valor inicial. Si esta asignación ocurre dentro de una función, la variable en cuestión es local a dicha función y eso significa que no se puede acceder a ella desde otras funciones. Las variables locales son algo bueno ya que tienen un alcance y tiempo de vida limitado a una región usualmente reducida de código. Esto conlleva a que una función sea, en principio, más fácil de entender y modificar de manera aislada. Sin embargo, es bastante común que algunos principiantes intenten acceder a variables que están fuera de alcance (definidas en otra función),  por ejemplo:
from math import sqrt

def hipotenusa():
    return sqrt(a ** 2 + b ** 2)

def principal():
    a = float(input('Primer cateto: '))
    b = float(input('Segundo cateto: '))
    c = hipotenusa()
    print('La hipotenusa es', c)
La función principal() define tres variables locales: a, b y c. La función hipotenusa() intenta usar dos de esas variables (a y b) produciendo un error ya que no están visibles en ese contexto. La forma más sencilla de corregir este problema es enviar como parámetros aquellos datos que deseemos compartir:
from math import sqrt

def hipotenusa(a, b):
    return sqrt(a ** 2 + b ** 2)

def principal():
    a = float(input('Primer cateto: '))
    b = float(input('Segundo cateto: '))
    c = hipotenusa(a, b)
    print('La hipotenusa es', c)
Los parámetros a y b de la función hipotenusa() reciben una copia de los valores de las variables locales a y b de la función principal(). Aquí utilizamos los mismos nombres (a y b) en ambas funciones por mera conveniencia, pero no tiene que ser así.

Mi recomendación para evitar problemas con variables fuera de alcance consiste en hacerles entender a los estudiantes cuáles son las implicaciones de que una variable sea local a una función. Así mismo, es necesario explicarles que cuando un programa requiere compartir información entre dos funciones se pueden utilizar parámetros para enviar datos de entrada y la instrucción return para devolver datos de salida. Esta es generalmente la manera más adecuada de diseñar un programa pues lo hace más legible y facilita su mantenimiento. Alternativamente, es posible usar variables globales o incluso definir funciones más extensas (por ejemplo unir hipotenusa() y principal() en una sola función). Sin embargo estas opciones provocan normalmente que el código sea más complicado de entender y modificar, por lo que conviene disuadir a los estudiantes de hacer esto, especialmente en las primeras etapas de un curso introductorio de programación.

Conclusión

En mi experiencia, los problemas más usuales que tienen los estudiantes al comenzar a programar en Python se pueden resumir en:
  • Falta de comprensión clara sobre algunos conceptos fundamentales (indentación, instrucción return, alcance de las variables).
  • Falta de conocimiento de cómo usar las herramientas disponibles (principalmente el editor).
Los alumnos aprenden a evitar caer en ciertas trampas al momento en que entienden bien los conceptos y obtienen más experiencia usando las herramientas apropiadas.

Amigo lector, si eres maestro y enseñas a programar, ¿qué tipo de errores has visto que tus alumnos cometen frecuentemente? ¿Cómo le haces para evitar que los cometan? Usa la sección de comentarios para compartir tus experiencias. Gracias de antemano.

14 de octubre de 2016

Aprendizaje vivencial con juegos de estrategia

En la semana del 26 al 30 de septiembre de 2016 se llevó a cabo en el Tecnológico de Monterrey la segunda edición de un evento denominado “Semana i”. Una vez por año, durante una semana completa, se suspenden todas las clases de nivel licenciatura en la institución con el fin de que los estudiantes afronten y resuelvan un reto de tiempo completo:
El aprendizaje basado en retos se fundamenta en el aprendizaje vivencial, enfoque pedagógico que forma parte del Modelo Educativo Tec21 con el que se involucra activamente al estudiante en una situación o problemática real, relevante, y de vinculación con el entorno. Semana i implica la definición de un reto, su análisis y resolución y la implementación y evaluación de una solución. Así, se desarrollan o fortalecen las competencias profesionales y personales de los alumnos1.
En la Semana i 2016 más de 50 mil alumnos y 3 mil profesores participaron en 1,400 proyectos que se llevaron a cabo en 26 campus del Tecnológico de Monterrey. Cada alumno tuvo la opción de llevar a cabo su reto en su propio campus, en otro campus, en México o incluso en el extranjero.


Reto IA con juegos de estrategia

Para la Semana i 2016 el profesor Roberto Martínez Román, colega de mi departamento, y yo diseñamos durante varios meses un reto titulado “Introducción a la inteligencia artificial orientada al diseño y programación de juegos de estrategia”. La actividad estuvo dirigido a alumnos de segundo a quinto semestre de las carreras de Ingeniero en Sistemas Computacionales (ISC) e Ingeniero en Sistemas Digitales y Robótica (ISDR). El objetivo principal del reto consistía en que los estudiantes diseñaran y programaran en Python un jugador autónomo inteligente que fuera capaz de competir en un torneo de juegos de estrategia contra los programas elaborados por sus demás compañeros.


El reto procuró fortalecer las siguientes competencias disciplinares y transversales:
  • Uso de TI para la solución efectiva de problemas
  • Pensamiento lógico y algorítmico
  • Reflexión ética
Se inscribieron 57 alumnos en total (todos del Campus Estado de México), con los que se conformaron 20 equipos compuestos de 2 o 3 integrantes cada uno. Los estudiantes tuvieron la libertad de formar sus equipos y nombrarlos a su gusto.

Alumnos y profesores en el último día del reto

A continuación realizo un recuento de todas las actividades que conformaron nuestro reto durante la Semana i 2016.

Arrancando el reto

Desde unos días antes de comenzar el reto creamos un grupo de Facebook para servirnos como principal medio de comunicación entre instructores y alumnos. Ahí estuvimos publicando anuncios y fotos durante toda la Semana i (e incluso un poco después).

Grupo de Facebook

A las nueve de la mañana del lunes 26 de septiembre dio inicio nuestro reto. Comenzamos explicando lo que planeábamos hacer, presentando la página web del reto, y dando un espacio para la conformación de los equipos de trabajo. También aprovechamos para hacer una breve introducción a los conceptos de “inteligencia artificial” y “prueba de Turing”.

Alan M. Turing, padre de la inteligencia artificial

Concurso de programación

El mismo día lunes tuvimos un concurso de programación con la intención de que los alumnos tuvieran la oportunidad de recordar un poco cómo programar con Python. Cabe mencionar que todos nuestros estudiantes ya habían trabajado con Python anteriormente, al menos durante el curso Fundamentos de programación que llevan en primer semestre, sin embargo la mayoría tenía varios semestres de no utilizarlo.


Antes del concurso invertimos una hora en un taller de preparación. Para el taller y el concurso utilizamos la plataforma de programación competitiva omegaUp.com, la cual permite crear competencias en línea de manera bastante sencilla. En el taller se utilizó el problema titulado “El mayor de dos” para que los equipos se familiarizaran con los mecanismos necesarios para resolver y subir problemas de programación usando Python.

El concurso tuvo una duración de tres horas. Cada equipo, sin importar el número de integrantes, podía utilizar hasta dos computadoras: una para programar y enviar las soluciones, y la otra para revisar la documentación. Los siete problemas del concurso fueron los siguientes:
  1. Bit de paridad (100 puntos)
  2. Calculando divisores (100 puntos)
  3. Buscando el mayor (100 puntos)
  4. Tabla de factoriales (100 puntos)
  5. Multiplicación binaria (200 puntos)
  6. Perímetro de asteriscos (200 puntos)
  7. Conociendo la moda (200 puntos)
El juez en línea se encargó de evaluar las soluciones que enviaron los equipos y de llevar la cuenta de los problemas resueltos, el tiempo transcurrido de cada envío y las penalizaciones aplicadas por envíos de soluciones incorrectas.  Al terminar el concurso teníamos una tabla con todos los equipos rankeados del primer al último lugar.

El concurso de programación

El primer equipo en resolver todos los problemas del concurso tardó solo 40 minutos. Al finalizar el concurso, trece equipos habían resuelto correctamente los siete problemas.

Framework Dagor para juegos de estrategia

El martes por la mañana les presentamos a los alumnos el framework Dagor2. Yo me encargué de diseñar y programar este framework con el fin de utilizarlo como herramienta central para este reto. Muchas de las ideas de Dagor fueron tomadas del sitio Gamesman elaborado por el profesor Dan Garcia de la Universidad de California en Berkeley. El software y la documentación de Dagor están disponibles en la página del reto. Dagor es software libre y se distribuye bajo la licencia GPLv3.

Dagor sirve para programar diversos tipos de jugadores que compiten entre sí para ganar un juego combinacional. En teoría de juegos, un juego combinacional tiene las siguientes características:
  • Siempre hay dos jugadores que toman turnos para tirar de manera alternada.
  • No hay elementos aleatorios como dados o cartas barajadas.
  • Ambos jugadores tienen información perfecta (no hay información oculta).
  • El juego es finito — debe terminar eventualmente.
  • Usualmente el último jugador en tirar gana.
Dagor es un módulo de Python que implementa el patrón de diseño conocido como método de la plantilla (Template Method). De manera muy general, para escribir el código de un jugador inteligente se requiere escribir una clase que implemente dos métodos:
  • tira: Este método se invoca automáticamente por el controlador del juego cuando le toca al código de mi jugador realizar su tiro.
  • heuristica: Este método lo implementamos para poder evaluar diversas posiciones durante el juego y decidir cuál nos conviene más al momento de realizar nuestro tiro.
El código fuente de Dagor muestra cómo implementar diversas categorías de jugadores para diferentes juegos. Hay tres categorías de jugadores:
  • Jugadores aleatorios: Realizan tiros válidos pero siempre al azar, salvo que puedan producir en un momento dado un tiro ganador.
  • Jugadores interactivos: Permiten que un usuario humano interactúe con el juego a través de la consola y verifican que los tiros seleccionados sean válidos.
  • Jugadores estratégicos: Implementan una o varias estrategias que les permitan jugar de manera inteligente.
El código de los jugadores es modular e intercambiable. Esto significa, por ejemplo, que podemos tener un juego en donde un jugador aleatorio compita con un jugador estratégico. O podemos tener un juego con dos jugadores aleatorios, o cualquier otra combinación que deseemos. Juegos como estos, siempre y cuando no involucren usuarios humanos, se llevan a cabo muy rápido (en unos cuantos segundos), de tal forma que podemos darnos el lujo de tener encuentros compuestos de muchos juegos entre los mismos dos jugadores.

El taller de Dagor

El juego de Orugas

Concretamente, el reto de nuestros alumnos fue implementar un jugador estratégico inteligente con Dagor para el juego de Orugas. A continuación se encuentran las reglas de este juego.


Las reglas

Piezas y tablero: Este juego se juega en un tablero rectangular de n renglones por m columnas, donde 4 ≤ n ≤ 10, 4 ≤ m ≤ 10. Inicialmente cada jugador ocupa una localidad del tablero (la cabeza de la oruga), determinada de manera aleatoria.

Para tirar: Cada jugador controla una oruga que crece rápidamente a partir de su cabeza. Durante el turno de un jugador, éste debe seleccionar una localidad contigua vacía en la que pueda crecer la cabeza de su oruga. La oruga puede crecer hacia la localidad de arriba, abajo, izquierda o derecha, mas no en diagonal. Es posible crecer saliéndose de una orilla del tablero y llegar a su correspondiente localidad opuesta.

Para ganar: Un jugador gana cuando su oponente ya no tenga una localidad donde crecer.

En el tablero, el primer jugador usa el símbolo B (blanco) como cabeza y b para el resto del cuerpo. El segundo jugador usa el símbolo N (negro) como cabeza y n para el resto del cuerpo.

Ejemplo

El siguiente es un tablero de Orugas de 4 renglones por 5 columnas, donde las posiciones iniciales de B y N son determinadas de manera aleatoria:
   0   1   2   3   4 
0    |   |   |   |   
  ---+---+---+---+---
1    |   |   |   |   
  ---+---+---+---+---
2    |   |   | N |   
  ---+---+---+---+---
3    | B |   |   |   
B tira primero. Tiene la opción de moverse a la izquierda, a la derecha y hacia arriba. También puede moverse hacia abajo, pero como está en la orilla se iría al extremo opuesto del tablero (renglón 0, columna 1). El jugador decide moverse hacia arriba al renglón 2, columna 1:
   0   1   2   3   4 
0    |   |   |   |   
  ---+---+---+---+---
1    |   |   |   |   
  ---+---+---+---+---
2    | B |   | N |   
  ---+---+---+---+---
3    | b |   |   |   
Ahora es el turno de N y se mueve hacia abajo al renglón 3, columna 3:
   0   1   2   3   4 
0    |   |   |   |   
  ---+---+---+---+---
1    |   |   |   |   
  ---+---+---+---+---
2    | B |   | n |   
  ---+---+---+---+---
3    | b |   | N |   
El juego continúa de esta forma, alternando los turnos entre B y N. Finalmente, en el tiro número 11, el jugador B se mueve al renglón 0, columna 2:
   0   1   2   3   4 
0  b | b | B |   |   
  ---+---+---+---+---
1  b | N | n |   |   
  ---+---+---+---+---
2  b | b | n | n |   
  ---+---+---+---+---
3    | b | n | n |   
En este momento el jugador N está completamente bloqueado por lo que no puede moverse a ningún lado. Por tanto N pierde en este turno y B resulta el ganador de este juego.

Desarrollando el jugador estratégico

El tiempo con el que contaron los alumnos para diseñar e implementar su jugador estratégico fue relativamente corto: desde el martes por la tarde hasta el jueves por la mañana.

En esencia, los alumnos tuvieron que programar una manera de evaluar qué tan buenas o malas eran ciertas posiciones en el juego y seleccionar la que les daba una mejor ventaja en cada tiro. Podían considerar, por ejemplo, qué tantas opciones de movimiento quedaban disponibles para su jugador después de un tiro, o qué tanto podían alejarse del jugador contrario con el fin de evitar un posible bloqueo más adelante. Hubo un equipo que diseñó una estrategia para cuando eran los segundos en tirar, y ésta consistía en imitar como espejo los tiros del contrario bajo el supuesto de que perdería primero por bloquearse solo. 

El miércoles antes del medio día los instructores realizamos una revisión del avance de cada equipo con el fin de verificar que estuvieran trabajando adecuadamente y resolver cualquier duda que pudieran tener. Ese mismo día por la tarde tuvimos un pre-torneo voluntario. Los equipos que quisieron pudieron competir entre sí para tener una idea más clara de qué tan efectivo estaba siendo su código. Seis de los veinte equipos participaron en este pre-torneo.

A todos los estudiantes se les permitió observar el pre-torneo, sin importar si su equipo estaba participando o no. Sin embargo, no se permitió que los equipos vieran el código de los demás, solo que vieran los resultados de los encuentros. El pre-torneo también sirvió para que los instructores nos percatáramos de las dificultadas logísticas que podrían llegar a presentarse al día siguiente en el torneo real. Nos dimos cuenta de que era necesario automatizar el proceso del torneo lo más que pudiéramos, de otra forma resultaría demasiado tardado.

El pre-torneo

Torneo de estrategias

El momento de la verdad por fin llegó. El jueves a las 11 de la mañana nos dimos cita en el salón de congresos del campus para llevar a cabo el torneo de estrategias. Los equipos debían habernos enviado por correo electrónico sus programas antes de la hora señalada. Cada programa se inspeccionó manualmente para verificar que fuera un módulo válido de Python y que no tuviera código malicioso o instrucciones prohibidas (acceso a Internet, manipulación de archivos, creación de procesos externos, etc.).

Se definió al inicio del torneo que cada encuentro consistiría de 100 juegos en un tablero de 7 renglones por 6 columnas, en donde cada tiro debería efectuarse en menos de 3 segundos. Durante el desarrollo del proyecto los alumnos desconocían el tamaño que tendría el tablero durante el torneo, solo sabían que los renglones y columnas estarían entre 4 y 10. El turno inicial de cada juego se iría alternando entre los dos jugadores, es decir, el primer jugador comenzaría 50 juegos del encuentro y el segundo jugador comenzaría los otros 50.

Inicialmente el programa de cada equipo se enfrentó al jugador aleatorio provisto por Dagor. A los equipos que ganaran al menos 60 de los 100 juegos de este encuentro se les brindarían 20 puntos extra sobre la calificación final del reto. De nuestros veinte equipos, solo dos no lograron cubrir este objetivo.

Cerca de la una de la tarde, y después de resolver varias dificultades técnicas, comenzamos el torneo de todos contra todos. En total hubo 190 encuentros (el número de combinaciones de 20 elementos tomando 2 a la vez) cada uno consistiendo de 100 juegos. Cada equipo recibió una cierta cantidad de puntos dependiendo del resultado de cada encuentro:
  • 3 puntos si ganó 51 juegos o más
  • 1 punto si ganó exactamente 50 juegos
  • 0 puntos si ganó 49 juegos o menos
El ranking de cada equipo en el torneo se determinó por el total de puntos obtenidos.

Se utilizaron múltiples scripts para automatizar todo el proceso del torneo, desde la ejecución de los encuentros hasta la contabilización de los resultados. La mitad (aproximadamente) de los encuentros se corrieron en la computadora portátil del profesor Roberto Martínez, y la otra mitad en mi computadora. Se utilizó una tercera computadora para ir proyectando a la audiencia los resultados conforme se iban obteniendo3.

Después de un poco más de dos horas, el torneo había terminado. El equipo que obtuvo el primer lugar investigó e implementó el algoritmo minimax con poda alfa-beta. Esto le permitió ser el vencedor en sus 19 encuentros, 7 de los cuales ganó 100 juegos a 0. El segundo lugar solo perdió un encuentro, y el tercer lugar perdió dos.

El torneo

Conferencia del Dr. Edgar Vallejo

El viernes estuvo mucho más relajado que los días anteriores. Ese día tuvimos el honor de contar con la presencia del Dr. Edgar Emmanuel Vallejo Clemente, profesor de nuestro departamento, quien nos presentó la conferencia titulada “Predicción de epidemias de Zika usando redes sociales y redes de contacto vectorial”. 

El profesor Edgar Vallejo en su conferencia

La plática fue sobre el proyecto que está siendo elaborado por el Dr. Vallejo y su alumno de doctorado Héctor Manuel Sánchez Castellanos. El objetivo es que a través de un modelo informático sea posible estimar el riesgo de infectarse con el virus del Zika. Este modelo permitirá que se haga una predicción temprana de brotes para que se tomen las medidas necesarias para el control de la enfermedad. El proyecto fue uno de los ganadores los Premios de Investigación en América Latina de Google 2016. El premio consistió en un apoyo económico para que los involucrados puedan llevar a cabo sus investigaciones.

Reflexión ética

El mismo día viernes les proyectamos a los alumnos la película “Ex Machina (2015)”.  Esta reciente cinta británica es una excelente película de ciencia ficción que trata sobre un joven programador que es seleccionado para ser el juez en una prueba de Turing, en donde deberá evaluar las cualidades humanas presentes en la I.A. de un asombroso robot humanoide con forma femenina. El filme permite abrir una discusión y reflexión muy interesante sobre los conflictos morales que podríamos afrontar al interactuar con máquinas que se comportan con una inteligencia similar a la de los seres humanos.

Alicia Vikander, Domhnall Gleeson y Oscar Isaac
protagonizan “Ex Machina (2015)”, A24 Films.

Al finalizar la película, les pedimos a los alumnos que respondieran en equipo a las preguntas de discusión de RameyLady tomadas de su sitio web Just:Words.

Premiación

Como última actividad del día viernes, entregamos reconocimientos a los equipos que obtuvieron los primeros tres lugares tanto en el concurso de programación como en el torneo de estrategias. Y con esto clausuramos nuestro reto de la Semana i.

Los profesores responsables del reto, Ariel Ortiz y Roberto
Martínez, con el equipo nokygerand, ganador del
primer lugar tanto del concurso de programación como
del torneo de estrategias. El equipo está integrado
por Andrés de Lago Gómez y  Gerardo Galván Olvera,
ambos alumnos de tercer semestre de la carrera ISC.

Documentando la experiencia

Durante toda la semana cada equipo fue documentando en su propio blog las impresiones y experiencias de las actividades que se fueron realizando. A continuación se listan los blogs de todos los equipos:

Conclusión

Nuestra Semana i 2016 resultó, tanto para los alumnos como para los instructores, un evento bastante agotador pero con un saldo final positivo. Me sentí muy complacido con los resultados obtenidos. Los comentarios de la mayoría de los alumnos fueron en general muy favorables.

Este fue un comentario bastante alentador tomado del blog del equipo rebelcoder:
Este reto me resultó grato desde muchos aspectos y considero que también me llevo muchas experiencias y aprendizajes nuevos. Los principales aprendizajes que me llevo tienen que ver con el “diseño e implementación de una solución de software ante un problema”, ya que la mayoría de los proyectos en los cuales he estado normalmente sabíamos el proceso para llegar a la solución o conocíamos la solución y aplicábamos reingeniería. En cambio en este reto no conoces el proceso para llegar al final ni tampoco la solución final para el problema del juego de Orugas, generando que nosotros tuviéramos que idear muchos algoritmos, casos de prueba y comparar otras soluciones para ver cuál era la mejor.
Este reto de la Semana i enfocado en el desarrollo de una Inteligencia Artificial en un Juego de Estrategia me gustó mucho y la verdad me agradaron mucho todas las actividades que hicimos. Lo que más me emocionó fue el torneo en sí cuando nosotros veíamos los resultados de cada uno de los enfrentamientos entre nuestros códigos y al final cuando nos enteramos del lugar en que quedamos. Gracias profesor Ariel y profesor Roberto por haber realizado esta actividad para nosotros.
— Servio Reyes
Hubo algunos detalles que nos señalaron varios participantes en cuestión de posibles mejoras. Un tema recurrente fue el manejo de los tiempos, al parecer muchos equipos hubieran querido contar con más tiempo para implementar su jugador estratégico. Algunos también sugirieron que se le permitiera al equipo ganador exponer ante los demás la manera en que diseñaron su solución. Me parece que vale mucho la pena considerar estos comentarios para mejorar el reto para cuando se llegue a ofrecer nuevamente en un futuro.


1 Inicia la segunda edición de Semana i con más retos en movimiento.

2 La palabra Dagor significa “batalla” en el idioma sindarin (conocido también como élfico gris), creado por el escritor británico J. R. R. Tolkien, autor del “Señor de los anillos”.

3 Lo que se estuvo proyectando realmente eran los resultados de unos comandos de SQL que tecleaba en el momento para consultar la base de datos con los resultados. Por falta de tiempo no pude automatizar esta parte del proceso.