Dedicado a mis proyectos en Gambas ,un lenguaje de programación parecido al Visual Basic + Java pero ampliamente mejorado y...¡¡para gnu/linux!!.La potencia del gnu/linux sumada a la facilidad del Basic



Consultas, Desarrollo de programas y petición de presupuestos:



Mostrando entradas con la etiqueta conecta4. Mostrar todas las entradas
Mostrando entradas con la etiqueta conecta4. Mostrar todas las entradas

martes, 8 de octubre de 2013

Juego Conecta 4: Paso a Paso (6) Formularios

Juego Conecta 4: 

Paso a Paso (6) 

Formularios



Formulario: FormInicio
Este formulario tiene la misión de iniciar el formulario de  FormJugadores, donde obtendrá los datos de los jugadores (humano/ordenador y nivel), y se los pasará al formulario FmainTablero, mostrandolo para que se inicie el juego.



Al pulsar el botón de "Nueva Partida", lo que hace es definir dos variables del tipo variant[] y string[], las cuales las vamos a pasar al formulario de jugadores,  estas variables al ser array se pasan por referencia (y no por valor), lo cual nos es necesario para que cuando sean modificadas por el usuario en el formulario de jugadores, esa información la pasemos al formulario del tablero.


El formulario FormJugadores:
Usando Combobox, nos permite elegir el tipo de jugador y la dificultad (1 a 4).



El formulario FmainTablero:
Es el encargado de mostrar la partida (tanto gráficamente), como rellenando los textarea para mostrar los movimientos. Tambien se va  a encargar de captar el evento click del usuario en el tablero gráfico (drawingArea), para informar al programa de en que columna ha hecho click.










La parte del código más interesante es esta:


Ya que permite "escuchar" la acción que hace el usuario en el drawingArea, (pulsar el botón del ratón), y modifica dos variables (col[] y producidoclick[]), que a la vez están usadas en el bucle del método juega de la clase ClassJugadorHumano (que se le han pasado como parametros de la función), permitiendo así, actuar según la columna pulsada.
Nota:
La sentencia Wait 0.005, es muy importante, ya que hace que haya una pequeña "parada" a la hora de ejecutar el bucle while...wend, y esa pequeña "parada" dentro del bucle, hace que se pueda escuchar las acciones del usuario (la pulsación del botón del ratón). Si no hubiera esa pequeña parada, el bucle se continua ejecutando indefinidamente, sin escuchar, y por lo tanto se no modificarían nunca las variables producidoclick ni col.

viernes, 4 de octubre de 2013

Juego Conecta 4: Paso a Paso (5) El módulo de Inteligencia Artificial. Algoritmo Negamax

Juego Conecta 4:

 Paso a Paso (5) 

El módulo de Inteligencia Artificial. Algoritmo Negamax



La inteligencia artificial de este programa consiste en evaluar las posiciones que se producen con las distintas jugadas (tantos las de un jugador como las del otro,  ordenandolas por niveles) y elegir la que más nos beneficie. Basicamente, consiste, en ver cuales jugadas puedo hacer, evaluar las que podria hacer el contrario, volver a evaluar mi respuesta a la del contrario, y asi hasta cierto nivel o profundad.

Por lo tanto vemos que hay dos partes bien diferenciada:
-La parte de evaluar el "arbol" de las distintas jugadas y sus respuestas, y elegir entre ellas la que más nos beneficie.
-La evaluación de la jugada, osea del tablero que queda cuando ejecuto esa jugada.
Código del negamax


Cosas que hace este código por orden:
1) Obtiene el numero de jugadas posibles del tablero que recibe
2) Crea un tablero auxiliar que es copia del que recibe
3) Entra en un bucle que se repite tantas veces como numero de jugadas posibles se puedan hacer
4) Hace una jugada (inserta una ficha en el tablero auxiliar)
5) Comprueba si es GamerOver o la profundidad del análisis es 0,
5.1) Si es así, evalúa la jugada y retorna su valor.
5.2) Sino:
5.2.1) vuelve a llamar a la funcion negamax, pasandole el nuevo tablero, el jugador contrario (lo multiplica por -1), una profundidad menor, y dos valores -beta, y -alfa_local, que sirven para "podar el arbol de opciones"
5.2.2) Evalúa la puntuación que se obtiene
5.2.2.1) Si es mayor su puntuación a la máxima puntuación, la jugada es elegida como posible mejor jugada
5.2.2.2) Sino, si es igual, de elige aleatoriamente a una de ellas. Esto se hace para que el juego sea un poco aleatorio, al no elegir siempre la misma jugada que tenga la misma puntuación que otras.
5.2.3) Hace una poda alfa-beta, para dejar de revisar jugadas, si superan el valor de beta.

Código del EvaluaJugadaMov

Evaluar una posición de las fichas del  tablero producido por una jugada, consiste en averiguar cuantas veces se han formado grupos de 2, 3 o 4 fichas del mismo color en horizontal, vertical o diagonal (esto se hace usando el método comprueba_linea del objeto tablero).
En principio el valor viene dado por la formula:
 valor_jugada = (4 * n2 + 9 * n3 + 100000 * n4)
Siendo:
n2: numero de grupo de 2 fichas del mismo color en horizonta, vertical o diagonal
n3: numero de grupo de 2 fichas del mismo color en horizonta, vertical o diagonal
n4: numero de grupo de 2 fichas del mismo color en horizonta, vertical o diagonal

Se ha simplificado un poco el algoritmo de cálculo, ya que si se forma un grupo de 4 fichas,  es un tablero ganador, y por lo tanto, no hace falta calcular los otros grupos (n2 ni n3).

En la próxima entradas explicaremos como se ha realizado los formularios (el modo gráfico)





Juego Conecta 4: Paso a Paso (4) Las clases para los jugadores

Juego Conecta 4: 

Paso a Paso (4) 

Las clases para los jugadores





Como dije en anteriores entradas tenemos una clase ClassJugador de donde van a heredar las clase ClassHumano y ClassOrdenador.

La clase ClassJugador, contienen las cosas "comunes" a las otras dos:


En concreto contiene dos propiedades:
- numero: indica que jugador es (amarillo (-1), naranja (1) )
- profundidad: indica la inteligencia que tendrá el jugador

La clase ClassJugadorHumano()
Tiene el método juega, que hace que cuando se produzca un click en el tablero, lea la variable col[0] que contiene la columna donde se ha hecho el click.
Las variable ProducidoClick y col son arrays, ya que de esa manera se pasan por referencia (y no por valor), haciendo que cuando cambian en alguna parte del programa, también cambien en el bucle While... Wend que tenemos en el método. Podiendo asi recoger la información de la columna y ejecutando el método insertar ficha en dicha columna.

La clase ClassJugadorOrdenador()
En esta clase, el método juega, esta definido para hacer uso del algoritmo negamax, el cual devuelve la mejor jugada dado un tablero.

Además actualiza datos de los  textlabel y textarea que recibe en la llamada (para actualizar la información que aparezca en el formulario)





Juego Conecta 4: Paso a Paso (3). La Clase ClassTablero(2)

Juego Conecta 4: 
Paso a Paso (3).
La Clase ClassTablero(2)

Seguimos con los métodos de esta clase:



El método ganador:
Es el encargado de retornar se ha habido ganador, y cual ha sido.


Como hay dos jugadores, uno de ellos se identifica por -1 y el otro por +1. 
De igual modo. las fichas (como solo hay un tipo), se van a identificar por 1 añadiendole el signo del jugador (- o +).

Para hacer esto, hace uso de método comprueba_linea_Cuatro.

El método comprueba_linea_Cuatro():
Es una simplificación del método comprueba_linea, ya que solo va devolver que jugador ha formado una linea de 4 fichas continuas (ya sea horizontal, vertical o diagonal), y si lo encuentra, sale del método inmediatamente.



Os dejo aquí un esquema de la comprobación de las diagonales:


El método comprueba_linea()
Es un método mas "general" que el anterior y sirve para contar las lineas que se han formado por las fichas del jugador en el tablero, se le pasa el valor a comprobar (2,3, 4 fichas) y devuelve el número de las que hay formada en el tablero actual. Es más lento que el anterior, ya que comprueba todas las posibilidades (no sale automaticamente cuando encuentra una fila formada por el numero indicado de fichas),



El método comparaCuartetaElemento()
Se encarga de comprobar si la  cuarteta que recibe tienes todos sus valores iguales a otro valor recibido.



Nota:
El que se llame cuarteta, no quiere decir que sean 4 elementos, sino que como máximo va a tener 4 elementos.




Juego Conecta 4: Paso a Paso (2). La Clase ClassTablero(1)

Juego Conecta 4:

 Paso a Paso (2). La Clase ClassTablero(1)


En esta entrada iré comentando las clases principales y su código.
Se va a comentar el código de la version 0.0.4 (se encuentra en la entrada http://jsbsan.blogspot.com.es/2013/09/juego-conecta4-inteligencia-artificial.html , al final de la entrada en Notas: Actualizaciones 3/10/2013).



Empezamos con la clase:
ClassTablero.
Propiedades:
El tablero lo vamos a definir como una matriz de 7x7 elementos, y usaremos un array de dos dimensiones del tipo integer, para manejarlo en el programa. Lo definimos como la propiedad "casillas":



- Como detalle importante, la propiedad se define como Integer[], pero la variable privada hcasillas hay que definirla como de dos dimensiones y con el tamaño de la matriz: New Integer[7,7]

Métodos:
El método _New: 
Es el "método constructor" y  va a ser llamado cada vez que instanciamos una nueva clase ClassTablero.


- Como veis, en la definición tiene un parámetro opcional, que nos va a permitir que el método se comporte de distinta forma si le pasamos un argumento ("t" es otro objeto del tipo ClassTablero) o si no le pasamos ningún argumento (toma valor nulo).
Si el no hay argumento pasado, "t" toma el valor Null y hacemos que todos los valores de las casillas se pongan a 0. Esto indica que la casilla esta vacía. Para recorrer las casillas una a una, usamos dos bucles For...Next.
En caso contrario, que se haya pasado argumento, lo que se hace es copiar los valores del objeto "t" a la variable interna hcasillas, (que es la que usa la propiedad casillas)
Con esto conseguimos que este método nos sirva tanto para iniciar un tablero en blanco (complentamente vacio) como que nos permita copiar un tablero que nos pasen como argumento.

El método muestra:
Incialmente este médodo lo diseñe para mostar el estado del tablero en la consola (por eso hay tantas ordenes print comentadas). Pero luego lo use para que me devolviera el array de las casillas.


El método copia:
Sirve para devolver una copia de las casillas del actual objeto., Este método esta en des huso, porque su función la puede hacer también el uso del constructor _New,



El método insertaficha:
Es el encargado de colocar la ficha en el tablero, indicándole como argumentos la columna donde hay que colocar la ficha y el jugador al que pertenece. El se encarga de comprobar cual es la primera casilla de la columna que este vacia y cuando la encuentra, asigna el valor de la casilla  al jugador.



- Es importante que veais cual es el criterio seguido para insertar una ficha. Lo podemos ver en estos esquemas:
Matriz [ i, j ]: sentido de la caida de una ficha
Matriz [i , j]: ejemplo del llenado de una columna


Como podéis ver el en código, el dato de  la columna esta en el 2º termino de la matriz. Y es el primer termino de la matriz es el que va tomando los valores empezando desde el 6 hasta llegar a 0, buscando la celda que esté vacía (cuyo valor sea 0). 
Si la encuentra vacía, cambia el valor de la celda al jugador dado, y además cambia la variable ok a True, saliendo del bucle (orden Break) y devolviendo el valor de la variable ok  (Return ok).
Como veis el "relleno" matemático de las columnas del  tablero se va haciendo de derecha a izquierda.

Nota:
Como veis el tablero esta "girado" 90º hacia la izquierda, y esto es importante saberlo, para tenerlo en cuenta a la hora de representarlo gráficamente.

¿para que se usa la variable "ok"?
Si una columna esta llena,  la variable "ok", mantiene su valor inicial (definida como False), y devolverá False, esto nos va a indicar que no ha sido posible poner una ficha en dicha columna.
Si esta función no devolviera ningún valor, no sabríamos si ha sido posible insertar la ficha en el tablero.

Nota:
Como veis el tablero esta "girado" 90º hacia la izquierda, y esto es importante saberlo, para tenerlo encuenta a la hora de representarlo gráficamente.

El método deshacer:
Deshace un movimiento (quita la ficha) sabiendo la columna donde fue insertada.


-Mediante un bucle For...Next, va recorriendo la columna de izquierda a derecha (i=0,1,2,3...6), hasta encontrar una casilla que no este vacia (<>0) , y la deja vacía (me.casillas[i,columas]=0).
Una ver hecho esto, sale del bucle y del método.


Método GameOver()
Este método se encarga de indicar si se ha terminado la partida. La partida termina cuando uno de los dos jugadores ha ganado o se ha producido tablas.


- Primero lo que hace es comprobar si hay ganador que se sea igual a a "0", comparando la salida del  método .ganador() con "0". Si es asñi hace un recorrido por el tablero, para averiguar si hay casillas vacias, si es asi , devuelve "False"  , para indicar que no hay ganador.
En el caso de que haya algun ganador (1 o -1) devuelve TRUE



El método jugadasPosibles()
Es el método que nos devuelve las jugadas que son posible.





 Esto lo hace, comprobando la última fila del tablero. Como lo tenemos girado 90º matemáticamente, es la siguiente:

Las jugadas que sean posibles, se pueden devolver con un array integer[] (de una dimensión), ya que indicaría las columnas libres, que pueden ser usadas para insertar una ficha.

En la próxima entrada veremos los métodos :

  • ganador()
  • comprueba_linea()
  • comprueba_linea_Cuadro()
  • comparaCuaternaElemento()

Saludos
 
Nota:
Diferencia entre un parámetro y un argumento:
Parámetros
  • Un parámetro representa un valor que el procedimiento espera que se transfiera cuando es llamado.  La declaración del procedimiento define sus parámetros.
  • Cuando se define un procedimiento Function o Sub, se especifica una lista de parámetros entre paréntesis que va inmediatamente después del nombre de procedimiento. 
Argumentos
  • Un argumento representa el valor que se transfiere a un parámetro del procedimiento cuando se llama al procedimiento. El código de llamada proporciona los argumentos cuando llama al procedimiento.
  • Cuando se llama al procedimiento Function o Sub, se incluye una lista de argumentos entre paréntesis que van inmediatamente después del nombre del procedimiento. Cada argumento se corresponde con el parámetro situado en la misma posición de la lista.





martes, 1 de octubre de 2013

Juego Conecta4: Paso a Paso (1) Estructura del Proyecto

Juego Conecta4: Paso a Paso (1)

Estructura del Proyecto


Como dije en la primera entrada de Conecta4 ( enlace ), vamos a desmembrar el código fuente para estudiarlo y explicarlo como funciona.

En primer lugar vamos a ver la estructura del código fuente del proyecto, en la siguiente imagen:
Como veis tenemos:
A) Clases:
ClassJugador (clase "padre") y ClassJugdorHumano y ClassJugadorOrdenador "que heredan de de la clase ClassJugador"
ClassJugador: Es la clase "padre" de las que van a heredar la clase ClassJugadorHumano y ClassJugadorOrdenador.
Estas últimas clases, tiene el método "juega" que se comparta de manera muy diferente si pertenece a una u otra clase. En el caso de ClassJugadorHumano, el método "juega" va a esperar que se el jugador humano pulse sobre una columna del tablero, para realizar el movimiento (si es posible) en el tablero.
Sin embargo en la clase ClassJugadorOrdenador, el método "juega", usa la función negamax del módulo "ia", para elegir la jugada a realizar.

ClassPartida:
En la versión 0.0.3, es una simple clase que se usa para iniciar una nueva partida, usando el formulario FormInicio. En principio preveo en próximas versiones, añadirle la opción de guardar y abrir partidas.

ClassTablero:
Es la clase más importante, ya que contiene:
- Propiedades:
La propiedad "casillas": Posee la información de la situación de las fichas de los jugadores (representados por 1,-1 ó 0 si esta vacía) en el tablero,  es un array integer de 2 dimensiones (7x7).
- Métodos:
New: inicia un tablero y es aprovechado para iniciar las casillas con valor "0"
Muestra: devuelve una copia del array casillas (información de fichas y jugador al que perteneces), para que sea usado en las subrutinas de dibujar el tablero. En versiones anteriores, mostraba en la consola el tablero.
Copia: devuelve una copia del array casillas, que será usado para crear tableros auxiliares.
InsertaFicha: es la función que se usa para modificar el tablero cuando se inserta una ficha. En el caso que devuelva el valor "False", se debe a que no se puede introducir una ficha en la columna que se ha indicado, y si devuelve "True", se ha colocado  correctamente y sin problemas la ficha en el tablero.
GameOver(): Comprueba si se ha producido tablas o ha ganado alguien (con la función ganador).
ganador: Comprueba si un jugador tiene 4 fichas juntas (en fila,columna o diagonal) en el tablero
comprueba_linea: Funcione que devuelve el numero de lineas de fichas que hay en el tablero de un jugador
comprueba_linea_Cuatro(): comprueba si se han formado conjunto de 4 fichas en Filas/Columnas/o Diagonal.
comparaCuaternaElemento(): Compara elementos de una array con otro, para ver si son iguales todos o no.
jugadasPosibles(): devuelve una array de las posibles jugadas que se pueden realizar. Esto significa que devuelve las columnas que no están llenas y por lo tanto podemos insertar una ficha.


B) Formularios:
FormInicio:
Simple formulario que nos muestras los iconos de gambas y conecta4, con un botón que es el que inicia una partida.

FormJugadores:
Este formulario es usado para dar a elegir el tipo de jugadores Humano o Ordenador, y si es Ordenador, indicar el nivel de inteligencia del jugador. Devuelve estos datos elegidos por el usuario, que serán usados para pasárselos al Formulario FmainTablero, para configurar asi el nuevo juego.


FmainTablero: Es el formulario donde mostraremos los datos de la partida: el tablero propiamente dicho, las fichas, el listado de jugadas que se ha realizado e información sobre la clase de jugadores (humano/ordenador / nivel). Además es donde el jugador humano interactua con el dibujo del tablero para indicar en que columna coloca su ficha.




C) Módulos
ia:
Contiene las funciones relacionadas con la Inteligencia Artificial del programa.
- La función negamax es la encargada de analizar el árbol de movimientos y quedarse con la mejor jugada (incluso hace las podas de jugadas no necesarias de calcular), y se usa de un modo "recursivo" (se llama a si misma).
El árbol de movimiento, se genera a partir del tablero inicial, donde se ven que movimientos son posibles, se ejecutan estos movimientos en tableros auxiliares (copias del original) y se vuelve a usar la función negamax de modo recursivo para analizar el nuevo tablero. Este proceso se repite hasta llegar al final de la profundidad a analizar del árbol o se encuentre un tablero donde se termine la partida. Al final devuelve la mejor jugada que puede realizar el jugador.
- La función EvaluaJugadaMov es la encargada de hacer la valoración un tablero según el jugador que se le pase, valorando el numero de veces que se produce en el tablero conjuntos de 2 fichas, 3 fichas o 4 fichas, que estén en el tablero de forma diagonal, horizontal o vertical.

main: 
Es el módulo de inicio del programa (indicado por el triangulo negro, junto a su nombre). Crea una instancia de la clase "ClassPartida", y usa su método ".inicio", con lo cual hace arrancar una partida. Además contiene la variable "maxinteger", que va a ser usada como variable global, (tendrán acceso todos los módulos y clases del programa).

Utilidades: 
Las funciones que incluye este formulario son de tipo "publicas" y de uso común  (osea muy usadas en otras aplicaciones). Pero realmente no se ha usado ninguna de ellas en esta aplicación.

E) Datos:
En esta carpeta tenemos alojados las dos imagenes que usamos en la aplicación, el icono de gambas3 y la imagen del juego conecta4


Nota:
¿seria necesario una clase Jugada o estructura que definiera la Jugada a realizar?
En concreto en este juego de conecta 4, las jugadas simplemente representa la columnas donde podemos insertar una ficha. Básicamente se trata de una información de una dimensión (valor de las columnas). Por lo tanto, una jugada es un número y se puede usar un array integer para contener la lista de jugadas posibles.

Sin embargo en otros juegos más complicados (por ejemplo las damas), las jugadas van a contener más información (coordenada donde empieza y coordenada donde termina, incluso coordenadas intermedias para indicar "saltos"). En este caso si sería necesario usar variables más complicadas (por ejemplo arrays de Variant, o estructuras o directamente clases ), para que pudieran manejar la información del movimiento de la ficha o pieza.

En la próxima entrada, entraremos más profundamente en el código.

Saludos

lunes, 30 de septiembre de 2013

Juego Conecta4: Inteligencia Artificial usando algoritmo negamax


Juego Conecta4:

 Inteligencia Artificial 

usando

 algoritmo NEGAMAX




Este programa esta basado en el post de "La máquina imbencible 2" publicado en el blog http://www.divertimentosinformaticos.com/ cuyo autor es Alberto García Serrano, el cual describe el algoritmo negamax y como usarlo en  el programa para jugar al conecta 4, usando el lenguaje Python.

Yo lo que he hecho  es:
- traducirlo a Gambas3
- darle un aspecto visual (ya que en Python funcionaba en modo consola),
- Añadirle la variante de poder jugar entre Humano-Computadora  y Computadora-Computadora.
- Además lo he pasado al paradigma de la Programación Orientada a Objetos, con el fin de que la estructura del programa y las clases, os sirvan para futuros planteamientos a otros juegos de este tipo (los llamados de conocimiento perfecto: damas, ajedrez, etc).


Os dejo un video del desarrollo de una partida:






En próximas entradas de este blog, iré "desmembrando" el código fuente, para explicaros como funciona el programa "paso a paso".




Aqui teneis tanto el instalador en .deb como el código fuente.
Descarga paquete .deb: enlace
Código fuente en gambas3.4.2: enlace


Nota:
Actualizacion 3/10/2013 new
Version 0.0.4: enlace codigo fuente
 -> Corregido algoritmo de negamax, ya que en algunas ocasiones la poda era erronea.


Saludos