INTELIGENCIA EN REDES DE COMUNICACIONES PRÁCTICA FINAL BLACKJACK Ignacio Ribas Ramos Miguel Flecha Lozano Ingeniería de Telecomunicaciones 1. Explicación del juego de cartas del Blackjack El Blackjack es un juego de cartas originalmente llamado “21” y que apareció en Francia por el año 1700. Los que juegan al Blackjack no compiten entre ellos, sino contra la banca. Las dos partes se turnan para coger cartas de la baraja intentando obtener 21 puntos o un resultado mayor que el oponente, pero sin pasarse de 21. Para el cálculo de la puntuación, el palo de la carta no influye en absoluto. Tampoco influye para el desarrollo del juego. El As vale 1, pero si al cambiarlo por 11 conseguimos una puntuación igual o menor a 21, lo cambiaremos. Las figuras valen 10, y el resto de cartas tienen el valor de su número correspondiente. Para comenzar el juego se debe hacer una apuesta. Después de que se hayan hecho las apuestas, la banca reparte dos cartas a cada jugador y otras 2 a si misma. Los jugadores tienen sus cartas boca arriba y la banca tiene una boca arriba y otra boca abajo. Si un jugador logra Blackjack (21 puntos con las 2 primeras cartas) recibirá de la banca el doble de la cantidad apostada. Si después de coger cartas adicionales logra 21 puntos y gana, esto no se considera como un Blackjack, luego recibirá de la banca la cantidad apostada, no el doble. En caso de que la banca o el jugador hayan acumulado más de 21 puntos se denomina bancarrota. En este caso pierde el que obtuvo más de 21. Si los 2 se pasan de 21 el dinero de la apuesta del jugador será para la banca. Cuando el usuario lo desee puede plantarse y no recibir más cartas. En ese caso la banca deberá seguir jugando y si la banca obtiene más de 21 puntos gana el jugador. Si la banca obtiene menos de 21 puntos, sus puntos se comparan con los del jugador y gana el que tengo un mayor número de puntos en su haber. Por último, en caso de empate gana la banca. 2. Explicación de nuestro programa Nuestro programa está hecho en Java y consta de 5 clases, que son las siguientes: 2.1 TextIO.java La clase TextIO es la encargada de realizar las operaciones que van a interactuar con el jugador. Lo único que utilizaremos de esta clase son los métodos put() y putln() para escribir en pantalla y los métodos getlnInt() y getlnChar() para recibir del usuario los datos que nos va a ir introduciendo. Esta es una clase de la que ya disponíamos antes de empezar a hacer el programa y que ni siquiera hemos modificado. 2.2 Carta.java La clase Carta es la encargada de definir cada una de las cartas que van a definir la baraja. Con un entero ‘palo’ y otro entero ‘valor’ somos capaces de definir una carta de forma inequívoca. También definimos una serie de constantes para definir los posibles palos y los posibles valores de las cartas que nos vamos a encontrar. Como se puede observar, para las cartas comprendidas entre el 2 y el 10 no es necesario definir ninguna constante, ya que el propio número de la carta será su valor. La clase define otra serie de métodos auxiliares utilizados para obtener la propiedad de cada carta. Podemos obtenerla tanto en formato entero, que es como se ha definido como propiedad de la clase; o también tenemos otra pareja de métodos que nos devuelven las propiedades de la carta como si fuera un String. Estos métodos son getPalo(), getValor(), getPaloString() y getValorString(). Otro método toString() nos devuelve un String con la descripción del palo, es decir, “As de Diamantes” o “4 de Picas”, lo cual nos será útil para decirle al jugador las cartas en cuestión. 2.3 Baraja.java La clase Baraja define simplemente una baraja de cartas, que lo simularemos con un array de objetos Carta. Está formada únicamente por 4 métodos, uno de ellos el constructor, encargado de generar toda la baraja de 52 cartas. Con un par de bucles for vamos recorriendo los posibles palos y los posibles valores hasta generar la baraja completa. Definimos otra propiedad ‘restantes’ para saber cuantas cartas nos quedan en la baraja. El método barajar() se encarga de ordenar la baraja. Para ello utilizaremos el método random de la case Math y una variable auxiliar. Por último, el método restantes() nos dice cuantas cartas quedan en la baraja y el método robar() coge una carta de la baraja, la primera del mazo. Dado que en el constructor no barajamos la baraja, lo haremos en el método de robar en caso de que la baraja esté completa. También nos definimos un array auxiliar llamado vistas, que utilizará la banca para decidir si coge nueva carta o no. Este array tiene tamaño 13 y en cada una de las posiciones tiene el número de cartas, cuyo valor es dicha posición, que se encuentran descubiertas en la mesa o las posee la banca. 2.4 Mano.java Utilizaremos la clase Mano para indicar el número de cartas que va a tener un jugador y la banca en la mano. Para ello utilizaremos un Vector de objetos Carta. No utilizamos un array como en la clase Baraja, porque en este caso no sabemos cual va a ser el número de cartas que va a tener cada participante del juego en su mano. En la clase Mano también tendremos la información sobre el dinero apostado por dicho jugador en esta ronda del juego. Aquí definiremos todas las alternativas que puede tomar el jugador durante la partida cuando tiene un conjunto de cartas en su mano. Vamos a explicar brevemente cada una de ellas. Para empezar, los métodos cogerCarta() y dejarCarta() realizan las acciones que sus nombres indican. En el caso de dejarCarta() podemos soltar una carta determinada o la que se encuentre en una posición indicada. El método soltar() suelta todas las cartas que tiene el jugador en la mano. El método contar() cuenta las cartas que tiene el jugador en su mano y el método obtenerCarta() devuelve la carta situada en una posición específica. Por último, el método más importante de esta clase es el getBlackjackValor(). Este método se encarga de contar la puntuación de las cartas que tiene el jugador en su mano. Para eso se basará en que las figuras tienen un valor de 10 puntos, las cartas entre el 2 y el 10 tienen el valor especificado en su número y el as tendrá un valor u otro en función de la jugada. Como ya se ha explicado antes, el as valdrá 11 puntos en caso de que la suma con el resto de cartas de la mano sea menor o igual que 21. En caso contrario valdrá 1 punto. 2.5 Blackjack.java La clase Blackjack es la clase principal del programa, ya que en ella se encuentra el método main() que va a llevar el desarrollo del juego. Lo primero que se hace al entrar en el método main() es presentarle al usuario el juego y sus autores, y a continuación preguntar cual va a ser la cantidad inicial de dinero en euros de la que va a disponer cada jugador y el número de jugadores que van a participar en el juego. Una vez hecho esto comienza el juego y el programa entra en un bucle infinito del que solo se saldrá si todos los jugadores desean interrumpir la partida o si se quedan sin dinero. Inmediatamente después se le preguntará a cada jugador cuanto dinero desea jugar y se le presentará un mensaje de error en caso de que intente jugar con más dinero del que tiene o en caso de que introduzca una apuesta negativa. Una vez comprobado que la apuesta es correcta y que todavía quedan jugadores en la partida (no se han arruinado todos o han abandonado) podemos pasar al método jugar(), que es el que va a llevar la partida. Lo primero que haremos en el método jugar() será repartir 2 cartas a cada jugador y otras 2 a la banca. Antes de imprimir por pantalla las cartas del jugador y la carta descubierta de la banca se comprobará si alguno de los jugadores o la banca suma 21 puntos (Blackjack) en cuyo caso se termina el juego y se vuelve a realizar una nueva apuesta. En caso de que no haya Blackjack se muestran las cartas a cada jugador, por orden, y este decide si se planta o si sigue jugando. El jugador introduce un carácter ‘C’ para pedir nueva carta o un ‘P’ para plantarse. En caso de que introduzca un carácter incorrecto se le mostrará un mensaje de error. Si el jugador decide plantarse, el turno pasará a la siguiente y si decide coger una nueva carta se le añadirá a su mano y, si no supera los 21 puntos, se le volverá a preguntar ‘C’ o ‘P’. En caso de superar los 21 puntos el jugador habrá perdido. Este proceso se irá repitiendo para todos los jugadores involucrados en la partida. Una vez que todos los jugadores hayan jugado, es el turno de la banca. En los casinos, la banca tiene la norma de en cuanto supere los 16 puntos debe plantarse, pero nosotros hemos hecho un algoritmo más elaborado para dotarle de más inteligencia. Cuando sea el turno de la banca, esta deberá decidir si roba nueva carta o no. Para decidirse comparará 2 posibles situaciones. La primera de ellas será calcular el beneficio que obtendría en la situación actual, teniendo en cuenta su puntuación, la de los demás y los jugadores que ya se han pasado de 21 puntos. Para calcular lo segundo nos basaremos en la probabilidad de obtener una carta u otra al robar del mazo. Para ello, calcularemos la probabilidad de obtener la carta x y el beneficio que obtendríamos con dicha carta. Sumando todas las posibilidades obtendríamos un beneficio hipotético. Beneficio hipotético = probabilidad(carta i)·Beneficio(carta i) Por tanto, comparando el beneficio hipotético con el beneficio actual podemos decidir si cogemos una nueva carta o no. Una vez hecho esto deberemos ver quien ha ganado. Todos compiten contra la banca, luego esta comparación deberemos hacerla entre cada uno de los jugadores y la banca. Durante el programa, siempre que algún jugador gana o pierde asignamos un valor a un entero dentro de un array llamado ‘resultados’ y salimos del bucle. El valor de dicho entero determina quien ha ganado y en qué condiciones. En la siguiente tabla presentamos los posibles valores de dicho entero y lo que significa cada uno: Valor 0 1 -1 2 Significado Situación inicial He ganado He perdido He logrado Blackjack NOTA: La práctica la hemos hecho enteramente juntos, por tanto no podemos especificar que parte ha hecho cada uno. 3. Código fuente comentado Carta.java /* Clase con la que vamos a representar cada una de las cartas de la baraja */ public class Carta { /* Constantes enteras que definen los palos y las cartas que no tienen valor numerico */ public final static int ESPADAS = 0, CORAZONES = 1, DIAMANTES = 2, PICAS = 3; public final static int AS = 1, JACK = 11, QUEEN = 12, KING = 13; /* Las 2 propiedades de nuestra carta seran valor y palo. Las definimos como privadas y a continuacion definimos los metodos para obtenerlas */ private final int palo; private final int valor; /* Metodo constructor */ public Carta(int val, int pal) { valor = val; palo = pal; } /* Metodos que nos devuelven valor y palo como entero y como String */ public int getPalo() { return palo; } public int getValor() { return valor; } public String getPaloString() { switch ( palo ) { } } case ESPADAS: return "Espadas"; case CORAZONES: return "Corazones"; case DIAMANTES: return "Diamantes"; case PICAS: return "Picas"; default: return "??"; public String getValorString() { switch ( valor ) { case 1: return "As"; case 2: return "2"; case 3: return "3"; case 4: return "4"; case 5: return "5"; case 6: return "6"; case 7: return "7"; case 8: return "8"; case 9: return "9"; case 10: return "10"; case 11: return "J"; case 12: return "Q"; case 13: return "K"; default: return "??"; } } public String toString() { return getValorString() + " de " + getPaloString(); } } Baraja.java /* Clase que representara nuestra baraja de 52 cartas*/ public class Baraja { // La baraja sera un array de Cartas private static Carta[] baraja; // Definimos un array de cartas vistas. En cada posicion del array tendremos el numero de cartas, cuyo // valor es dicha posicion, que hay descubiertas sobre la mesa o tiene la banca public int[] vistas; // Numero de cartas robadas private int robadas; // Metodo constructor. Recorremos todos los valores posibles y todos los palos posibles public Baraja() { baraja = new Carta[52]; vistas = new int[13]; int creadas = 0; for ( int palo = 0; palo <= 3; palo++ ) { for ( int valor = 1; valor <= 13; valor++ ) { baraja[creadas] = new Carta(valor,palo); creadas++; } } robadas = 0; } // Ordenamos la baraja en orden aleatorio public void barajar() { for ( int i = 51; i > 0; i-- ) { int rand = (int)(Math.random()*(i+1)); Carta temp = baraja[i]; baraja[i] = baraja[rand]; baraja[rand] = temp; } robadas = 0; } // Numero de cartas que nos quedan en la baraja public int restantes() { return 52 - robadas; } // Cuando robamos una carta cogemos la primera del mazo y actualizamos el array de cartas vistas public Carta robar() { if (robadas == 52) barajar(); robadas++; vistas[((baraja[robadas-1]).getValor())-1]++; }} return baraja[robadas - 1]; Mano.java /* Con esta clase vamos a representar las cartas que tiene un jugador en cierta jugada y el dinero que ha apostado en esa jugada */ import java.util.Vector; public class Mano{ // Se trata de un vector y no un array porque el numero de cartas en la mano es variable private Vector mano; int apuesta; // Constructor public Mano() { mano = new Vector(); } // Suelta todas las cartas public void soltar() { mano.removeAllElements(); } // Añade una carta a su mano public void cogerCarta(Carta c) { if (c != null) mano.addElement(c); } // Suelta una de sus cartas public void dejarCarta(Carta c) { mano.removeElement(c); } // Suelta la carta en la posicion marcada por pos public void dejarCarta(int pos) { if (pos >= 0 && pos < mano.size()) mano.removeElementAt(pos); } // Cuenta las cartas que tiene en la mano public int contar() { return mano.size(); } // Nos dice la carta que tiene en la posicion pos public Carta obtenerCarta(int pos) { if (pos >= 0 && pos < mano.size()) return (Carta)mano.elementAt(pos); else return null; } // Cuenta los puntos que suman las cartas de nuestra mano public int getBlackjackValor() { int val; boolean as; int cartas; val = 0; as = false; cartas = contar(); for ( int i = 0; i < cartas; i++ ) { Carta carta; int cartaVal; carta = obtenerCarta(i); cartaVal = carta.getValor(); if (cartaVal > 10) { cartaVal = 10; } if (cartaVal == 1) { as = true; } val = val + cartaVal; } /* El as en principio vale 1, pero si al cambiar su valor por 11 conseguimos un resultado igual a 21 o menor lo cambiaremos*/ if ( as == true && val + 10 <= 21 ) val = val + 10; }} return val; Blackjack.java /* Clase principal que llevara el control del juego */ public class Blackjack { // Metodo main public static void main(String[] args) { int saldo; int[] resultados; int[] saldos; int numero,i; Mano manoJugador[]; boolean primera=true; boolean bancarrota[]; // Presentacion TextIO.putln(); TextIO.putln("Bienvenido al juego de Blackjack."); TextIO.putln(); TextIO.putln(" AUTORES"); TextIO.putln(); TextIO.putln("Ignacio Ribas Ramos"); TextIO.putln("Miguel Flecha Lozano"); TextIO.putln(); // Preguntamos por número de jugadores y por la cantidad con la que empiezan TextIO.put("Numero de jugadores:"); numero = TextIO.getlnInt(); TextIO.put("Con que cantidad empezara cada jugador (euros)?"); saldo = TextIO.getlnInt(); // Inicializamos los arrays que vamos a utilizar saldos=new int[numero]; manoJugador=new Mano[numero]; resultados=new int[numero+1]; bancarrota=new boolean[numero]; for(i=0;i<numero;i++){ saldos[i]=saldo; bancarrota[i]=false; } // Comienza el bucle... while (true) { // Le preguntamos a cada jugador cuanto dinero desea apostar for(int j=0;j<numero;j++){ // Creamos la mano manoJugador[j]= new Mano(); if(saldos[j]<=0) bancarrota[j]=true; TextIO.putln(); TextIO.putln("JUGADOR NUMERO " + (j+1) +". Tiene " + saldos[j] + " euros."); if(!bancarrota[j]){ do { TextIO.putln("Cuantos euros quiere apostar? (0 para salir)"); manoJugador[j].apuesta=TextIO.getlnInt(); if (manoJugador[j].apuesta < 0 || manoJugador[j].apuesta > saldos[j]) TextIO.putln("Su apuesta debe estar entre 0 y " + saldos[j] +' .' ); utilizamos el mismo array queda sin dinero, para // En caso de que el jugador no desee jugar más // de booleanos que usamos cuando un jugador se // que la siguiente ronda no se le pregunte if (manoJugador[j].apuesta == 0){ TextIO.putln(); TextIO.putln("ADIOS jugador "+(j+1)); bancarrota[j]=true; } } while (manoJugador[j].apuesta < 0 || manoJugador[j].apuesta > saldos[j]); } } // En caso de que no se hayan arruinado todos los jugadores entramos al metodo jugar if(!queda_alguno(bancarrota,numero)) resultados = jugar(numero,manoJugador,bancarrota); else{ TextIO.putln(); TextIO.putln("Todos los jugadores se han quedado sin dinero o no hay mas jugadores"); TextIO.putln("Adioooos"); System.exit(-1); } // Al salir del metodo jugar comprobamos las puntuaciones ya actualizamos los saldos for(int j=0;j<numero;j++){ if((resultados[0]==2)||(resultados[0]==1)) saldos[j] = saldos[j] manoJugador[j].apuesta; else{ switch (resultados[j+1]){ case -1: saldos[j] = saldos[j] manoJugador[j].apuesta;break; case 1: saldos[j] = saldos[j] + manoJugador[j].apuesta;break; case 2: saldos[j] = saldos[j] + 2*manoJugador[j].apuesta;break; default: saldos[j] = saldos[j]-manoJugador[j].apuesta; } } } } } // Metodo jugar que lleva el desarrollo principal de la partida static int[] jugar(int jugadores,Mano[] manoJugador,boolean[] bancarrota) { Baraja baraja; Mano manoBanca; boolean fin=false; int i,j; // En resultados almacenamos lo que ha hecho cada jugador. // 0 en un principio, -1 si se pasa y 1 si gana. // La posicion 0 sera la de la banca y la posicion i la del jugador i // Pondremos un 2 en caso de blackjack. int resultados[]; resultados=new int[jugadores+1]; for (int m=0;m<=jugadores;m++){ resultados[m]=0; } baraja = new Baraja(); // Barajamos y repartimos baraja.barajar(); // La banca roba sus cartas manoBanca = new Mano(); manoBanca.cogerCarta( baraja.robar() ); manoBanca.cogerCarta( baraja.robar() ); TextIO.putln(); TextIO.putln(); for(i=0;i<jugadores;i++){ // Los jugadores van robando manoJugador[i].cogerCarta( baraja.robar() ); manoJugador[i].cogerCarta( baraja.robar() ); // En caso de que el usuario consiga Blackjack (21 a la primera) gana el doble de la apuesta // y se acaba la ronda if ((manoJugador[i].getBlackjackValor() == 21)&&(!bancarrota[i])) { TextIO.putln("La banca tiene " + manoBanca.obtenerCarta(0) + " y " + manoBanca.obtenerCarta(1) + "."); TextIO.putln("El jugador "+(i+1)+" tiene " + manoJugador[i].obtenerCarta(0) + " y " + manoJugador[i].obtenerCarta(1) + "."); TextIO.putln(); TextIO.putln("El jugador "+(i+1)+" tiene Blackjack y gana"); resultados[i+1]=2; } } fin=true; // Si la banca tiene BJ gana y se acaba la partida if (manoBanca.getBlackjackValor() == 21) { TextIO.putln("La banca tiene " + manoBanca.obtenerCarta(0) + " y " + manoBanca.obtenerCarta(1) + "."); TextIO.putln(); TextIO.putln("La banca tiene Blackjack y gana."); } resultados[0]=2; fin=true; if (fin) return resultados; // Si ninguno de los 2 tiene BJ seguimos con el juego // Comienza la iteracion para cada uno de los jugadores for(j=0;j<jugadores;j++){ banca // Mostramos las cartas de los jugadores y una de las cartas de la TextIO.putln("--------- JUGADOR "+(j+1)+" ---------"); TextIO.putln(); fin=false; while (!bancarrota[j]) { TextIO.putln(); TextIO.putln("Jugador "+(j+1)+".Sus cartas son:"); for ( i = 0; i < manoJugador[j].contar(); i++ ) TextIO.putln(" " + manoJugador[j].obtenerCarta(i)); TextIO.putln("Y suman un total de " + manoJugador[j].getBlackjackValor()+" puntos."); TextIO.putln(); TextIO.putln("La banca muestra " + manoBanca.obtenerCarta(0)); TextIO.putln(); // Carta o se planta? TextIO.put("Carta (C) o se Planta (P)? "); char accion; do { accion = Character.toUpperCase( TextIO.getlnChar() ); if (accion != ' C'&& accion != ' P' ) TextIO.put("Por favor responda C o P: "); } while (accion != ' C'&& accion != ' P' ); // Si se planta salimos del bucle if ( accion == ' P') { TextIO.putln(); TextIO.putln("El jugador "+(j+1)+" se planta."); TextIO.putln(); break; } // Si no se planta seguimos con una nueva carta else { Carta newCarta = baraja.robar(); manoJugador[j].cogerCarta(newCarta); TextIO.putln(); TextIO.putln("Usted roba carta."); TextIO.putln("Su carta es " + newCarta); TextIO.putln("Y usted tiene " + manoJugador[j].getBlackjackValor()+" puntos"); // Si se pasa de 21 puntos pierde y pone su resultado a -1 if (manoJugador[j].getBlackjackValor() > 21) { TextIO.putln(); TextIO.putln("El jugador "+(j+1)+" se ha pasado de 21. Ha perdido"); TextIO.putln(); resultados[j+1]=-1; fin=true; } } } } if (fin) break; // Ahora le toca jugar a la banca TextIO.putln(); TextIO.putln("Las cartas de la banca son "); TextIO.putln(" " + manoBanca.obtenerCarta(0)); TextIO.putln(" " + manoBanca.obtenerCarta(1)); while(true){ float beneficio=0; float beneficio_hip=0; float beneficio_aux=0; float probabilidad=0; // Primero comprobamos el beneficio que obtendriamos en la situacion actual for(i=0;i<jugadores;i++){ if(manoBanca.getBlackjackValor()>=manoJugador[i].getBlackjackValor()) beneficio=beneficio+manoJugador[i].apuesta; else if(resultados[i+1]!=-1) beneficio=beneficiomanoJugador[i].apuesta; } // Sabiendo las cartas que hay en la mesa calcularemos la esperanza de sacar cada una de las // posibles cartas y en funcion de eso el beneficio hipotetico que obtendriamos. for (j=1;j<14;j++){ Carta aux=new Carta(j,0); manoBanca.cogerCarta(aux); probabilidad=baraja.vistas[j-1]/baraja.restantes(); for(i=0;i<jugadores;i++){ if(manoBanca.getBlackjackValor()>=manoJugador[i].getBlackjackValor()) beneficio_aux=beneficio_aux+manoJugador[i].apuesta; else if(resultados[i+1]!=-1) beneficio_aux=beneficio_aux-manoJugador[i].apuesta; } } beneficio_hip=beneficio_hip+beneficio_aux*probabilidad; beneficio_aux=0; manoBanca.dejarCarta(manoBanca.contar()-1); // Si el beneficio hipotetico es mayor que el actual robamos carta if(beneficio_hip>beneficio){ Carta newCarta = baraja.robar(); TextIO.putln("La banca roba " + newCarta); manoBanca.cogerCarta(newCarta); if (manoBanca.getBlackjackValor() > 21) { resultados[0]=-1; break; } }else{ } carta } break; // Repetimos esto hasta que nos pasemos o decidamos no coger nueva TextIO.putln("El total de la banca es de " + manoBanca.getBlackjackValor()+" puntos"); // Vamos a comparar los puntos de cada uno para ver quien ha ganado a quien y lo reflejamos // en el array de resultados for(i=0;i<jugadores;i++){ if((resultados[i+1]==-1)||(bancarrota[i])) continue; if(resultados[0]==-1){ resultados[i+1]=1; continue; } if(manoJugador[i].getBlackjackValor()>manoBanca.getBlackjackValor()) resultados[i+1]=1; else resultados[i+1]=-1; } TextIO.putln(); TextIO.putln(); // Imprimimos por pantalla quien ha ganado a quien for(i=1;i<=jugadores;i++){ if(!bancarrota[i-1]){ if(resultados[i]>resultados[0]) TextIO.putln("Jugador "+i+" gana a la banca."); else TextIO.putln("La banca gana al jugador "+i+"."); } } return resultados; } dinero // Este metodo comprueba si todos los jugadores se han quedado sin static boolean queda_alguno(boolean bancarrota[],int numero){ boolean bancarrota_total=true; for(int p=0;(p<numero)&&bancarrota_total;p++){ if(!bancarrota[p]){ bancarrota_total=false; break; } } return bancarrota_total; } }