Corrigiendo un último error

Sincronizando teclado y juego

Después de un rato de estar jugando nuestro juego ya terminado, quizá nos demos cuenta de un pequeño y horrible error (O mejor dicho, horror) que sucede en el juego. Quizá ya vayas por la longitud 30, teniendo tu mejor record, entonces pasas la manzana al lado, regresas por ella y ¡Pum! ¡Game Over! ¿Cómo? ¿Que pasó? Bueno, si aun no te ha sucedido, has esto:

Iniciando el juego, presiona arriba izquierda, abajo derecha tan rápido como puedeas. Si lo haces con la suficiente velocidad, notarás que tras un ratito, el juego lanza un "Game Over" al parecer inexplicable. ¿Que es lo que sucede aquí? Intentaré explicarlo de forma sencilla:

Como recordarás, tenemos una función que controla cuando presionamos las teclas, y otra distinta que controlaba el flujo de nuestro juego. Desafortunadamente, estas dos tienen una sincronía distinta, pues el juego se actualiza cada 50 milisegundos, mientras que el teclado se actualiza en tiempo real. Por tanto, aunque hayamos impedido que de medias vueltas, si uno da arriba y luego derecha, la máquina nota esto como que no dio una media vuelta, aunque esto haya sido más rápido que la actualización del juego en si, y por tanto, a la siguiente que se actualiza, la serpiente choca contra su propio cuerpo, dando como resultado este error fatal. ¿Cómo arreglarlo? La mejor solución es sincronizar de alguna forma el teclado con el juego, y esta es la forma en que yo lo he logrado. Primero, declaremos una variable:

	boolean leible;

Con esta variable, indicaremos al juego cuando pueda leer una nueva tecla (Que será después de que el juego se haya actualizado y no se haya presionado una nueva tecla antes). Para lograr este efecto, vayamos primero al teclado y cambiemos la primer condición:

			case Event.UP:
				if (direccion != 2 && leible)

Con esto le decimos que solo hará el cambio cuando la direscción no sea la 2 (abajo) Y ADEMáS solo cuando sea leible. Tras esto, hacemos que además de cambiar dirección leible se vuelva falso para que no pueda volverse a leer hasta que se actualize, repetimos este proceso para todas las condiciones, quedando al final así:

			//	Cambia el rumbo de la serpiente
			case Event.UP:
				if (direccion != 2 && leible)
				{
					direccion = 0;
					leible = false;
				}
				break;
			case Event.RIGHT:
				if (direccion != 3 && leible)
				{
					direccion = 1;
					leible = false;
				}
				break;
			case Event.DOWN:
				if (direccion != 0 && leible)
				{
					direccion = 2;
					leible = false;
				}
				break;
			case Event.LEFT:
				if (direccion != 1 && leible)
				{
					direccion = 3;
					leible = false;
				}
				break;

Ahora, debemos asegurarnos que las teclas puedan volver a ser leibles tan pronto como termine de moverse la cabeza, agregando esta línea:

			leible = true;

De esta forma, sincronizaremos el teclado con el juego. Presionemos Shift+F6 para probarlo, tu código final debería quedar de esta forma:

import GameLib.Game;
import java.awt.Graphics;
import java.awt.Color;
import java.awt.Point;
import java.awt.Event;

public class snake extends Game
{
	// Aquí se declaran nuestras variables
	boolean PAUSE = true;
	int maxlong = 500;
	Point serpiente[] = new Point[maxlong];
	Point manzana = new Point (100, 100);
	int direccion = 1;
	int longitud = 1;
	boolean gameover;
	boolean leible;
	
	public void init()
	{
		// Comienza descarga asíncrona de recursos pesados
		setBackground(Color.black);
		for (int i = 0; i < maxlong; i++)
		{
			serpiente[i] = new Point(0, 0);
		}
		reset();
	}
	
	public void reset()
	{
		serpiente[0].x=50;
		serpiente[0].y=50;
		direccion = 1;
		longitud = 3;
		manzana.x = (int)(Math.random() * (this.getWidth()/10 - 1)) * 10;
		manzana.y = (int)(Math.random() * (this.getHeight()/10 - 2)) * 10 + 10;
		gameover = false;
	}

	public void game()
	{
		// Aquí va el código de nuestro juego
		if (!PAUSE)
		{
			//	Se mueve el serpiente
			for (int i = longitud; i > 0; i--)
			{
				serpiente[i].x = serpiente[i-1].x;
				serpiente[i].y = serpiente[i-1].y;
			}
				
			//	Se mueve el serpiente[0]
			switch	(direccion)
			{
				case 0:
					serpiente[0].y -= 10;
					break;
				case 1:
					serpiente[0].x += 10;
					break;
				case 2:
					serpiente[0].y += 10;
					break;
				case 3:
					serpiente[0].x -= 10;
					break;
			}
			leible = true;
			
			//	Salida del escenario
			if (serpiente[0].x < 0)
			{
				serpiente[0].x = this.getWidth()-10;
			}
			if (serpiente[0].x > this.getWidth()-10)
			{
				serpiente[0].x = 0;
			}
			if (serpiente[0].y < 10)
			{
				serpiente[0].y = this.getHeight()-10;
			}
			if (serpiente[0].y > this.getHeight()-10)
			{
				serpiente[0].y = 10;
			}
			
			//	Colisión serpiente[0]-Manzana
			if (serpiente[0].x == manzana.x && serpiente[0].y == manzana.y)
			{
				longitud += 1;
				manzana.x = (int)(Math.random() * (this.getWidth()/10 - 1)) * 10;
				manzana.y = (int)(Math.random() * (this.getHeight()/10 - 2)) * 10 + 10;
			}
				
				//	Colisión con el serpiente
				for (int i = 2; i < longitud; i++)
				{
					if (serpiente[0].x == serpiente[i].x && serpiente[0].y == serpiente[i].y)
					{
						PAUSE = true;
						gameover = true;
					}
				}
		}
	}
	
	public void paint(Graphics g)
	{
		// Aquí se dibujan todos los objetos
		g.clearRect(0, 0, this.getWidth(), this.getHeight());
		g.setColor(Color.green);
		for (int i = 0; i < longitud; i++)
		{
			g.drawOval(serpiente[i].x, serpiente[i].y, 10, 10);
		}
		g.setColor(Color.red);
		g.drawOval(manzana.x, manzana.y, 10, 10);
		g.setColor(Color.white);
		g.drawString("Longitud: " + longitud, 10, 10);
		if (gameover)
		{
			g.drawString("GAME OVER", this.getWidth()/2-30, this.getHeight()/2);
		}
		else if (PAUSE)
		{
			g.drawString("PAUSA", this.getWidth()/2-20, this.getHeight()/2);
		}
	}
	
	public boolean keyDown(Event e, int key)
	{
		switch(key)
		{
			//	Cambia el rumbo de la serpiente
			case Event.UP:
				if (direccion != 2 && leible)
				{
					direccion = 0;
					leible = false;
				}
				break;
			case Event.RIGHT:
				if (direccion != 3 && leible)
				{
					direccion = 1;
					leible = false;
				}
				break;
			case Event.DOWN:
				if (direccion != 0 && leible)
				{
					direccion = 2;
					leible = false;
				}
				break;
			case Event.LEFT:
				if (direccion != 1 && leible)
				{
					direccion = 3;
					leible = false;
				}
				break;
			case Event.ENTER:
				PAUSE = !PAUSE;
				if (gameover)
				{
					reset();
				}
				break;
		}
		return true;
	}
}

Y el juego debe de verse así:

¡Felicidades! Hemos terminado el juego. Con esto hemos obtenido todos los conocimientos básicos sobre programación de juegos, y si has sido capaz de comprender lo que hicimos hasta ahora, con un poco de creatividad ¡Serás capaz de crear tur propios juegos! Nosotros por nuestra parte seguiremos subiendo algunos extras para que puedas mejorar tu juego y cualquier otro que hagas.

Por favor deja tus comentarios en nuestro Libro de visitas o envianos un correo para saber tu opinión de este curso. Si vemos buena respuesta, nos impulsará a crear más cursos para hacer nuevos juegos con distintas opciones. ¡Muchas gracias, y divierte con tu nueva creación!

[Un proyecto de Ayotli Diseño Web]