In my mining-survial processing game, I need to implement animations that go through a sprite sheet for the player, enemy, and block states. Right now only static images are used but I need to implement a system into my program that supports animations instead. Firstly I want to implement player animations, my player sprite "BlueMiner.png" sheet is 192 x336 pixels and has 7 rows of 4 columns with each row being a different animation. The game should update the sprites according to their animation state, with that being said, please implement a system to load these sprite sheets starting with the player: 1st row is walking facing towards camera, 2nd row is walking facing away, 3rd is walking facing right, fourth is dying, fifth is attack facing, sixth is attack facing away, seventh is attack facing right. When  the player is in a left-looking state, they should use the right-looking sprites FLIPPED to make it look like left using matrices or other technique.

here is my main class:
// Game constants
final int TILE_SIZE = 32;
final int GRID_WIDTH = 40;
final int GRID_HEIGHT = 30;
final int SCREEN_WIDTH = GRID_WIDTH * TILE_SIZE;
final int SCREEN_HEIGHT = GRID_HEIGHT * TILE_SIZE;
final int MINING_RANGE = 2;
final int ENEMY_DETECTION_RANGE = 10 * TILE_SIZE;

// Game objects
Player player;
ArrayList<Enemy> enemies;
Tile[][] map;
ArrayList<Bullet> bullets;

// UI elements
int health = 4;
int ammo = 100;

// Sprites and animations
PImage playerSprite;
PImage enemySprite;
PImage[] oreSprites;
PImage pickaxeSprite;
PImage gunSprite;

// Game state
boolean mining = false;
boolean isMousePressed = false;
Tile currentHoveredTile = null;

float screenShakeAmount = 0;
float screenShakeDecay = 0.9;
float redFlashAlpha = 0;

// Resource tracking
HashMap<String, Integer> inventory;

void setup() {
    size(1280, 960);
    
    //Load sprites
    playerSprite = loadImage("player.png");
    enemySprite = loadImage("enemy.png");
    oreSprites = new PImage[4];
    for (int i = 0; i < 4; i++) {
        oreSprites[i] = loadImage("ore" + i + ".png");
    }
    pickaxeSprite = loadImage("pickaxe.png");
    gunSprite = loadImage("gun.png");
    
    //Initialize game objects
    player = new Player(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2);
    enemies = new ArrayList<Enemy>();
    enemies.add(new Enemy(100, 100)); // Add first enemy
    bullets = new ArrayList<Bullet>();
    
    //Initialize inventory
    inventory = new HashMap<String, Integer>();
    inventory.put("Rock", 0);
    inventory.put("Ore0", 0);
    inventory.put("Ore1", 0);
    inventory.put("Ore2", 0);
    
    //Generate or load the map
    generateMap();
}

void draw() {
    background(0);
    
    if (screenShakeAmount > 0) {
        translate(random( - screenShakeAmount, screenShakeAmount), random( - screenShakeAmount, screenShakeAmount));
        screenShakeAmount *= screenShakeDecay;
        if (screenShakeAmount < 0.1) screenShakeAmount = 0;
    }
    
    //Updateand draw map
    drawMap();
    
    //Updateand draw player
    player.update();
    player.display();
    
    //Update and draw enemies
    for (int i = enemies.size() - 1; i >= 0; i--) {  // traditional loop to allow concurrent removal when dead
        Enemy enemy = enemies.get(i);
        if (!enemy.isAlive()) {
            enemies.remove(i);
            continue;
        }
        enemy.update();
        enemy.display();

        if (dist(player.x, player.y, enemy.x, enemy.y) < TILE_SIZE && player.canTakeDamage()) {
            player.takeDamage();
            screenShakeAmount = 10;
            redFlashAlpha = 255;
        }
    }
    
    //Update anddraw bullets
   for (int i = bullets.size() - 1; i >= 0; i--) {
        Bullet bullet = bullets.get(i);
        bullet.update();
        bullet.display();

        if (bullet.isOffscreen()) {
            bullets.remove(i);
            continue;
        }

        for (int j = enemies.size() - 1; j >= 0; j--) {
            Enemy enemy = enemies.get(j);
            if (dist(bullet.x, bullet.y, enemy.x, enemy.y) < TILE_SIZE / 2) {
                enemy.takeDamage(10); // Assume each bullet deals 10 damage
                bullets.remove(i);
                break;
            }
        }
    }
    
    if (mining) {
        int tileX = floor(mouseX / TILE_SIZE);
        int tileY = floor(mouseY / TILE_SIZE);
        currentHoveredTile = map[tileX][tileY];
        
        if (isMousePressed) {}
        if (isWithinMiningRange(tileX, tileY)) {
            TileType minedType = map[tileX][tileY].mine();
            if (minedType != null) {
                addToInventory(minedType);
            }
        }
        drawMiningProgressBar();
        
    }
    
    if (redFlashAlpha > 0) {
        fill(102, 0, 0, redFlashAlpha);
        rect(0, 0, width, height);
        redFlashAlpha *= 0.7;
    }
    
    drawUI();
}

void drawMiningProgressBar() {
    if (currentHoveredTile != null && currentHoveredTile.type != TileType.EMPTY) {
        float barWidth = 200;
        float barHeight = 20;
        float x = (width - barWidth) / 2;
        float y = height - barHeight - 10;
        
        fill(100);
        rect(x, y, barWidth, barHeight);
        
        // Draw progress
        float progress = map(currentHoveredTile.health, 0, currentHoveredTile.getMaxHealth(), 0, barWidth);
        fill(96, 60, 31);
        rect(x, y, progress, barHeight);
        
        // Draw text
        fill(255);
        textAlign(CENTER, CENTER);
        text(currentHoveredTile.health + " / " + currentHoveredTile.getMaxHealth(), x + barWidth / 2, y + barHeight / 2);
        textAlign(LEFT);
    }
}

void addToInventory(TileType type) {
    switch(type) {
        case ROCK:
            inventory.put("Rock", inventory.get("Rock") + 1);
            break;
        case ORE0:
            inventory.put("Ore0", inventory.get("Ore0") + 1);
            break;
        case ORE1:
            inventory.put("Ore1", inventory.get("Ore1") + 1);
            break;
        case ORE2:
            inventory.put("Ore2", inventory.get("Ore2") + 1);
            break;
    }
}

boolean isWithinMiningRange(int tileX, int tileY) {
    int playerTileX = floor(player.x / TILE_SIZE);
    int playerTileY = floor(player.y / TILE_SIZE);
    return abs(tileX - playerTileX) <= MINING_RANGE && abs(tileY - playerTileY) <= MINING_RANGE;
}


void generateMap() {
    map = new Tile[GRID_WIDTH][GRID_HEIGHT];
    
    // Initialize all tiles as empty
    for (int x = 0; x < GRID_WIDTH; x++) {
        for (int y = 0; y < GRID_HEIGHT; y++) {
            map[x][y] = new Tile(TileType.EMPTY);
        }
    }
    
    // Place initial random rocks
    for (int i = 0; i < GRID_WIDTH * GRID_HEIGHT * 0.4; i++) {
        int x = int(random(GRID_WIDTH));
        int y = int(random(GRID_HEIGHT));
        map[x][y].type = TileType.ROCK;
    }
    
    // Apply cellular automata to create clusters
    for (int step = 0; step < 5; step++) {
        Tile[][] newMap = new Tile[GRID_WIDTH][GRID_HEIGHT];
        for (int x = 0; x < GRID_WIDTH; x++) {
            for (int y = 0; y < GRID_HEIGHT; y++) {
                int neighbors = countRockNeighbors(x, y);
                
                if (map[x][y].type == TileType.ROCK) {
                    // Rock survives with 4 or more rock neighbors
                    newMap[x][y] = new Tile(neighbors >= 4 ? TileType.ROCK : TileType.EMPTY);
                } else {
                    // Empty becomes rock with 5 or more rock neighbors
                    newMap[x][y] = new Tile(neighbors >= 5 ? TileType.ROCK : TileType.EMPTY);
                }
            }
        }
        map = newMap;
    }
    
    // Generate ore clusters coherently
    int numClusters = 10;
    for (int i = 0; i < numClusters; i++) {
        int clusterX = int(random(GRID_WIDTH));
        int clusterY = int(random(GRID_HEIGHT));
        int clusterSize = int(random(5, 15));
        int oreType = int(random(0,4)); // 0, 1, or 2 for different ore types
        
        generateCluster(clusterX, clusterY, clusterSize, oreType);
    }
}


int countRockNeighbors(int x, int y) {
    int count = 0;
    for (int dx = -1; dx <= 1; dx++) {
        for (int dy = -1; dy <= 1; dy++) {
            if (dx == 0 && dy == 0) continue;
            
            int nx = x + dx;
            int ny = y + dy;
            
            if (nx >= 0 && nx < GRID_WIDTH && ny >= 0 && ny < GRID_HEIGHT && map[nx][ny].type == TileType.ROCK) {
                count++;
            }
        }
    }
    return count;
}

void generateCluster(int startX, int startY, int size, int oreType) {
    for (int i = 0; i < size; i++) {
        int x = startX + int(random( -3, 4));
        int y = startY + int(random( -3, 4));
        
        if (x >= 0 && x < GRID_WIDTH && y >= 0 && y < GRID_HEIGHT) {
            TileType type = (oreType == 0) ? TileType.ORE0 : (oreType == 1) ? TileType.ORE1 : TileType.ORE2;
            map[x][y] = new Tile(type);
        }
    }
}

void drawMap() {
    for (int x = 0; x < GRID_WIDTH; x++) {
        for (int y = 0; y < GRID_HEIGHT; y++) {
            map[x][y].display(x * TILE_SIZE, y * TILE_SIZE);
        }
    }
}

void drawUI() {
    // Reset any transformations to ensure UI is drawn in the correct position
    resetMatrix();
    
    fill(255);
    textSize(20);
    text("Health: " + player.health, 10, 30);
    text("Ammo: " + ammo, 10, 60);
    text("Mining: " + (mining ? "ON" : "OFF"), 10, 90);
    
    // Display inventory
    text("Inventory:", 10, 120);
    int yOffset = 150;
    for (String resource : inventory.keySet()) {
        text(resource + ": " + inventory.get(resource), 10, yOffset);
        yOffset += 30;
    }
    
    drawMiningProgressBar();
}

void keyPressed() {
    // if(key == ' ') {
    //     bullets.add(new Bullet(player.x, player.y, player.facingDirection));
    //     ammo--;
// }
    if (key == 'm' || key == 'M') {
        mining = !mining;
    }
    player.setMove(keyCode, true);
}

void keyReleased() {
    player.setMove(keyCode, false);
}

void mousePressed() {
    isMousePressed = true;
    if (mining) {
        // Mining logic in draw()
    } else if (mouseButton == LEFT) {
        player.shoot();
        ammo--;
    }
}

void mouseReleased() {
    isMousePressed = false;
}

enum TileType {
    EMPTY, ROCK, ORE0, ORE1, ORE2
}


here is my player class:
class Player {
    float x, y;
    int facingDirection; // This can still be used for animations
    float speed = 3;
    boolean[] keys = new boolean[4]; // 0: up, 1: down, 2: left, 3: right
    int health;
    int maxHealth = 3;
    float invulnerabilityTimer = 0;
    float invulnerabilityDuration = 1;

    Player(float x, float y) {
        this.x = x;
        this.y = y;
        this.facingDirection = 0;
        this.health = maxHealth;
    }

    void setMove(int k, boolean b) {
        switch(k) {
            case 87:
                keys[0] = b;
                break;
            case 83:
                keys[1] = b;
                break;
            case 65:
                keys[2] = b;
                break;
            case 68:
                keys[3] = b;
                break;
        }
    }
    
    void update() {
        float newX = x;
        float newY = y;
        
        if (keys[0]) {
            newY -= speed;
            facingDirection = 1;
        }
        if (keys[1]) {
            newY += speed;
            facingDirection = 1;
        }
        if (keys[2]) {
            newX -= speed;
            facingDirection = 2;
        }
        if (keys[3]) {
            newX += speed;
            facingDirection = 0;
        }
        
        if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
            int tileX = floor(newX / TILE_SIZE);
            int tileY = floor(newY / TILE_SIZE);
            if (map[tileX][tileY].type == TileType.EMPTY) {
                x = newX;
                y = newY;
            }
        }
          
        if (invulnerabilityTimer > 0) {
            invulnerabilityTimer -= 1.0 / frameRate;
        }
    }
    
    void display() {
        pushMatrix();
        translate(x, y);
        
        if (invulnerabilityTimer > 0 && (int)(invulnerabilityTimer * 10) % 2 == 0) {
            tint(255, 128); // Semi-transparent
        }

        if (facingDirection == 1 || facingDirection == 3) {
            rotate(HALF_PI * (facingDirection - 1));
        } else {
            scale(facingDirection == 2 ? - 1 : 1, 1);
        }
        
        image(playerSprite, -TILE_SIZE / 2, -TILE_SIZE / 2, TILE_SIZE, TILE_SIZE);
        
        if (mining) {
            image(pickaxeSprite, 0, -TILE_SIZE / 2, TILE_SIZE, TILE_SIZE);
        } else {
            image(gunSprite, 0, -TILE_SIZE / 2, TILE_SIZE, TILE_SIZE);
        }
        
        popMatrix();
        noTint();
    }

    boolean canTakeDamage() {
        return invulnerabilityTimer <= 0;
    }

    void takeDamage() {
        if (canTakeDamage()) {
            health--;
            invulnerabilityTimer = invulnerabilityDuration;
        }
    }
    
    void shoot() {
        float angle = atan2(mouseY - y, mouseX - x);
        bullets.add(new Bullet(x, y, angle));
    }
}