/** Spawns a new fruit onto the board. */ private void spawnFruit() { // Reset the score for this fruit to 100. this.nextFruitScore = 100; /* * Get a random index based on the number of free spaces left on the board. */ int index = random.nextInt(BoardPanel.COL_COUNT * BoardPanel.ROW_COUNT - snake.size()); /* * While we could just as easily choose a random index on the board * and check it if it's free until we find an empty one, that method * tends to hang if the snake becomes very large. * * This method simply loops through until it finds the nth free index * and selects uses that. This means that the game will be able to * locate an index at a relatively constant rate regardless of the * size of the snake. */ int freeFound = -1; for (int x = 0; x < BoardPanel.COL_COUNT; x++) { for (int y = 0; y < BoardPanel.ROW_COUNT; y++) { TileType type = board.getTile(x, y); if (type == null || type == TileType.Fruit) { if (++freeFound == index) { board.setTile(x, y, TileType.Fruit); break; } } } } }
/** * Adds a piece to the game board. Note: Doesn't check for existing pieces, and will overwrite * them if they exist. * * @param type The type of piece to place. * @param x The x coordinate of the piece. * @param y The y coordinate of the piece. * @param rotation The rotation of the piece. */ public void addPiece(TileType type, int x, int y, int rotation) { /* * Loop through every tile within the piece and add it * to the board only if the boolean that represents that * tile is set to true. */ for (int col = 0; col < type.getDimension(); col++) { for (int row = 0; row < type.getDimension(); row++) { if (type.isTile(col, row, rotation)) { setTile(col + x, row + y, type); } } } }
/** Resets the game's variables to their default states and starts a new game. */ private void resetGame() { /* * Reset the score statistics. (Note that nextFruitPoints is reset in * the spawnFruit function later on). */ this.score = 0; this.fruitsEaten = 0; /* * Reset both the new game and game over flags. */ this.isNewGame = false; this.isGameOver = false; /* * Create the head at the center of the board. */ Point head = new Point(BoardPanel.COL_COUNT / 2, BoardPanel.ROW_COUNT / 2); /* * Clear the snake list and add the head. */ snake.clear(); snake.add(head); /* * Clear the board and add the head. */ board.clearBoard(); board.setTile(head, TileType.SnakeHead); /* * Clear the directions and add north as the * default direction. */ directions.clear(); directions.add(Direction.North); /* * Reset the logic timer. */ logicTimer.reset(); /* * Spawn a new fruit. */ spawnFruit(); }
/** * Checks whether or not {@code row} is full. * * @param line The row to check. * @return Whether or not this row is full. */ private boolean checkLine(int line) { /* * Iterate through every column in this row. If any of them are * empty, then the row is not full. */ for (int col = 0; col < COL_COUNT; col++) { if (!isOccupied(col, line)) { return false; } } /* * Since the line is filled, we need to 'remove' it from the game. * To do this, we simply shift every row above it down by one. */ for (int row = line - 1; row >= 0; row--) { for (int col = 0; col < COL_COUNT; col++) { setTile(col, row + 1, getTile(col, row)); } } return true; }
/** * Updates the snake's position and size. * * @return Tile tile that the head moved into. */ private TileType updateSnake() { /* * Here we peek at the next direction rather than polling it. While * not game breaking, polling the direction here causes a small bug * where the snake's direction will change after a game over (though * it will not move). */ Direction direction = directions.peekFirst(); /* * Here we calculate the new point that the snake's head will be at * after the update. */ Point head = new Point(snake.peekFirst()); switch (direction) { case North: head.y--; break; case South: head.y++; break; case West: head.x--; break; case East: head.x++; break; } /* * If the snake has moved out of bounds ('hit' a wall), we can just * return that it's collided with itself, as both cases are handled * identically. */ if (head.x < 0 || head.x >= BoardPanel.COL_COUNT || head.y < 0 || head.y >= BoardPanel.ROW_COUNT) { return TileType.SnakeBody; // Pretend we collided with our body. } /* * Here we get the tile that was located at the new head position and * remove the tail from of the snake and the board if the snake is * long enough, and the tile it moved onto is not a fruit. * * If the tail was removed, we need to retrieve the old tile again * incase the tile we hit was the tail piece that was just removed * to prevent a false game over. */ TileType old = board.getTile(head.x, head.y); if (old != TileType.Fruit && snake.size() > MIN_SNAKE_LENGTH) { Point tail = snake.removeLast(); board.setTile(tail, null); old = board.getTile(head.x, head.y); } /* * Update the snake's position on the board if we didn't collide with * our tail: * * 1. Set the old head position to a body tile. * 2. Add the new head to the snake. * 3. Set the new head position to a head tile. * * If more than one direction is in the queue, poll it to read new * input. */ if (old != TileType.SnakeBody) { board.setTile(snake.peekFirst(), TileType.SnakeBody); snake.push(head); board.setTile(head, TileType.SnakeHead); if (directions.size() > 1) { directions.poll(); } } return old; }