int chorizo; if (a==b) { chorizo = 7; } else { chorizo = 0; }
¿Acojona que algo que se ha hecho en 6 lÃneas antes lo haya hecho en una sola? Pues esa es la magia del operador ternario. Básicamente sirve para expresiones de este tipo:
¡Ojo! Que solo sirve para cosas exactamente iguales a las que he puesto. Me explico, el bloque if...else solo tiene dos ramas if y else. Nada de else if.... Luego, en ambas ramas (que insisto, no hay más ramas que dos) hay que asignar valores a una variable (la misma variable) en ambas ramas. Y no puede haber más cosas dentro.
Entonces, teniendo esos elementos, la cosa se transforma en esto:
variable = condicion ? valor1 : valor2;
Y nada más. Todo en ese orden y sin que falte nada.
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality✓ Free Actions
Free to watch • No registration required • HD streaming
Othello - Inteligencia Artificial II - Implementación (parte 2)
Cuando estaba escribiendo esta implementación, me di cuenta de que no era, ni de lejos, la mejor que podÃa haber hecho. De hecho, es cuanto menos absurdo, la implementación de un árbol n-ario cuando es totalmente innecesario guardar en memoria todos los hijos de un nodo.
Aun asÃ, vamos a ver el constructor secundario de OthelloNode, que sirve para construir nodos que no son el nodo cabeza.
Además es privado porque solo será visible desde el propio OthelloNode.
El otro constructor
private OthelloNode(OthelloCPU game, char x, int y, OthelloNode parent, int depth) { this.game = game; this.parent = parent; this.depth = depth;
if (depth==0) { this.minmax = this.profit; } else { int max = 0; int min = 0; // El máximo o mÃnimo de los hijos. OthelloNode pointer = this.children.remove(0); while (pointer != null) { max = pointer.minmax>max ? pointer.minmax : max; min = pointer.minmax<min ? pointer.minmax : min; pointer = this.children.isEmpty() ? null : this.children.remove(0); } this.minmax = this.author==Piece.BLACK ? max : min; } }
Para finalizar vamos a implementar createChildren
private void createChildren() { this.children = new ArrayList<OthelloNode>(); for (char x = 'A'; x <= 'H'; x++) { for (int y = 1; y <= 8; y++) { if (this.game.isValidMovement(x, y)) { try { this.children.add(new OthelloNode(this.game.clone(), x, y, this, this.depth-1)); } catch (CloneNotSupportedException e) {} }// if }// for }// for }
Othello - Inteligencia Artificial II - Implementación (parte 1)
Vamos a crear la estructura de árbol n-ario necesaria para el algoritmo nuestro.
Recordemos que se trata de un árbol en la que cada nodo tiene un número indefinido de hijos. Por tanto, la información de los hijos estará almacenada, por ejemplo, en una Lista.
Además cada nodo debe tener cierta información: el tablero en un momento dado, las coordenadas del movimiento, el autor del movimiento (negras o blancas), el beneficio del movimiento, el valor minmax y la profundidad del nodo.
Empezamos implementando el Nodo
Vamos a hacer un constructor para la cabeza y otro para los demás nodos.
Atributos:
public class OthelloNode { private static int size; private OthelloCPU game; private Piece author; // The author of the last movement private int profit; private int minmax; private int depth; private OthelloNode parent;
Constructor de la cabeza
Pasa como atributos la situación actual del juego (una instancia completa de OthelloCPU) y la profundidad deseada (depth)
Para ello vamos a utilizar una estructura de árbol con unas caracterÃsticas peculiares.
Cada nodo almacena una situación del tablero en un momento dado.
La cabeza almacena la situación actual del tablero.
Cada nodo tendrá tantos hijos como movimientos posibles.
Cada nodo almacena diversa información.
La implementación del árbol es algo compleja y lenta.
Creamos la cabeza del árbol metiendo en ella la situación del tablero actual.
Le asignamos profundidad n (donde n es la complejidad de la búsqueda)
Desplegamos tantos hijos como movimientos posibles para el jugador haya. Los desplegamos con una búsqueda en profundidad. Es decir, primero los más profundos y luego los siguientes.
Les asignamos profundidad = profundidad del padre - 1.
Calculamos la nueva situación del tablero en cada nodo.
Introducimos el beneficio de este nodo = beneficio del padre + beneficio de este movimiento
Repetimos lo mismo hasta llegar a una profundidad 0
En los nodos de profundidad 0, calculamos el valor MINMAX minmax = beneficio de este nodo.
En un nodo cualquiera que no sea de profundidad 0
Si el movimiento ha sido máquina (y por tanto el siguiente es humano), minmax = mÃnimo de sus hijos
Si el movimiento ha sido humano (y por tanto el siguiente es máquina), minmax = máximo de sus hijos
El mejor movimiento será el máximo de los minmax de los hijos de la cabeza
Explicación con un ejemplo
Esta es la situación que vamos a simular.
Primero, ponemos la cabeza del árbol y vamos a asignarle profundad 6 por ejemplo. Si queremos aumentar la complejidad del cálculo, tenemos que incrementar dicho número de profundidad.
Prof 5 B2 > WHITE > 0 Prof 5 C1 > WHITE > 0 Prof 5 C4 > WHITE > 0 Prof 5 C6 > WHITE > 0 Prof 5 C7 > WHITE > 0 Prof 5 D3 > WHITE > 0 Prof 5 D7 > WHITE > 0 Prof 5 E2 > WHITE > 0 Prof 5 E7 > WHITE > 0 Prof 5 F2 > WHITE > 0 Prof 5 F3 > WHITE > 0 Prof 5 F7 > WHITE > 0 Prof 5 G3 > WHITE > 0 Prof 5 G4 > WHITE > 0 Prof 5 G6 > WHITE > 0 Prof 5 G7 > WHITE > 0
Por lo tanto, el valor "minmax" para todos los movimientos es 0. Es decir, estudiados 1621746 movimientos posibles, la conclusión es que "cualquier movimiento nos da el mismo beneficio". Luego, tenemos que mejorar el algoritmo para poder elegir un movimiento mejor que los demás..
Mañana viernes pondremos la implementación y la semana que viene mejoraremos el algoritmo
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality✓ Free Actions
Free to watch • No registration required • HD streaming
Tendremos una nueva clase llamada OthelloCPU, que hará casi lo mismo que la clase Othello. Las caracterÃsticas que distinguen OthelloCPU de Othello son:
En OthelloCPU, el jugador humano es la ficha blanca. El jugador CPU la ficha negra.
public void putPiece(char x, int y) throws InvalidMovementException, UnactiveGameException { // Plays the human super.putPiece(x, y); // Plays the CPU playCPU(); }
private int simulateEatPieces(char x, int y) { int piecesEaten = 0; for (int i = 0; i < 8; i++) { piecesEaten += this.simulateEatPieces(x, y, Direction.values()[i]); } return piecesEaten; }
Asà que definimos simulateEatPieces(char x, int y, Direction dir) que hace lo mismo que eatPieces(char x, int y, Direction dir) pero sin llegar a comer ninguna pieza (solo contando las que se comerÃa)
private int simulateEatPieces(char x, int y, Direction dir) { if (!this.isValidMovement(x, y)) return 0; // Get the piece at the square
Esta es la única variación
Piece piece = super.getTurn();
El resto no cambia
int piecesToEat = 0; x += dir.x(); y += dir.y(); // Follow the direction and find the next piece of same colour while (x >= 'A' && x <= 'H' && y >= 1 && y <= 8 && this.getBoard().getPiece(x, y) == piece.getEnemy()) { piecesToEat++; x += dir.x(); y += dir.y(); } if (this.getBoard().getPiece(x, y)!=piece) { piecesToEat = 0; } return piecesToEat; }
Con esto habrÃamos hecho un cálculo muy básico del siguiente movimiento.
public Piece getPiece(char x, int y) { return squares[y-1][x-'A']; } public void putPiece(Piece piece, char x, int y) throws InvalidMovementException { if (piece==null) throw new InvalidMovementException("Piece given is null"); if (x<'A' || x>'H') throw new InvalidMovementException("Coordinate x '" + x + "' is not valid"); if (y<0 || y>8) throw new InvalidMovementException("Coordinate y '" + y + "' is not valid"); squares[y-1][x-'A'] = piece; } public void flipPiece(char x, int y) throws InvalidMovementException { Piece current = this.getPiece(x, y); this.putPiece(current.getEnemy(), x, y); }
Como observación, decir que si getPiece recibe argumentos no válidos, puede lanzar una excepción no controlada del tipo ArrayOutOfBoundsException.
public void putPiece(char x, int y) throws InvalidMovementException, UnactiveGameException { if (!active) throw new UnactiveGameException("Game has been ended"); if (x < 'A' || x > 'H') throw new InvalidMovementException("Coordinate x '" + x + "' is not valid"); if (y < 1 || y > 8) throw new InvalidMovementException("Coordinate y '" + y + "' is not valid"); if (this.board.getPiece(x, y) != null) throw new InvalidMovementException("The square is not empty"); if (!this.willEatPiece(x, y, turn)) throw new InvalidMovementException("The movement is not valid");
private int eatPieces(char x, int y) { int piecesEaten = 0; for (int i = 0; i < 8; i++) { piecesEaten += this.eatPieces(x, y, Direction.values()[i]); } return piecesEaten; }
private int eatPieces(char x, int y, Direction dir) {
Primero, obtiene la ficha e inicializa un contador a 0.
Piece piece = this.board.getPiece(x, y); int piecesToEat = 0;
Avanza los cursores en la dirección dada
x += dir.x(); y += dir.y();
Ahora, va avanzando mientras que se vaya encontrando fichas enemigas. Las posibles salidas del bucle son 3:
CASO 1. Se ha llegado al final del tablero. El cursor (x,y) está fuera del rango ([1,8],[A,H]) En este caso, no se come ninguna ficha
CASO 2. La ficha a la que llega el cursor no es del enemigo porque no hay ficha En este caso, no se come ninguna ficha
CASO 3. La ficha a la que llega el cursor no es del enemigo porque la ficha no es la del enemigo, sino la propia En este caso, el programa voltea todas las fichas intermedias
Implementamos
// Follow the direction and find the next piece of same colour while (x >= 'A' && x <= 'H' && y >= 1 && y <= 8 && this.board.getPiece(x, y) == piece.getEnemy()) { piecesToEat++; x += dir.x(); y += dir.y(); }
Si la salida no es por el caso 3, no se come ninguna ficha
if (this.board.getPiece(x, y)!=piece) { piecesToEat = 0; }
Ahora, desde la posición a la que habÃa llegado el cursor, vamos retrocediendo piecesToEat veces.
for (int i=piecesToEat; i>0; i--) { x -= dir.x(); y -= dir.y(); try { this.board.flipPiece(x, y); } catch (InvalidMovementException e) {} // Already handled } return piecesToEat; }
La implementación de willEatPiece es similar.
Aquà está el código completo de las clases Board y Othello. Y aquà el de las clases auxiliares InvalidMovementException, Direction y Piece.
Las reglas del juego son muy sencillas: un tipo de movimiento, un tipo de fichas y por ello, es muy interesante crear un programa que juegue a Othello.
De hecho, existe un programa creado en 1997 llamado Logistello, implementado en C. Su código fuente lo podemos encontrar aquÃ. Esta máquina es considerada prácticamente imbatible y se podrÃa decir que casi ha superado a la inteligencia humana. En un torneo celebrado en el mismo año 1997, el campeón (humano) del torneo, Takeshi Murakami, se enfrentó a dicha máquina y la máquina ganó 6-0.
y estas dos clases auxiliares que se hacen necesarias al avanzar la implementación.
othello.pieces.Direction. Un ENUM que tiene 8 elementos: NORTH, SOUTH, EAST, WEST, NORTHWEST, SOUTHWEST, NORTHEAST, SOUTHEAST que corresponden a los 4 puntos cardinales más sus diagonales.
othello.pieces.Piece. Un ENUM con 2 elementos: WHITE para la ficha blanca y BLACK para la ficha negra.
Con los smartphones, ha cambiado la manera que tenemos de navegar por internet y por supuesto, la web ha cambiado una barbaridad desde entonces. Yo he creado este blog variando únicamente el ancho de la web adaptándola al ancho del dispositivo. Evidentemente podrÃamos haber seleccionado otra variable cualquiera, pero he preferido esa por diversos motivos. Uno de ellos porque el usuario está acostumbrado a hacer scroll vertical.
Sin embargo, los smartphones tienen una caracterÃstica que es el zoom y el zoom automático que ajusta la página a la pantalla que nos da muchos problemas. Tras el salto explicamos este problema y su solución.
¿Pero la pantalla del viewport no tiene el mismo tamaño que la pantalla (resolución) de mi móvil?.
Y la respuesta, por increÃble que pueda parecer, es NO.
Esto tiene cosas buenas y cosas malas.
Lo bueno
Supongamos que tengo una imagen que ocupa 1600px de ancho y quiero verla en un móvil que tiene una resolución de 320x240
La pantalla del móvil tiene una resolución efectiva de 320x240. Si el viewport tuviera el mismo tamaño, la foto se verÃa asÃ:
Pero lo que hace nuestro móvil es considerar que el viewport tiene un tamaño mucho más grande, 1600px de ancho. Y luego, lo que hace es adaptar el viewport a la pantalla. AsÃ, la foto se ve entera
Lo malo
Ahora, tenemos una página que está hecha para que se adapte al ancho del móvil, aunque en realidad lo que hacemos es adaptarnos al ancho del viewport.
Lo lógico es que el móvil considere que el ancho del móvil es el ancho del viewport y por tanto haga esto:
Es decir, tome la página que está para "dispositivos con ancho de 320px" y la muestre.
Sin embargo, la respuesta a la pregunta es otra:
El móvil considera el ancho que le de la gana
Supongamos que el móvil considera un viewport de 800px de ancho. Esto quiere decir, que la página se verá como si tuviera 800px de ancho y luego el móvil la adaptará para ajustarla a los 320px de ancho de la pantalla.
Afortunadamente, tenemos una etiqueta que nos permite decirle al móvil que no coja un viewport cualquiera el que nosotros le digamos que tiene que coger.
Utilizaremos un metadato, es decir, la etiqueta meta de HTML que nos permitirá controlar eso. Aquà tenemos la sintaxis básica de dicha etiqueta:
Además de elegir el ancho inicial, los navegadores tienen una caracterÃstica: el zoom. Nosotros, con la etiqueta que hemos visto podemos definir cuánto zoom permitimos al usuario.
El atributo initial-scale es el zoom inicial de la página (si lo tiene). En caso de que queramos que la página se vea inicialmente sin zoom pondremos el valor 1 que equivale al 100%.
Los atributos minimum-scale, maximum-scale definen la mÃnima y máxima cantidad de zoom permitida al usuario. Podemos poner valores desde 0.1 (10%) hasta el que queramos.
El atributo user-scalable=no (con el valor no) impide cualquier zoom al usuario quedándose siempre con el valor que especifiquemos en initial-scale. Omitirlo es como si tuviera el valor yes.
En nuestro caso vamos a utilizar esta configuración.
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality✓ Free Actions
Free to watch • No registration required • HD streaming
Hoy vamos a seguir hablando de este blog, #exacs. En concreto, de lo que tendremos que cambiar en estos próximos dÃas.
En primer lugar, para las versiones móviles, tenemos que hacer que se vea la versión móvil. Esto lo haremos con la etiqueta <meta>.
Continuando con las versiones móviles, vamos a tener que ajustar el tamaño de fuente, sobre todo de las fuentes de los tÃtulos que a veces pienso que son excesivamente grandes.
Esta canción fue usada por Apple en un vÃdeo el año pasado. El vÃdeo se llama "Design by Apple - Intention" y es muy bonito pero la música es realmente encantadora.
A continuación, explico el funcionamiento de dicha propiedad y cómo la aplicaremos en #daetsam y posteriormente en #exacs.
Existen algunas ocasiones en las que un objeto HTML cambia de estilo. Por ejemplo, con el flag :hover que nos permite definir estilos diferentes si un objeto tiene el cursor encima.
O bien, utilizando las clases como en #daetsam. Recordemos que en dicho caso, al pulsar el botón de "abrir menú", mediante JavaScript, conseguimos que el menú nav#nav-global reciba la clase opened y de ese modo su CSS es diferente.
Sin embargo, los cambios de CSS son instantáneos. Y para que no sean instantáneos existe esta propiedad CSS: transition.
La propiedad transition
La propiedad transition define cuánto tiempo dura un cambio de valor de una propiedad CSS. Lo ilustramos con un ejemplo.
Supongamos que tenemos un CSS tal que asÃ
a { color:#F00; } a:hover { color:#000; text-decoration: underline; }
Si ahora lo que queremos es que el paso de rojo a negro no sea instantáneo, tenemos que usar la propiedad transition asÃ:
a { color:#F00; transition: color 1s; } a:hover { color:#000; text-decoration: underline; }
De este modo, decimos que la transición para la propiedad "color" dure 1 segundo.
El caso #daetsam
Tenemos que tener en cuenta que no todas las propiedades se pueden animar. Por ejemplo display que es la que queremos animar nosotros, no podemos hacerlo. Asà que nos las tenemos que ingeniar un poco para mostrar una animación.
Lo que haremos será poner el elemento con el alto mÃnimo (1px) para que haga la transición con la propiedad height y en el .opened ponemos la altura máxima (210px)
Lo único que hace este script es abrir y cerrar el menú completo si se hace clic en el icono de menú (#nav-menu-button) o abrir el submenú en caso de que se haga clic en su tÃtulo (#nav-global h2).
Aún tenemos que hacer unos cambios para mejorarlo:
Que tenga animación, es decir, que no haya un corte entre el estado "oculto" y el "visible". Esto lo podremos hacer mediante CSS o mediante Javascript. Es más limpio con CSS.
Anya is live and ready to show you everything. Watch her strip, dance, and perform exclusive shows just for you. Interact in real-time and make your fantasies come true.
✓ Live Streaming✓ Interactive Chat✓ Private Shows✓ HD Quality✓ Free Actions
Free to watch • No registration required • HD streaming