En el artículo anterior, Juego Snake en aplicación de consola (I). Definiendo la pantalla, vimos cómo crear la pantalla inicial del juego Snake, en éste veremos cómo crear la serpiente, dotarla de movimiento y mostrar comida que pueda ir comiendo y aumentando la puntuación y su longitud.
De esta forma completaremos la funcionalidad básica del juego.
Para dibujar la serpiente y dotarla de movimiento voy a definir 3 funciones.
La función GetDirection recibe como argumento la dirección en la que se mueve actualmente la serpiente, comprueba si el jugador ha pulsado alguna de las teclas de dirección y, si es así, devuelve la dirección en la que se debería mover ahora la serpiente.
private static Direction GetDirection(Direction currentDirection) { if (!Console.KeyAvailable) return currentDirection; var key = Console.ReadKey(true).Key; switch (key) { case ConsoleKey.DownArrow: if (currentDirection != Direction.Up) currentDirection = Direction.Down; break; case ConsoleKey.LeftArrow: if (currentDirection != Direction.Right) currentDirection = Direction.Left; break; case ConsoleKey.RightArrow: if (currentDirection != Direction.Left) currentDirection = Direction.Right; break; case ConsoleKey.UpArrow: if (currentDirection != Direction.Down) currentDirection = Direction.Up; break; } return currentDirection; }
Private Function GetDirection(currentDirection As Direction) As Direction If Not Console.KeyAvailable Then Return currentDirection Dim key = Console.ReadKey(True).Key Select Case key Case ConsoleKey.DownArrow If currentDirection <> Direction.Up Then _ currentDirection = Direction.Down Case ConsoleKey.LeftArrow If currentDirection <> Direction.Rigth Then _ currentDirection = Direction.Left Case ConsoleKey.RightArrow If currentDirection <> Direction.Left Then _ currentDirection = Direction.Rigth Case ConsoleKey.UpArrow If currentDirection <> Direction.Down Then _ currentDirection = Direction.Up End Select Return currentDirection End Function
La función GetNextPosition recibe como argumentos la posición actual y la dirección en la que debe moverse la serpiente y devuelve la nueva posición de ésta.
private static Point GetNextPosition(Direction direction, Point currentPosition) { Point nextPosition = new Point(currentPosition.X, currentPosition.Y); switch (direction) { case Direction.Up: nextPosition.Y--; break; case Direction.Left: nextPosition.X--; break; case Direction.Down: nextPosition.Y++; break; case Direction.Right: nextPosition.X++; break; } return nextPosition; }
Private Function GetNextPosition(direction As Direction, currentPosition As Point) Dim nextPosition As New Point(currentPosition.X, currentPosition.Y) Select Case direction Case Direction.Up nextPosition.Y -= 1 Case Direction.Left nextPosition.X -= 1 Case Direction.Down nextPosition.Y += 1 Case Direction.Rigth nextPosition.X += 1 End Select Return nextPosition End Function
Por último, la función MoveSnake recibirá como argumentos la lista de posiciones de la serpiente, la posición destino, la longitud de la serpiente y el tamaño de la pantalla, y se encargará de realizar el movimiento de la serpiente a la ubicación destino.
Esta función devuelve un valor booleano indicando si el movimiento ha sido posible. Si devuelve false indicará que el movimiento no se ha podido realizar (bien porque ha chocado contra su cola o ha salido de los límites de la pantalla) y por lo tanto debería finalizar el juego.
La función comprueba si la posición destino coincide con alguna de las posiciones ya incluidas en la cola de la serpiente o si está fuera de los límites de la pantalla y, si es así, devuelve false.
Si no es así toma la anterior posición de la cabeza y escribe un espacio con fondo verde, que es el color que utilizaremos para la cola. Esta posición, como era hasta este movimiento la cabeza de la serpiente, debería estar pintado con color rojo, que es el color que utilizaremos para pintar la cabeza de la serpiente.
A continuación añade la nueva posición de la cabeza a la cola de la serpiente y la dibuja con color rojo (establecemos el fondo rojo y escribimos un espacio).
Por último comprueba si la longitud de la serpiente supera la longitud máxima y, si es así, extrae la última posición de la cola de la serpiente y la elimina de la pantalla (escribiendo un espacio con fondo negro).
private static bool MoveSnake(Queue<Point> snake, Point targetPosition, int snakeLength, Size screenSize) { var lastPoint = snake.Last(); if (lastPoint.Equals(targetPosition)) return true; if (snake.Any(x => x.Equals(targetPosition))) return false; if (targetPosition.X < 0 || targetPosition.X >= screenSize.Width || targetPosition.Y < 0 || targetPosition.Y >= screenSize.Height) { return false; } Console.BackgroundColor = ConsoleColor.Green; Console.SetCursorPosition(lastPoint.X + 1, lastPoint.Y + 1); Console.WriteLine(" "); snake.Enqueue(targetPosition); Console.BackgroundColor = ConsoleColor.Red; Console.SetCursorPosition(targetPosition.X + 1, targetPosition.Y + 1); Console.Write(" "); // Quitar cola if (snake.Count > snakeLength) { var removePoint = snake.Dequeue(); Console.BackgroundColor = ConsoleColor.Black; Console.SetCursorPosition(removePoint.X + 1, removePoint.Y + 1); Console.Write(" "); } return true; }
Private Function MoveSnake(snake As Queue(Of Point), targetPosition As Point, _ snakeLength As Integer, screenSize As Size) Dim lastPoint = snake.Last() If lastPoint.Equals(targetPosition) Then Return True If snake.Any(Function(x) x.Equals(targetPosition)) Then Return False If targetPosition.X < 0 OrElse targetPosition.X >= screenSize.Width _ OrElse targetPosition.Y < 0 OrElse targetPosition.Y >= screenSize.Height Then Return False End If Console.BackgroundColor = ConsoleColor.Green Console.SetCursorPosition(lastPoint.X + 1, lastPoint.Y + 1) Console.WriteLine(" ") snake.Enqueue(targetPosition) Console.BackgroundColor = ConsoleColor.Red Console.SetCursorPosition(targetPosition.X + 1, targetPosition.Y + 1) Console.Write(" ") ' Quitar cola If snake.Count > snakeLength Then Dim removePoint = snake.Dequeue() Console.BackgroundColor = ConsoleColor.Black Console.SetCursorPosition(removePoint.X + 1, removePoint.Y + 1) Console.Write(" ") End If Return True End Function
Teniendo estas tres funciones simplemente tendremos que declarar un bucle para ir modificando la dirección, calculando la nueva posición y realizando el correspondiente movimiento hasta que un movimiento no se pueda realizar. Momento en el que finalizará el juego mostrando un mensaje de "Game Over".
static void Main() { var score = 0; var speed = 100; var foodPosition = Point.Empty; var screenSize = new Size(60, 20); var snake = new Queue<Point>(); var snakeLength = 3; var currentPosition = new Point(0, 9); snake.Enqueue(currentPosition); var direction = Direction.Right; DrawScreen(screenSize); ShowScore(score); while (MoveSnake(snake, currentPosition, snakeLength, screenSize)) { Thread.Sleep(speed); direction = GetDirection(direction); currentPosition = GetNextPosition(direction, currentPosition); } Console.ResetColor(); Console.SetCursorPosition(screenSize.Width/2 - 4, screenSize.Height/2); Console.Write("Game Over"); Thread.Sleep(2000); Console.ReadKey(); }
Sub Main() Dim score = 0 Dim speed = 100 Dim foodPosition = Point.Empty Dim screenSize As New Size(60, 20) Dim snake As New Queue(Of Point) Dim snakeLength = 3 Dim currentPosition As New Point(0, 9) snake.Enqueue(currentPosition) Dim direction As Direction = Direction.Rigth DrawScreen(screenSize) ShowScore(score) While MoveSnake(snake, currentPosition, snakeLength, screenSize) Thread.Sleep(speed) direction = GetDirection(direction) currentPosition = GetNextPosition(direction, currentPosition) End While Console.ResetColor() Console.SetCursorPosition(screenSize.Width/2 - 4, screenSize.Height/2) Console.Write("Game Over") Thread.Sleep(2000) Console.ReadKey() End Sub
Ahora, si arrancamos la aplicación podremos ver que ya aparece la serpiente y somos capaces de dirigir sus movimientos por la pantalla utilizando las flechas del teclado.
Añadir la comida y puntuación
Nos queda ya un único detalle para completar el juego: mostrar elementos que se pueda "comer" la serpiente de forma que el jugador pueda ir acumulando puntos y, al mismo tiempo, aumentar la longitud de la serpiente para aumentar la dificultad.
Para este último paso definiremos una nueva función ShowFood que recibirá como argumentos el tamaño de la pantalla y las posiciones que ocupa la serpiente. La función calcula de forma aleatoria un punto en el que mostrar la comida de la serpiente, comprobando que el punto no esté ocupado por la serpiente y que tenga una distancia mínima con la cabeza de la serpiente.
Una vez calculado el punto dibuja una espacio azul que representará la comida y devuelve la ubicación al método principal.
private static Point ShowFood(Size screenSize, Queue<Point> snake) { var foodPoint = Point.Empty; var snakeHead = snake.Last(); var rnd = new Random(); do { var x = rnd.Next(0, screenSize.Width - 1); var y = rnd.Next(0, screenSize.Height - 1); if (snake.All(p => p.X != x || p.Y != y) && Math.Abs(x - snakeHead.X) + Math.Abs(y - snakeHead.Y) > 8) { foodPoint = new Point(x, y); } } while (foodPoint == Point.Empty); Console.BackgroundColor = ConsoleColor.Blue; Console.SetCursorPosition(foodPoint.X + 1, foodPoint.Y + 1); Console.Write(" "); return foodPoint; }
Private Function ShowFood(screenSize As Size, snake As Queue(Of Point)) As Point Dim foodPoint = Point.Empty Dim snakeHead = snake.Last() Dim rnd As New Random() Do Dim x = rnd.Next(0, screenSize.Width - 1) Dim y = rnd.Next(0, screenSize.Height - 1) If snake.All(Function(p) p.X <> x OrElse p.Y <> y) _ AndAlso Math.Abs(x - snakeHead.X) + Math.Abs(y - snakeHead.Y) > 8 Then foodPoint = new Point(x, y) End If Loop While foodPoint = Point.Empty Console.BackgroundColor = ConsoleColor.Blue Console.SetCursorPosition(foodPoint.X + 1, foodPoint.Y + 1) Console.Write(" ") Return foodPoint End Function
Ya sólo nos queda modificar el método principal para que vaya mostrando la comida de la serpiente con la función ShowFood. En cada movimiento la aplicación comprobará si la cabeza de la serpiente se ha movido a la ubicación en que se encontraba la comida y, de ser así, aumenta la puntuación y la longitud de la serpiente y muestra un nuevo elemento de comida.
static void Main() { var score = 0; var speed = 100; var foodPosition = Point.Empty; var screenSize = new Size(60, 20); var snake = new Queue<Point>(); var snakeLength = 3; var currentPosition = new Point(0, 9); snake.Enqueue(currentPosition); var direction = Direction.Right; DrawScreen(screenSize); ShowScore(score); while (MoveSnake(snake, currentPosition, snakeLength, screenSize)) { Thread.Sleep(speed); direction = GetDirection(direction); currentPosition = GetNextPosition(direction, currentPosition); if (currentPosition.Equals(foodPosition)) { foodPosition = Point.Empty; snakeLength++; score += 10; ShowScore(score); } if (foodPosition == Point.Empty) { foodPosition = ShowFood(screenSize, snake); } } Console.ResetColor(); Console.SetCursorPosition(screenSize.Width/2 - 4, screenSize.Height/2); Console.Write("Game Over"); Thread.Sleep(2000); Console.ReadKey(); }
Sub Main() Dim score = 0 Dim speed = 100 Dim foodPosition = Point.Empty Dim screenSize As New Size(60, 20) Dim snake As New Queue(Of Point) Dim snakeLength = 3 Dim currentPosition As New Point(0, 9) snake.Enqueue(currentPosition) Dim direction As Direction = Direction.Rigth DrawScreen(screenSize) ShowScore(score) While MoveSnake(snake, currentPosition, snakeLength, screenSize) Thread.Sleep(speed) direction = GetDirection(direction) currentPosition = GetNextPosition(direction, currentPosition) If currentPosition.Equals(foodPosition) Then foodPosition = Point.Empty snakeLength += 1 score += 10 ShowScore(score) End If If foodPosition = Point.Empty Then foodPosition = ShowFood(screenSize, snake) End If End While Console.ResetColor() Console.SetCursorPosition(screenSize.Width/2 - 4, screenSize.Height/2) Console.Write("Game Over") Thread.Sleep(2000) Console.ReadKey() End Sub
Y de esta forma tendríamos ya la operativa principal del juego funcionando:
A partir de aquí se podría añadir otras funciones como paredes que tenga que evitar la serpiente, diferentes diseños de pantallas, diferentes niveles de dificultad modificando la velocidad o la posibilidad de guardar las puntuaciones logradas.
Pero estas cosas ya las dejo para cada uno. A mí me apetece ponerme a jugar un par de partidas.
El código completo tanto en C# como en Visual Basic .NET está disponible en:
Códigos de muestra - Ejemplos MSDN. Juego Snake en aplicación de consola
No hay comentarios:
Publicar un comentario