package com.example.flappybirdgame;

import android.content.Context;
import android.content.SharedPreferences;

public class Achievement {

    private String name;           // Name of the achievement
    private String description;    // Description of the achievement
    private boolean unlocked;      // Unlock status
    private String key;            // Unique key for saving to SharedPreferences

    public Achievement(String name, String description, String key) {
        this.name = name;
        this.description = description;
        this.key = key;
        this.unlocked = false;
    }

    // Load the unlock status from SharedPreferences
    public void load(Context context) {
        SharedPreferences prefs = context.getSharedPreferences("achievements", Context.MODE_PRIVATE);
        unlocked = prefs.getBoolean(key, false);
    }

    // Save the unlock status to SharedPreferences
    public void save(Context context) {
        SharedPreferences prefs = context.getSharedPreferences("achievements", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putBoolean(key, unlocked);
        editor.apply();
    }

    // Unlock the achievement
    public void unlock(Context context) {
        if (!unlocked) {
            unlocked = true;
            save(context); // Save the new status
        }
    }

    // Getters
    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public boolean isUnlocked() {
        return unlocked;
    }

    public String getKey() {
        return key;
    }

    // Setters
    public void setUnlocked(boolean unlocked) {
        this.unlocked = unlocked;
    }
}
package com.example.flappybirdgame;

import android.content.Context;
import android.content.SharedPreferences;
import android.widget.Toast;

import java.util.ArrayList;

public class AchievementManager {

    private ArrayList<Achievement> achievements = new ArrayList<>();
    private Context context;

    public AchievementManager(Context context) {
        this.context = context;

        // Define achievements
        achievements.add(new Achievement(context.getString(R.string.flap_1_time),
                context.getString(R.string.flap_1_time_desc), "achievement_first_flap"));
        achievements.add(new Achievement(context.getString(R.string.flap_500_times),
                context.getString(R.string.flap_500_times_desc), "achievement_500_flaps"));
        achievements.add(new Achievement(context.getString(R.string.score_100_points),
                context.getString(R.string.score_100_points_desc), "achievement_high_scorer"));

        // Load unlocked achievements
        loadAchievements();
    }

    // Changed access modifier from private to public
    public void loadAchievements() {
        SharedPreferences prefs = context.getSharedPreferences("achievements", Context.MODE_PRIVATE);
        for (Achievement achievement : achievements) {
            achievement.setUnlocked(prefs.getBoolean(achievement.getKey(), false));
        }
    }

    public void saveAchievements() {
        SharedPreferences prefs = context.getSharedPreferences("achievements", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        for (Achievement achievement : achievements) {
            editor.putBoolean(achievement.getKey(), achievement.isUnlocked());
        }
        editor.apply();
    }

    public void checkAchievements(int flapCount, int score) {
        for (Achievement achievement : achievements) {
            if (!achievement.isUnlocked()) {
                if (achievement.getKey().equals("achievement_first_flap") && flapCount >= 1) {
                    achievement.unlock(context);
                    notifyPlayer(achievement);
                } else if (achievement.getKey().equals("achievement_500_flaps") && flapCount >= 500) {
                    achievement.unlock(context);
                    notifyPlayer(achievement);
                } else if (achievement.getKey().equals("achievement_high_scorer") && score >= 100) {
                    achievement.unlock(context);
                    notifyPlayer(achievement);
                }
            }
        }
        saveAchievements(); // Save after checking
    }

    private void notifyPlayer(Achievement achievement) {
        // Notify the player about the unlocked achievement
        String message = String.format(context.getString(R.string.achievement_unlocked),
                achievement.getName());
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }

    // Getter for achievements
    public ArrayList<Achievement> getAchievements() {
        return achievements;
    }
}
package com.example.flappybirdgame;

import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;

public class AchievementsActivity extends AppCompatActivity {
    private ListView achievementsListView;
    private ArrayAdapter<String> adapter;
    private ArrayList<String> achievementsList = new ArrayList<>();
    private AchievementManager achievementManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_achievements);

        achievementManager = new AchievementManager(this);
        achievementsListView = findViewById(R.id.achievementsListView);

        // Load achievements and populate the list
        loadAchievements();

        adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, achievementsList);
        achievementsListView.setAdapter(adapter);
    }

    /**
     * Loads achievements and updates the ListView.
     */
    private void loadAchievements() {
        achievementsList.clear();
        for (Achievement achievement : achievementManager.getAchievements()) {
            String status = achievement.isUnlocked() ? getString(R.string.unlocked) : getString(R.string.locked);
            achievementsList.add(achievement.getName() + " - " + status);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        // Reload and update achievements in case new ones were unlocked
        achievementManager.loadAchievements();
        loadAchievements();
        adapter.notifyDataSetChanged();
    }
}
package com.example.flappybirdgame;

import android.content.Context;
import android.content.SharedPreferences;
import android.media.AudioAttributes;
import android.media.SoundPool;
import android.util.DisplayMetrics;

import java.util.ArrayList;

/**
 * Holds various constants and settings used throughout the game.
 */
public class AppConstants {

    // Screen dimensions and ratios
    public static int SCREEN_WIDTH;
    public static int SCREEN_HEIGHT;
    public static float SCREEN_RATIO_X;
    public static float SCREEN_RATIO_Y;

    // Game speeds (Adjusted for better gameplay)
    public static int BACKGROUND_SPEED = 5; // Reduced from 10
    public static int PIPE_SPEED = 6;        // Reduced from 10

    public static int PIPE_GAP_SIZE = 500;

    // Sound management
    public static SoundPool soundPool;
    public static int soundWing, soundHit, soundPoint;
    public static boolean SOUND_ENABLED = true;

    // Game context
    public static Context gameActivityContext;

    // Ground position
    public static int GROUND_Y;

    // Difficulty and settings
    public static int DIFFICULTY = 1;
    public static boolean INVERTED_CONTROLS = false;
    public static int SENSITIVITY = 5;

    // Themes and birds
    public static Theme currentTheme;
    public static ArrayList<Theme> availableThemes;

    // Localization
    public static String LANGUAGE = "en";

    // Initialize constants
    public static void initialization(Context context) {
        gameActivityContext = context;

        // Initialize screen dimensions
        DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        SCREEN_WIDTH = displayMetrics.widthPixels;
        SCREEN_HEIGHT = displayMetrics.heightPixels;

        SCREEN_RATIO_X = SCREEN_WIDTH / 1080f;
        SCREEN_RATIO_Y = SCREEN_HEIGHT / 1920f;

        // Initialize SoundPool
        AudioAttributes audioAttributes = new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_GAME)
                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                .build();

        soundPool = new SoundPool.Builder()
                .setAudioAttributes(audioAttributes)
                .setMaxStreams(5)
                .build();

        // Load sounds
        soundWing = soundPool.load(context, R.raw.wing, 1);
        soundHit = soundPool.load(context, R.raw.hit, 1);
        soundPoint = soundPool.load(context, R.raw.point, 1);

        // Initialize themes
        availableThemes = new ArrayList<>();

        // Add default theme
        availableThemes.add(new Theme(
                R.drawable.background,     // Background
                R.drawable.pipe_top,       // Pipe Top
                R.drawable.pipe_bottom     // Pipe Bottom
        ));

        // Load settings from SharedPreferences
        SharedPreferences prefs = context.getSharedPreferences("game", Context.MODE_PRIVATE);
        SOUND_ENABLED = prefs.getBoolean("soundEnabled", true);
        DIFFICULTY = prefs.getInt("difficulty", 1);
        INVERTED_CONTROLS = prefs.getBoolean("invertedControls", false);
        SENSITIVITY = prefs.getInt("sensitivity", 5);
        LANGUAGE = prefs.getString("language", "en");

        // Load current theme index
        int themeIndex = prefs.getInt("currentTheme", 0);
        if (themeIndex < 0 || themeIndex >= availableThemes.size()) {
            themeIndex = 0; // Default to first theme
        }
        currentTheme = availableThemes.get(themeIndex);

        // Initialize BirdManager
        BirdManager.initialize(context);
    }

    // Method to save settings
    public static void saveSettings(Context context) {
        SharedPreferences prefs = context.getSharedPreferences("game", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putBoolean("soundEnabled", SOUND_ENABLED);
        editor.putInt("difficulty", DIFFICULTY);
        editor.putBoolean("invertedControls", INVERTED_CONTROLS);
        editor.putInt("sensitivity", SENSITIVITY);
        editor.putString("language", LANGUAGE);
        editor.apply();
    }

    // Method to set the current theme
    public static void setCurrentTheme(int index, Context context) {
        if (index >= 0 && index < availableThemes.size()) {
            currentTheme = availableThemes.get(index);
            SharedPreferences prefs = context.getSharedPreferences("game", Context.MODE_PRIVATE);
            SharedPreferences.Editor editor = prefs.edit();
            editor.putInt("currentTheme", index);
            editor.apply();
        }
    }
}
package com.example.flappybirdgame;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;

/**
 * Represents the background in the game, handling its position and rendering.
 */
public class Background {
    public int x = 0, y = 0;
    private Bitmap background;

    /**
     * Constructor for Background.
     * Initializes and scales the background bitmap to screen size.
     */
    public Background() {
        Bitmap original = ResourceManager.getInstance(AppConstants.gameActivityContext)
                .getBitmap(AppConstants.currentTheme.backgroundResourceId);

        if (original == null) {
            Log.e("Background", "Background bitmap is null. Using default background image.");
            original = BitmapFactory.decodeResource(AppConstants.gameActivityContext.getResources(),
                    android.R.drawable.screen_background_dark);
            if (original != null) {
                background = Bitmap.createScaledBitmap(original,
                        AppConstants.SCREEN_WIDTH, AppConstants.SCREEN_HEIGHT, false);
            } else {
                Log.e("Background", "Default background bitmap is also null. Rendering will fail.");
            }
        } else {
            // Scale the background bitmap to match screen dimensions
            background = Bitmap.createScaledBitmap(original,
                    AppConstants.SCREEN_WIDTH, AppConstants.SCREEN_HEIGHT, false);
        }
    }

    /**
     * Updates the background's horizontal position.
     */
    public void update() {
        if (background != null) {
            x -= AppConstants.BACKGROUND_SPEED;
            if (x + background.getWidth() < 0) {
                x = background.getWidth();
            }
        }
    }

    /**
     * Draws the background bitmap onto the canvas.
     *
     * @param canvas The Canvas object where the background will be drawn.
     */
    public void draw(Canvas canvas) {
        if (background != null) {
            canvas.drawBitmap(background, x, y, null);
        } else {
            // Fallback: Fill with black if background is unavailable
            canvas.drawColor(Color.BLACK);
        }
    }

    /**
     * Retrieves the background bitmap.
     *
     * @return The background bitmap, or null if not available.
     */
    public Bitmap getBitmap() {
        return background;
    }
}
package com.example.flappybirdgame;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Rect;
import android.util.Log;

import java.util.List;

/**
 * Represents the bird in the game, handling its position, animation, and interactions.
 */
public class Bird {
    private float x, y;              // Position as float
    private float velocity = 0;      // Current velocity
    private float gravity = 1;       // Gravity
    private float flapForce = -15;   // Flap force
    private int width, height;       // Dimensions
    private Bitmap[] birdFrames;     // Animation frames
    private int frameIndex = 0;      // Current frame index
    private int frameCount;          // Number of frames
    private int frameDelay = 5;      // Delay for animation speed
    private int frameTicker = 0;     // Frame ticker
    private Rect collisionRect;      // Collision rectangle
    private boolean isInvincible = false;   // Invincibility state
    private boolean isMagnetActive = false; // Magnet power-up state
    private int originalWidth, originalHeight; // Original dimensions
    private boolean isShrunk = false;       // Shrink power-up state

    /**
     * Constructor for Bird.
     *
     * @param screenY The height of the screen.
     */
    public Bird(int screenY) {
        // Fetch the current selected bird's frames from BirdManager
        List<Integer> currentBirdFrames = BirdManager.getCurrentBirdFrames();

        frameCount = currentBirdFrames.size();
        birdFrames = new Bitmap[frameCount];
        for (int i = 0; i < frameCount; i++) {
            Bitmap originalFrame = ResourceManager.getInstance(AppConstants.gameActivityContext)
                    .getBitmap(currentBirdFrames.get(i));
            if (originalFrame != null) {
                // Scale each frame to 5% of screen width
                int desiredWidth = (int) (AppConstants.SCREEN_WIDTH * 0.05f);
                birdFrames[i] = Bitmap.createScaledBitmap(originalFrame, desiredWidth,
                        originalFrame.getHeight() * desiredWidth / originalFrame.getWidth(), false);
            } else {
                Log.e("Bird", "Bird frame " + i + " is null.");
                // Use a default frame or a placeholder
                birdFrames[i] = BitmapFactory.decodeResource(AppConstants.gameActivityContext.getResources(),
                        android.R.drawable.sym_def_app_icon);
                if (birdFrames[i] != null) {
                    birdFrames[i] = Bitmap.createScaledBitmap(birdFrames[i], 50, 50, false); // default size
                }
            }
        }

        if (birdFrames[0] != null) {
            width = birdFrames[0].getWidth();
            height = birdFrames[0].getHeight();
        }

        // Initialize position as float
        x = AppConstants.SCREEN_WIDTH * 0.1f;
        y = screenY / 2f - height / 2f;

        collisionRect = new Rect();
    }

    /**
     * Gets the current animation frame.
     *
     * @return The current Bitmap frame.
     */
    public Bitmap getCurrentFrame() {
        return birdFrames[frameIndex];
    }

    /**
     * Updates the bird's position and animation.
     */
    public void update() {
        velocity += gravity;
        y += velocity;

        // Prevent bird from going off the screen
        if (y + height > AppConstants.GROUND_Y) {
            y = AppConstants.GROUND_Y - height;
            velocity = 0;
        }

        if (y < 0) {
            y = 0;
            velocity = 0;
        }

        // Update the collision rectangle
        collisionRect.set((int) x, (int) y, (int) x + width, (int) y + height);

        // Update animation frame
        frameTicker++;
        if (frameTicker >= frameDelay) {
            frameTicker = 0;
            frameIndex++;
            if (frameIndex >= frameCount) {
                frameIndex = 0;
            }
        }
    }

    /**
     * Makes the bird flap.
     *
     * @param sensitivity The sensitivity level affecting flap force.
     */
    public void flap(int sensitivity) {
        float adjustedFlapForce = flapForce * (sensitivity / 5.0f);
        if (AppConstants.INVERTED_CONTROLS) {
            velocity = -adjustedFlapForce;
        } else {
            velocity = adjustedFlapForce;
        }
    }

    /**
     * Makes the bird dive.
     *
     * @param sensitivity The sensitivity level affecting dive force.
     */
    public void dive(int sensitivity) {
        float adjustedDiveForce = -flapForce * (sensitivity / 5.0f);
        velocity = adjustedDiveForce;
    }

    /**
     * Shrinks the bird for a power-up.
     */
    public void shrink() {
        if (!isShrunk) {
            isShrunk = true;
            width /= 2;
            height /= 2;
            x += width / 2; // Adjust position to center
            y += height / 2;

            for (int i = 0; i < birdFrames.length; i++) {
                birdFrames[i] = Bitmap.createScaledBitmap(birdFrames[i], width, height, false);
            }
        }
    }

    /**
     * Resets the bird's size after a shrink power-up ends.
     */
    public void resetSize() {
        if (isShrunk) {
            x -= width / 2; // Adjust position back
            y -= height / 2;
            width = originalWidth;
            height = originalHeight;

            // Reload original frames based on the current bird
            List<Integer> currentBirdFrames = BirdManager.getCurrentBirdFrames();
            for (int i = 0; i < birdFrames.length; i++) {
                Bitmap originalFrame = ResourceManager.getInstance(AppConstants.gameActivityContext)
                        .getBitmap(currentBirdFrames.get(i));
                if (originalFrame != null) {
                    // Scale back to original size
                    birdFrames[i] = Bitmap.createScaledBitmap(originalFrame, originalWidth, originalHeight, false);
                }
            }
            isShrunk = false;
        }
    }

    /**
     * Gets the collision rectangle for collision detection.
     *
     * @return The collision Rect.
     */
    public Rect getCollisionShape() {
        return collisionRect;
    }

    /**
     * Sets the invincibility state of the bird.
     *
     * @param invincible True to make the bird invincible.
     */
    public void setInvincible(boolean invincible) {
        isInvincible = invincible;
    }

    /**
     * Checks if the bird is invincible.
     *
     * @return True if invincible, false otherwise.
     */
    public boolean isInvincible() {
        return isInvincible;
    }

    /**
     * Sets the magnet power-up state.
     *
     * @param magnetActive True to activate magnet.
     */
    public void setMagnetActive(boolean magnetActive) {
        isMagnetActive = magnetActive;
    }

    /**
     * Checks if the magnet power-up is active.
     *
     * @return True if active, false otherwise.
     */
    public boolean isMagnetActive() {
        return isMagnetActive;
    }

    // Getters for position and dimensions
    public int getX() {
        return (int) x;
    }

    public int getY() {
        return (int) y;
    }

    public int getHeight() {
        return height;
    }

    public int getWidth() {
        return width;
    }
}
package com.example.flappybirdgame;

import android.content.Context;
import android.graphics.Bitmap;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

/**
 * Adapter class for displaying bird previews in a GridView.
 */
public class BirdAdapter extends BaseAdapter {
    private Context context;
    private ArrayList<Integer> birdPreviews;

    public BirdAdapter(Context context, ArrayList<Integer> birdPreviews) {
        this.context = context;
        this.birdPreviews = birdPreviews;
    }

    @Override
    public int getCount() {
        return birdPreviews.size();
    }

    @Override
    public Object getItem(int position) {
        return birdPreviews.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    /**
     * Creates a view for each bird preview, scaling images appropriately.
     *
     * @param position    Position of the item within the adapter's data set.
     * @param convertView The old view to reuse, if possible.
     * @param parent      The parent that this view will eventually be attached to.
     * @return A View corresponding to the data at the specified position.
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView birdImageView;
        if (convertView == null) {
            birdImageView = new ImageView(context);
            // Adjust size based on screen width (e.g., 20% of screen width)
            int size = (int) (AppConstants.SCREEN_WIDTH * 0.2f);
            birdImageView.setLayoutParams(new GridView.LayoutParams(size, size));
            birdImageView.setScaleType(ImageView.ScaleType.FIT_CENTER);
            birdImageView.setPadding(8, 8, 8, 8);
        } else {
            birdImageView = (ImageView) convertView;
        }

        // Load and set the bitmap
        Bitmap birdBitmap = ResourceManager.getInstance(context).getBitmap(birdPreviews.get(position));
        if (birdBitmap != null) {
            birdImageView.setImageBitmap(birdBitmap);
        } else {
            Log.e("BirdAdapter", "Bird bitmap at position " + position + " is null.");
            birdImageView.setImageResource(android.R.drawable.sym_def_app_icon); // Fallback image
        }

        return birdImageView;
    }
}
package com.example.flappybirdgame;

import android.content.Context;
import android.content.SharedPreferences;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class BirdManager {
    private static ArrayList<List<Integer>> availableBirds = new ArrayList<>();
    private static int currentBirdIndex = 0;

    /**
     * Initializes the BirdManager by loading available birds and their frames.
     *
     * @param context The application context.
     */
    public static void initialize(Context context) {
        SharedPreferences prefs = context.getSharedPreferences("game", Context.MODE_PRIVATE);

        // Load available birds with their respective frames
        // Each bird is represented by a list of its frame resource IDs

        // Default Bird Frames
        List<Integer> defaultBirdFrames = Arrays.asList(
                R.drawable.bird_frame_1,
                R.drawable.bird_frame_2,
                R.drawable.bird_frame_3
        );
        availableBirds.add(defaultBirdFrames); // Default bird

        // Cool Bird Frames (unlocked via achievements)
        if (prefs.getBoolean("bird_unlocked_cool_bird", false)) {
            List<Integer> coolBirdFrames = Arrays.asList(
                    R.drawable.bird_cool_frame_1,
                    R.drawable.bird_cool_frame_2,
                    R.drawable.bird_cool_frame_3
            );
            availableBirds.add(coolBirdFrames);
        }

        // Load current bird index
        currentBirdIndex = prefs.getInt("currentBirdIndex", 0);
    }

    /**
     * Retrieves the current bird's animation frames.
     *
     * @return A list of resource IDs for the current bird's frames.
     */
    public static List<Integer> getCurrentBirdFrames() {
        if (availableBirds.isEmpty()) {
            // Fallback to default bird if no birds are available
            return Arrays.asList(
                    R.drawable.bird_frame_1,
                    R.drawable.bird_frame_2,
                    R.drawable.bird_frame_3
            );
        }
        return availableBirds.get(currentBirdIndex);
    }

    /**
     * Sets the current bird based on the selected index.
     *
     * @param index   The index of the bird to select.
     * @param context The application context.
     */
    public static void setCurrentBird(int index, Context context) {
        if (index >= 0 && index < availableBirds.size()) {
            currentBirdIndex = index;
            SharedPreferences prefs = context.getSharedPreferences("game", Context.MODE_PRIVATE);
            SharedPreferences.Editor editor = prefs.edit();
            editor.putInt("currentBirdIndex", index);
            editor.apply();
        }
    }

    /**
     * Unlocks a new bird by adding its frames to the available birds list.
     *
     * @param birdFrames The list of frame resource IDs for the new bird.
     * @param key        The preference key to mark the bird as unlocked.
     * @param context    The application context.
     */
    public static void unlockBird(List<Integer> birdFrames, String key, Context context) {
        if (!isBirdAlreadyAvailable(birdFrames)) {
            availableBirds.add(birdFrames);
            SharedPreferences prefs = context.getSharedPreferences("game", Context.MODE_PRIVATE);
            SharedPreferences.Editor editor = prefs.edit();
            editor.putBoolean(key, true);
            editor.apply();
        }
    }

    /**
     * Checks if a bird is unlocked based on the provided key.
     *
     * @param key     The preference key associated with the bird.
     * @param context The application context.
     * @return True if the bird is unlocked, false otherwise.
     */
    public static boolean isBirdUnlocked(String key, Context context) {
        SharedPreferences prefs = context.getSharedPreferences("game", Context.MODE_PRIVATE);
        return prefs.getBoolean(key, false);
    }

    /**
     * Retrieves the list of available birds.
     *
     * @return A list of all available birds, each represented by their frame resource IDs.
     */
    public static ArrayList<List<Integer>> getAvailableBirds() {
        return availableBirds;
    }

    /**
     * Helper method to check if a bird is already available.
     *
     * @param birdFrames The list of frame resource IDs for the bird.
     * @return True if the bird is already available, false otherwise.
     */
    private static boolean isBirdAlreadyAvailable(List<Integer> birdFrames) {
        for (List<Integer> availableBird : availableBirds) {
            if (availableBird.size() == birdFrames.size()) {
                boolean allMatch = true;
                for (int i = 0; i < birdFrames.size(); i++) {
                    if (!availableBird.get(i).equals(birdFrames.get(i))) {
                        allMatch = false;
                        break;
                    }
                }
                if (allMatch) return true;
            }
        }
        return false;
    }
}
package com.example.flappybirdgame;

import android.graphics.Canvas;
import android.util.Log;

/**
 * Represents a boss pipe in the game with vertical movement.
 */
public class BossPipe extends Pipe {
    private int verticalSpeed = 4; // Speed at which the boss pipe moves vertically
    private boolean movingDown = true; // Direction flag for vertical movement

    /**
     * Constructor for BossPipe.
     *
     * @param screenHeight The height of the screen.
     * @param gapSize      The gap size between the top and bottom pipes.
     */
    public BossPipe(int screenHeight, int gapSize) {
        super(screenHeight, gapSize); // Utilize the Pipe constructor
    }

    /**
     * Overrides the update method to include vertical movement.
     */
    @Override
    public void update() {
        super.update(); // Move horizontally

        // Vertical movement logic
        if (movingDown) {
            y += verticalSpeed;
            // Check lower boundary
            if (y + pipeTop.getHeight() + gap + pipeBottom.getHeight() >= AppConstants.GROUND_Y - 50) {
                movingDown = false; // Reverse direction
            }
        } else {
            y -= verticalSpeed;
            // Check upper boundary
            if (y <= -pipeTop.getHeight() + 50) {
                movingDown = true; // Reverse direction
            }
        }

        // Reset 'scored' status when off-screen
        if (isOffScreen()) {
            resetPosition(AppConstants.SCREEN_WIDTH, gap);
        }
    }

    /**
     * Draws the boss pipe onto the canvas.
     *
     * @param canvas The Canvas object where the pipe will be drawn.
     */
    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        // Additional boss-specific visuals can be added here
    }
}
package com.example.flappybirdgame;

import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

public class CharacterSelectionActivity extends AppCompatActivity {

    private GridView birdGridView;
    private BirdAdapter birdAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_character_selection);

        birdGridView = findViewById(R.id.birdGridView);
        BirdManager.initialize(this); // Ensure BirdManager is initialized

        birdAdapter = new BirdAdapter(this, getBirdPreviews());
        birdGridView.setAdapter(birdAdapter);

        birdGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, android.view.View view, int position, long id) {
                BirdManager.setCurrentBird(position, CharacterSelectionActivity.this);
                Toast.makeText(CharacterSelectionActivity.this, getString(R.string.bird_selected), Toast.LENGTH_SHORT).show();
                finish();
            }
        });
    }

    /**
     * Extracts the first frame of each bird as a preview image.
     *
     * @return A list of resource IDs representing each bird's preview image.
     */
    private ArrayList<Integer> getBirdPreviews(){
        ArrayList<Integer> previews = new ArrayList<>();
        List<List<Integer>> availableBirds = BirdManager.getAvailableBirds();

        for(List<Integer> birdFrames : availableBirds){
            if(!birdFrames.isEmpty()){
                previews.add(birdFrames.get(0)); // Use the first frame as the preview
            }
        }
        return previews;
    }

    @Override
    protected void onResume() {
        super.onResume();
        // Refresh bird previews in case new birds were unlocked
        birdAdapter = new BirdAdapter(this, getBirdPreviews());
        birdGridView.setAdapter(birdAdapter);
    }
}
package com.example.flappybirdgame;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Log;

import java.util.Random;

/**
 * Represents a collectible item (e.g., coin) in the game, handling its position and rendering.
 */
public class Collectible {
    private int x, y, width, height;
    private Bitmap bitmap;
    private Rect collisionRect;
    private int speed;
    private int originalWidth, originalHeight;
    private Random random = new Random();

    /**
     * Constructor for Collectible.
     * Initializes and scales the collectible bitmap.
     */
    public Collectible() {
        Bitmap original = ResourceManager.getInstance(AppConstants.gameActivityContext)
                .getBitmap(R.drawable.coin);

        if (original != null) {
            // Scale bitmap to 5% of screen width
            int desiredWidth = (int) (AppConstants.SCREEN_WIDTH * 0.05f);
            bitmap = Bitmap.createScaledBitmap(original, desiredWidth,
                    original.getHeight() * desiredWidth / original.getWidth(), false);

            width = bitmap.getWidth();
            height = bitmap.getHeight();
            originalWidth = width;
            originalHeight = height;

            x = AppConstants.SCREEN_WIDTH;
            y = getRandomYPosition();
            collisionRect = new Rect(x, y, x + width, y + height);
        } else {
            Log.e("Collectible", "Coin bitmap is null. Using default size.");
            width = 30;
            height = 30;
            bitmap = null;
            x = AppConstants.SCREEN_WIDTH;
            y = getRandomYPosition();
            collisionRect = new Rect(x, y, x + width, y + height);
        }
    }

    /**
     * Generates a random Y position within screen bounds for the collectible.
     *
     * @return A random Y position.
     */
    private int getRandomYPosition() {
        int minY = 0;
        int maxY = AppConstants.GROUND_Y - height;
        return minY + random.nextInt(Math.max(maxY - minY, 1));
    }

    /**
     * Updates the collectible's position.
     */
    public void update() {
        speed = AppConstants.PIPE_SPEED;
        x -= speed;
        collisionRect.set(x, y, x + width, y + height);
    }

    /**
     * Draws the collectible onto the canvas.
     *
     * @param canvas The Canvas object where the collectible will be drawn.
     */
    public void draw(Canvas canvas) {
        if (bitmap != null) {
            canvas.drawBitmap(bitmap, x, y, null);
        }
    }

    /**
     * Retrieves the collision rectangle for collision detection.
     *
     * @return The collision Rect.
     */
    public Rect getCollisionRect() {
        return collisionRect;
    }

    /**
     * Checks if the collectible is off-screen.
     *
     * @return True if off-screen, false otherwise.
     */
    public boolean isOffScreen() {
        return x + width < 0;
    }

    // Getters and Setters for Position
    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(int x) {
        this.x = x;
        collisionRect.left = x;
        collisionRect.right = x + width;
    }

    public void setY(int y) {
        this.y = y;
        collisionRect.top = y;
        collisionRect.bottom = y + height;
    }

    /**
     * Retrieves the bitmap associated with this collectible.
     *
     * @return The bitmap, or null if not available.
     */
    public Bitmap getBitmap() {
        return bitmap;
    }
}
package com.example.flappybirdgame;

import android.os.Bundle;
import android.view.WindowManager;

import androidx.appcompat.app.AppCompatActivity;

public class GameActivity extends AppCompatActivity {

    private GameView gameView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Make the game full-screen
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // Initialize AppConstants
        AppConstants.initialization(this.getApplicationContext());

        // Initialize GameView and set it as the content view
        gameView = new GameView(this);
        setContentView(gameView);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (gameView != null) {
            gameView.pause();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (gameView != null) {
            gameView.resumeGame();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (AppConstants.soundPool != null) {
            AppConstants.soundPool.release();
            AppConstants.soundPool = null;
        }
        if (gameView != null) {
            gameView.cleanup();
            gameView = null;
        }
    }
}
package com.example.flappybirdgame;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

/**
 * The main game view where all game elements are drawn and updated.
 */
public class GameView extends SurfaceView implements Runnable, SurfaceHolder.Callback {

    private Thread thread;
    private volatile boolean isPlaying; // Use volatile for visibility across threads
    private Background background1, background2;
    private Bird bird;
    private PipeManager pipeManager;
    private int screenX, screenY;
    private Paint paint;
    private GameActivity activity;
    private boolean isGameOver = false;
    private int score = 0;
    private Handler mainHandler = new Handler(Looper.getMainLooper());
    private Runnable restartRunnable;
    private Ground ground1, ground2;
    private int nextDifficultyScore = 10; // Increase difficulty every 10 points
    private boolean isPaused = false;
    private Rect pauseButtonRect;
    private Bitmap pauseButtonBitmap; // Ensure you have this drawable
    private ArrayList<PowerUp> powerUps = new ArrayList<>();
    private ArrayList<Collectible> collectibles = new ArrayList<>();
    private int totalCoinsCollected = 0;
    private Random random = new Random();
    private LevelManager levelManager;
    private int flapCount = 0;
    private AchievementManager achievementManager;

    // Synchronization object
    private final Object pauseLock = new Object();

    /**
     * Constructor for GameView.
     *
     * @param activity The GameActivity instance.
     */
    public GameView(GameActivity activity) {
        super(activity);
        this.activity = activity;

        // Initialize settings context
        AppConstants.gameActivityContext = activity.getApplicationContext();

        // Initialize AppConstants
        AppConstants.initialization(activity.getApplicationContext());

        // Initialize ResourceManager
        ResourceManager.getInstance(AppConstants.gameActivityContext);

        // Initialize game variables, bitmaps, etc.
        screenX = AppConstants.SCREEN_WIDTH;
        screenY = AppConstants.SCREEN_HEIGHT;

        // Add SurfaceHolder.Callback
        getHolder().addCallback(this);

        // Initialize objects
        background1 = new Background();
        background2 = new Background();
        background2.x = screenX;

        ground1 = new Ground();
        ground2 = new Ground();
        ground2.x = screenX;

        bird = new Bird(screenY);
        levelManager = new LevelManager();
        pipeManager = new PipeManager(levelManager);

        paint = new Paint();
        paint.setTextSize(64);
        paint.setColor(Color.WHITE);
        paint.setAntiAlias(true);

        // Initialize pause button rectangle and bitmap
        int btnSize = (int) (AppConstants.SCREEN_WIDTH * 0.1f);
        int btnX = AppConstants.SCREEN_WIDTH - btnSize - (int)(AppConstants.SCREEN_WIDTH * 0.02f); // 2% margin
        int btnY = (int) (AppConstants.SCREEN_HEIGHT * 0.02f); // 2% margin
        pauseButtonRect = new Rect(btnX, btnY, btnX + btnSize, btnY + btnSize);

        // Load pause button image with null check
        Bitmap pauseBitmap = ResourceManager.getInstance(AppConstants.gameActivityContext).getBitmap(R.drawable.pause_button_image);
        if (pauseBitmap != null) {
            pauseButtonBitmap = Bitmap.createScaledBitmap(pauseBitmap, btnSize, btnSize, false);
        } else {
            // Handle missing bitmap: use a default image or log an error
            Log.e("GameView", "Pause button bitmap is null. Using default.");
            pauseButtonBitmap = BitmapFactory.decodeResource(getResources(), android.R.drawable.ic_media_pause); // Default pause icon
            if (pauseButtonBitmap != null) {
                pauseButtonBitmap = Bitmap.createScaledBitmap(pauseButtonBitmap, btnSize, btnSize, false);
            } else {
                Log.e("GameView", "Default pause button bitmap is also null. Cannot render pause button.");
            }
        }

        applyLevelSettings();

        // Initialize achievement manager
        achievementManager = new AchievementManager(activity);
    }

    /**
     * Called when the surface is created. Starts the game loop.
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isPlaying = true;
        thread = new Thread(this);
        thread.start();
    }

    /**
     * Called when the surface changes. Not used here.
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        // Not needed
    }

    /**
     * Called when the surface is destroyed. Stops the game loop.
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        pause(); // Ensure the game is paused when the surface is destroyed
    }

    /**
     * The main game loop.
     */
    @Override
    public void run() {
        while (isPlaying) {
            if (!isPaused) {
                update();
            }
            drawCanvas();
            sleep();
        }
    }

    /**
     * Applies settings based on the current level.
     */
    private void applyLevelSettings() {
        Level currentLevel = levelManager.getCurrentLevel();
        AppConstants.PIPE_SPEED = currentLevel.getPipeSpeed();
        pipeManager.setGapSize(currentLevel.getGapSize()); // Pass the gap size directly from the current level
    }

    /**
     * Updates all game elements.
     */
    private void update() {
        // Update ground positions
        ground1.update();
        ground2.update();

        // Update background positions
        background1.update();
        background2.update();

        // Update bird
        bird.update();

        // Update pipes
        pipeManager.update();

        // Check for collisions
        if (pipeManager.checkCollision(bird) || bird.getY() <= 0 || bird.getY() + bird.getHeight() >= AppConstants.GROUND_Y) {
            if (!bird.isInvincible()) {
                isPlaying = false;
                isGameOver = true;
                if (AppConstants.SOUND_ENABLED) {
                    AppConstants.soundPool.play(AppConstants.soundHit, 1, 1, 0, 0, 1);
                }
                achievementManager.checkAchievements(flapCount, score); // Update achievements on game over
            }
        }

        // Increase score if bird passes a pipe
        if (pipeManager.checkForScore(bird)) {
            score++;
            checkForLevelAdvance();
            checkForBirdUnlock();
            achievementManager.checkAchievements(flapCount, score);
            if (AppConstants.SOUND_ENABLED) {
                AppConstants.soundPool.play(AppConstants.soundPoint, 1, 1, 0, 0, 1);
            }
        }

        // Check for increasing difficulty
        if (score >= nextDifficultyScore) {
            nextDifficultyScore += 10; // Set next score milestone

            // Increase pipe speed
            AppConstants.PIPE_SPEED += 1; // Adjusted to 1

            // Decrease gap between pipes
            pipeManager.decreaseGap(20); // Reduce gap by 20 pixels

            // Optionally increase background speed
            AppConstants.BACKGROUND_SPEED += 0.5f; // Use float for smoother speed adjustments

            // Apply new gaps and speeds to existing pipes
            pipeManager.setGapSize(pipeManager.getCurrentGapSize()); // Now available with getter
        }

        // Spawn power-ups at random intervals
        if (random.nextInt(1000) < 5) { // Adjust the probability as needed
            spawnPowerUp();
        }

        // Update power-ups
        Iterator<PowerUp> powerUpIterator = powerUps.iterator();
        while (powerUpIterator.hasNext()) {
            PowerUp powerUp = powerUpIterator.next();
            powerUp.update();

            // Remove if off-screen
            if (powerUp.isOffScreen()) {
                powerUpIterator.remove();
                continue;
            }

            // Check collision with bird
            if (bird.getCollisionShape().intersect(powerUp.getCollisionRect())) {
                activatePowerUp(powerUp.getType());
                powerUpIterator.remove();
            }
        }

        // Spawn collectibles at random intervals
        if (random.nextInt(1000) < 5) {
            collectibles.add(new Collectible());
        }

        // Update collectibles
        Iterator<Collectible> collectibleIterator = collectibles.iterator();
        while (collectibleIterator.hasNext()) {
            Collectible collectible = collectibleIterator.next();
            collectible.update();

            // Magnet effect
            if (bird.isMagnetActive()) {
                int deltaX = bird.getX() - collectible.getX();
                int deltaY = bird.getY() - collectible.getY();
                double distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
                if (distance < 300 * AppConstants.SCREEN_RATIO_X) {
                    collectible.setX(collectible.getX() + (int) (deltaX * 0.1));
                    collectible.setY(collectible.getY() + (int) (deltaY * 0.1));
                }
            }

            // Check if collectible is off-screen
            if (collectible.isOffScreen()) {
                collectibleIterator.remove();
                continue;
            }

            // Check collision with bird
            if (bird.getCollisionShape().intersect(collectible.getCollisionRect())) {
                totalCoinsCollected++;
                collectibleIterator.remove();
                // Optionally play a sound or update UI
                if (AppConstants.SOUND_ENABLED) {
                    AppConstants.soundPool.play(AppConstants.soundPoint, 1, 1, 0, 0, 1);
                }
            }
        }
    }

    /**
     * Draws all game elements onto the canvas.
     */
    private void drawCanvas() {
        if (getHolder().getSurface().isValid()) {

            Canvas canvas = getHolder().lockCanvas();

            // Clear the canvas with a color (e.g., black) if no background
            canvas.drawColor(Color.BLACK);

            // Draw background
            if (background1 != null) {
                background1.draw(canvas);
            }
            if (background2 != null) {
                background2.draw(canvas);
            }

            // Draw pipes
            if (pipeManager != null) {
                pipeManager.draw(canvas);
            } else {
                Log.e("GameView", "pipeManager is null. Cannot draw pipes.");
            }

            // Draw ground
            if (ground1 != null && ground1.getBitmap() != null) {
                ground1.draw(canvas);
            }
            if (ground2 != null && ground2.getBitmap() != null) {
                ground2.draw(canvas);
            }

            // Draw bird
            if (bird != null && bird.getCurrentFrame() != null) {
                canvas.drawBitmap(bird.getCurrentFrame(), bird.getX(), bird.getY(), null);
            } else {
                Log.e("GameView", "Bird or bird frame is null. Cannot draw bird.");
            }

            // Draw power-ups
            for (PowerUp powerUp : powerUps) {
                if (powerUp.getBitmap() != null) {
                    powerUp.draw(canvas);
                }
            }

            // Draw collectibles
            for (Collectible collectible : collectibles) {
                if (collectible.getBitmap() != null) {
                    collectible.draw(canvas);
                }
            }

            // Draw score and coins
            paint.setTextSize(64);
            paint.setColor(Color.WHITE);
            canvas.drawText(activity.getString(R.string.score, score), 50, 100, paint);
            canvas.drawText(activity.getString(R.string.coins, totalCoinsCollected), 50, 170, paint);

            // Draw Pause Button
            if (pauseButtonBitmap != null) {
                canvas.drawBitmap(pauseButtonBitmap, pauseButtonRect.left, pauseButtonRect.top, null);
            } else {
                Log.e("GameView", "pauseButtonBitmap is null. Cannot draw pause button.");
            }

            if (isGameOver) {
                // Save high score
                saveHighScore();

                // Draw Game Over text
                paint.setTextSize(150);
                paint.setColor(Color.RED);
                paint.setTextAlign(Paint.Align.CENTER);
                canvas.drawText(activity.getString(R.string.game_over), screenX / 2f, screenY / 2f, paint);

                // Reset paint settings
                paint.setTextAlign(Paint.Align.LEFT);
                paint.setTextSize(64);
                paint.setColor(Color.WHITE);

                // Ensure we only schedule the restart once
                if (restartRunnable == null) {
                    // Restart game after a delay
                    restartRunnable = () -> {
                        activity.startActivity(new Intent(activity, MainActivity.class));
                        activity.finish();
                    };
                    mainHandler.postDelayed(restartRunnable, 2000); // Delay of 2000 milliseconds (2 seconds)
                }
            }

            getHolder().unlockCanvasAndPost(canvas);
        }
    }

    /**
     * Saves the high score if the current score exceeds it.
     */
    private void saveHighScore() {
        SharedPreferences prefs = activity.getSharedPreferences("game", Context.MODE_PRIVATE);
        int highScore = prefs.getInt("highScore", 0);
        if (score > highScore) {
            SharedPreferences.Editor editor = prefs.edit();
            editor.putInt("highScore", score);
            editor.apply();
        }
    }

    /**
     * Pauses the game loop thread.
     */
    public void pause() {
        synchronized (pauseLock) {
            isPlaying = false;
            isPaused = true;
            try {
                if (thread != null) {
                    thread.join();
                    thread = null;
                }
            } catch (InterruptedException e) {
                Log.e("GameView", "Error while pausing thread", e);
            }
        }
    }

    /**
     * Resumes the game loop thread.
     */
    public void resumeGame() { // Renamed to avoid confusion with Activity's onResume()
        synchronized (pauseLock) {
            if (!isPlaying) {
                isPlaying = true;
                isPaused = false;
                thread = new Thread(this);
                thread.start();
            }
        }
    }

    /**
     * Sleeps the thread to control the frame rate.
     */
    private void sleep() {
        try {
            Thread.sleep(17); // Approximately 60 FPS
        } catch (InterruptedException e) {
            Log.e("GameView", "Thread sleep interrupted", e);
        }
    }

    /**
     * Handles touch events for flapping and pausing the game.
     *
     * @param event The MotionEvent object.
     * @return True if the event was handled.
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {

            float touchX = event.getX();
            float touchY = event.getY();

            if (!isGameOver) {
                // Check if pause button is tapped
                if (pauseButtonRect.contains((int) touchX, (int) touchY)) {
                    if (isPaused) {
                        // Resume the game
                        resumeGame();
                        mainHandler.post(() -> Toast.makeText(activity, "Game Resumed", Toast.LENGTH_SHORT).show());
                    } else {
                        // Pause the game
                        pause();
                        mainHandler.post(() -> Toast.makeText(activity, "Game Paused", Toast.LENGTH_SHORT).show());
                    }
                } else {
                    // Handle flap based on inverted controls
                    bird.flap(AppConstants.SENSITIVITY);
                    flapCount++;
                    achievementManager.checkAchievements(flapCount, score);

                    if (AppConstants.SOUND_ENABLED) {
                        AppConstants.soundPool.play(AppConstants.soundWing, 1, 1, 0, 0, 1);
                    }
                }
            }

            // Handle pauses during Game Over
            if (isGameOver) {
                // Optionally, restart the game immediately upon touch
                if (restartRunnable == null) {
                    restartRunnable = () -> {
                        activity.startActivity(new Intent(activity, MainActivity.class));
                        activity.finish();
                    };
                    mainHandler.postDelayed(restartRunnable, 500); // Shorter delay
                }
            }
        }
        return true;
    }

    /**
     * Checks if the game should advance to the next level based on the score.
     */
    private void checkForLevelAdvance() {
        Level currentLevel = levelManager.getCurrentLevel();
        if (score >= currentLevel.getTargetScore()) {
            levelManager.advanceToNextLevel();
            applyLevelSettings();
            pipeManager.onLevelChange(); // Notify PipeManager of level change

            mainHandler.post(() -> {
                String message;
                if (levelManager.isLastLevel()) {
                    message = activity.getString(R.string.boss_level);
                } else {
                    // Assuming @string/level is defined as "Level %1$d!"
                    message = String.format(activity.getString(R.string.level), levelManager.getCurrentLevel().getLevelNumber());
                }
                Toast.makeText(activity, message, Toast.LENGTH_SHORT).show();
            });
        }
    }

    /**
     * Spawns a random power-up.
     */
    private void spawnPowerUp() {
        PowerUpType[] types = PowerUpType.values();
        PowerUpType randomType = types[random.nextInt(types.length)];
        PowerUp powerUp = new PowerUp(randomType);
        powerUps.add(powerUp);
    }

    /**
     * Activates a specific power-up type.
     *
     * @param type The type of power-up to activate.
     */
    private void activatePowerUp(PowerUpType type) {
        switch (type) {
            case INVINCIBILITY:
                bird.setInvincible(true);
                mainHandler.post(() -> Toast.makeText(activity, "Invincibility Activated!", Toast.LENGTH_SHORT).show());
                mainHandler.postDelayed(() -> bird.setInvincible(false), 5000); // Active for 5 seconds
                break;
            case SLOW_MOTION:
                AppConstants.PIPE_SPEED = Math.max(AppConstants.PIPE_SPEED / 2, 1); // Prevent PIPE_SPEED from becoming 0
                AppConstants.BACKGROUND_SPEED = Math.max(AppConstants.BACKGROUND_SPEED / 2, 1);
                mainHandler.post(() -> Toast.makeText(activity, "Slow Motion Activated!", Toast.LENGTH_SHORT).show());
                mainHandler.postDelayed(() -> {
                    AppConstants.PIPE_SPEED *= 2;
                    AppConstants.BACKGROUND_SPEED *= 2;
                }, 5000); // Revert after 5 seconds
                break;
            case SHRINK:
                bird.shrink();
                mainHandler.post(() -> Toast.makeText(activity, "Shrunk!", Toast.LENGTH_SHORT).show());
                mainHandler.postDelayed(() -> bird.resetSize(), 5000); // Revert after 5 seconds
                break;
            case MAGNET:
                bird.setMagnetActive(true);
                mainHandler.post(() -> Toast.makeText(activity, "Magnet Activated!", Toast.LENGTH_SHORT).show());
                mainHandler.postDelayed(() -> bird.setMagnetActive(false), 5000); // Active for 5 seconds
                break;
        }
    }

    /**
     * Checks and unlocks new birds based on the score.
     */
    private void checkForBirdUnlock() {
        // Example: Unlock a new bird when score reaches 50
        if (score >= 50 && !BirdManager.isBirdUnlocked("bird_unlocked_cool_bird", activity)) {
            List<Integer> coolBirdFrames = List.of(
                    R.drawable.bird_cool_frame_1,
                    R.drawable.bird_cool_frame_2,
                    R.drawable.bird_cool_frame_3
            );
            BirdManager.unlockBird(coolBirdFrames, "bird_unlocked_cool_bird", activity);
            mainHandler.post(() -> Toast.makeText(activity, activity.getString(R.string.new_bird_unlocked), Toast.LENGTH_SHORT).show());
        }
    }

    /**
     * Cleanup resources to prevent memory leaks.
     */
    public void cleanup() {
        // Recycle pause button bitmap
        if (pauseButtonBitmap != null && !pauseButtonBitmap.isRecycled()) {
            pauseButtonBitmap.recycle();
            pauseButtonBitmap = null;
        }

        // Recycle all bitmaps in ResourceManager
        ResourceManager.getInstance(AppConstants.gameActivityContext).recycleBitmaps();
    }
}
package com.example.flappybirdgame;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.Log;

/**
 * Represents the ground in the game, handling its position and rendering.
 */
public class Ground {
    public int x = 0, y;
    private Bitmap ground;

    /**
     * Constructor for Ground.
     * Initializes and scales the ground bitmap to match screen width and 20% height.
     */
    public Ground() {
        Bitmap original = ResourceManager.getInstance(AppConstants.gameActivityContext)
                .getBitmap(R.drawable.ground);

        if (original == null) {
            Log.e("Ground", "Ground bitmap is null. Using default ground image.");
            original = BitmapFactory.decodeResource(AppConstants.gameActivityContext.getResources(),
                    android.R.drawable.ic_menu_report_image);
            if (original != null) {
                ground = Bitmap.createScaledBitmap(original,
                        AppConstants.SCREEN_WIDTH, (int) (AppConstants.SCREEN_HEIGHT * 0.2f), false);
            } else {
                Log.e("Ground", "Default ground bitmap is also null. Rendering will fail.");
            }
        } else {
            // Scale the ground bitmap to match screen width and 20% of screen height
            ground = Bitmap.createScaledBitmap(original,
                    AppConstants.SCREEN_WIDTH, (int) (AppConstants.SCREEN_HEIGHT * 0.2f), false);
        }

        if (ground != null) {
            y = AppConstants.SCREEN_HEIGHT - ground.getHeight();
            AppConstants.GROUND_Y = y; // Initialize GROUND_Y
        } else {
            y = AppConstants.SCREEN_HEIGHT; // Position at the bottom as a safe fallback
            AppConstants.GROUND_Y = y;
        }
    }

    /**
     * Updates the ground's horizontal position.
     */
    public void update() {
        if (ground != null) {
            x -= AppConstants.BACKGROUND_SPEED * 2; // Ground moves faster than background
            if (x + ground.getWidth() < 0) {
                x = ground.getWidth();
            }
        }
    }

    /**
     * Draws the ground bitmap onto the canvas.
     *
     * @param canvas The Canvas object where the ground will be drawn.
     */
    public void draw(Canvas canvas) {
        if (ground != null) {
            canvas.drawBitmap(ground, x, y, null);
        } else {
            // Fallback: Fill with green if ground bitmap is unavailable
            canvas.drawColor(Color.GREEN);
        }
    }

    /**
     * Retrieves the ground bitmap.
     *
     * @return The ground bitmap, or null if not available.
     */
    public Bitmap getBitmap() {
        return ground;
    }
}
package com.example.flappybirdgame;

import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;

import androidx.appcompat.app.AppCompatActivity;

public class HelpActivity extends AppCompatActivity {

    private ImageButton backButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_help);

        // Initialize back button
        backButton = findViewById(R.id.backButton);

        // Set up back button click listener
        backButton.setOnClickListener(view -> finish());
    }
}
package com.example.flappybirdgame;

public class Level {
    private int levelNumber;
    private int pipeSpeed;
    private int gapSize;
    private int targetScore;

    public Level(int levelNumber, int pipeSpeed, int gapSize, int targetScore) {
        this.levelNumber = levelNumber;
        this.pipeSpeed = pipeSpeed;
        this.gapSize = gapSize;
        this.targetScore = targetScore;
    }

    // Getters
    public int getLevelNumber() { return levelNumber; }
    public int getPipeSpeed() { return pipeSpeed; }
    public int getGapSize() { return gapSize; }
    public int getTargetScore() { return targetScore; }
}
package com.example.flappybirdgame;
import java.util.ArrayList;

public class LevelManager {
    private ArrayList<Level> levels = new ArrayList<>();
    private int currentLevelIndex = 0;

    public LevelManager() {
        // Define levels
        levels.add(new Level(1, 10, 500, 10));
        levels.add(new Level(2, 12, 450, 20));
        levels.add(new Level(3, 14, 400, 30));
        levels.add(new Level(4, 16, 350, 40));

        // Boss level
        levels.add(new Level(5, 18, 300, 50));
    }

    public Level getCurrentLevel() {
        return levels.get(currentLevelIndex);
    }

    public void advanceToNextLevel() {
        if (currentLevelIndex < levels.size() - 1) {
            currentLevelIndex++;
        }
    }

    public boolean isLastLevel() {
        return currentLevelIndex == levels.size() - 1;
    }
}
package com.example.flappybirdgame;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.util.Calendar;

public class MainActivity extends AppCompatActivity {

    private Button playButton;
    private TextView highScoreText;
    private Button settingsButton, helpButton, achievementsButton, characterSelectionButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); // Set the layout resource

        checkDailyReward();

        // Initialize UI elements using findViewById
        playButton = findViewById(R.id.playButton);
        highScoreText = findViewById(R.id.highScoreText);
        settingsButton = findViewById(R.id.settingsButton);
        helpButton = findViewById(R.id.helpButton);
        achievementsButton = findViewById(R.id.achievementsButton);
        characterSelectionButton = findViewById(R.id.characterSelectionButton);

        // Display high score
        SharedPreferences prefs = getSharedPreferences("game", MODE_PRIVATE);
        int highScore = prefs.getInt("highScore", 0);
        highScoreText.setText(getString(R.string.high_score, highScore));

        // Set up the Play button click listener
        playButton.setOnClickListener(view -> {
            Intent intent = new Intent(MainActivity.this, GameActivity.class);
            startActivity(intent);
            // No need to call finish() to allow the user to return to main menu
        });

        // Set up the Settings button click listener
        settingsButton.setOnClickListener(view -> {
            Intent intent = new Intent(MainActivity.this, SettingsActivity.class);
            startActivity(intent);
        });

        // Set up the Help button click listener
        helpButton.setOnClickListener(view -> {
            Intent intent = new Intent(MainActivity.this, HelpActivity.class);
            startActivity(intent);
        });

        // Set up the Achievements button click listener
        achievementsButton.setOnClickListener(view -> {
            Intent intent = new Intent(MainActivity.this, AchievementsActivity.class);
            startActivity(intent);
        });

        // Set up the Character Selection button click listener
        characterSelectionButton.setOnClickListener(view -> {
            Intent intent = new Intent(MainActivity.this, CharacterSelectionActivity.class);
            startActivity(intent);
        });
    }

    private void checkDailyReward() {
        SharedPreferences prefs = getSharedPreferences("game", MODE_PRIVATE);
        long lastLoginTime = prefs.getLong("lastLoginTime", 0);
        int loginStreak = prefs.getInt("loginStreak", 0);
        long currentTime = System.currentTimeMillis();

        if (!isSameDay(lastLoginTime, currentTime)) {
            // Reset or update login streak
            loginStreak = (currentTime - lastLoginTime) <= 24 * 60 * 60 * 1000 ? loginStreak + 1 : 1;
            SharedPreferences.Editor editor = prefs.edit();
            editor.putInt("loginStreak", loginStreak);
            editor.putLong("lastLoginTime", currentTime);
            editor.apply();

            // Reward player (e.g., unlock themes or give other rewards)
            Toast.makeText(this, "Daily Reward! Login Streak: " + loginStreak, Toast.LENGTH_SHORT).show();
        }
    }

    private boolean isSameDay(long time1, long time2) {
        Calendar cal1 = Calendar.getInstance();
        Calendar cal2 = Calendar.getInstance();
        cal1.setTimeInMillis(time1);
        cal2.setTimeInMillis(time2);
        return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
                cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
    }
}
package com.example.flappybirdgame;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Log;

/**
 * Represents a standard pipe in the game, handling its position and rendering.
 */
public class Pipe {
    protected int x, y, width, height;
    protected Bitmap pipeTop, pipeBottom;
    protected int gap;
    protected int pipeSpeed;
    protected boolean scored = false;
    protected int minGap;

    /**
     * Constructor for Pipe.
     *
     * @param screenHeight The height of the screen.
     * @param gapSize      The gap size between the top and bottom pipes.
     */
    public Pipe(int screenHeight, int gapSize) {
        // Load pipe bitmaps
        Bitmap originalPipeTop = ResourceManager.getInstance(AppConstants.gameActivityContext)
                .getBitmap(R.drawable.pipe_top);
        Bitmap originalPipeBottom = ResourceManager.getInstance(AppConstants.gameActivityContext)
                .getBitmap(R.drawable.pipe_bottom);

        if (originalPipeTop != null && originalPipeBottom != null) {
            // Scale pipe bitmaps based on screen width (e.g., 8% of screen width)
            int desiredWidth = (int) (AppConstants.SCREEN_WIDTH * 0.08f);
            pipeTop = Bitmap.createScaledBitmap(originalPipeTop, desiredWidth,
                    originalPipeTop.getHeight() * desiredWidth / originalPipeTop.getWidth(), false);
            pipeBottom = Bitmap.createScaledBitmap(originalPipeBottom, desiredWidth,
                    originalPipeBottom.getHeight() * desiredWidth / originalPipeBottom.getWidth(), false);

            width = pipeTop.getWidth();
            height = pipeTop.getHeight();

            // Set gap size and minimum gap
            this.gap = gapSize;
            this.minGap = (int) (screenHeight * 0.15f); // 15% of screen height

            // Set pipe speed from AppConstants
            this.pipeSpeed = AppConstants.PIPE_SPEED;

            // Initialize position off-screen to the right
            x = AppConstants.SCREEN_WIDTH;
            y = getRandomYPosition(screenHeight);
        } else {
            Log.e("Pipe", "Pipe bitmaps are null.");
        }
    }

    /**
     * Generates a random Y position for the top pipe.
     *
     * @param screenHeight The height of the screen.
     * @return A random Y position.
     */
    protected int getRandomYPosition(int screenHeight) {
        int minY = (int) (screenHeight * 0.1f) - height;
        int maxY = AppConstants.GROUND_Y - gap - pipeBottom.getHeight() - (int) (screenHeight * 0.1f);

        if (maxY <= minY) {
            maxY = minY + 1;
        }

        return minY + (int) (Math.random() * (maxY - minY + 1));
    }

    /**
     * Sets the gap size between the top and bottom pipes.
     *
     * @param gapSize The new gap size in pixels.
     */
    public void setGapSize(int gapSize) {
        this.gap = gapSize;
        // Recalculate Y position based on new gap
        y = getRandomYPosition(AppConstants.SCREEN_HEIGHT);
    }

    /**
     * Decreases the gap size by a specified amount.
     *
     * @param amount The amount to decrease the gap size by.
     */
    public void decreaseGap(int amount) {
        setGapSize(this.gap - amount);
    }

    /**
     * Updates the pipe's position.
     */
    public void update() {
        x -= pipeSpeed;
    }

    /**
     * Draws the pipe onto the canvas.
     *
     * @param canvas The Canvas object where the pipe will be drawn.
     */
    public void draw(Canvas canvas) {
        if (pipeTop != null && pipeBottom != null) {
            canvas.drawBitmap(pipeTop, x, y, null);
            canvas.drawBitmap(pipeBottom, x, y + pipeTop.getHeight() + gap, null);
        } else {
            Log.e("Pipe", "Cannot draw pipe: bitmaps are null.");
        }
    }

    // Getters
    public int getX() {
        return x;
    }

    public int getWidth() {
        return width;
    }

    public Rect getTopPipeRect() {
        return new Rect(x, y, x + width, y + height);
    }

    public Rect getBottomPipeRect() {
        return new Rect(x, y + height + gap, x + width, y + height + gap + pipeBottom.getHeight());
    }

    public boolean isOffScreen() {
        return x + width < 0;
    }

    public boolean isScored() {
        return scored;
    }

    public void setScored(boolean scored) {
        this.scored = scored;
    }

    /**
     * Resets the pipe's position to the right side of the screen.
     *
     * @param newX         The new X position.
     * @param gapSize      The new gap size.
     */
    public void resetPosition(int newX, int gapSize) {
        this.x = newX;
        setGapSize(gapSize);
        this.scored = false;
    }
}
package com.example.flappybirdgame;

import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Log;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Manages the creation, updating, and rendering of pipes within the game.
 */
public class PipeManager {

    private final List<Pipe> pipes;          // List of pipes
    private final int pipeSpacing;           // Spacing between pipes in pixels
    private final int numberOfPipes;         // Number of initial pipes
    private boolean isBossLevel = false;     // Flag to indicate boss level
    private int currentGapSize;              // Current gap size between pipes in pixels

    private final Object lock = new Object(); // Synchronization lock
    private final LevelManager levelManager;  // Reference to LevelManager

    /**
     * Constructor for PipeManager.
     *
     * @param levelManager The LevelManager instance to manage level-related attributes.
     */
    public PipeManager(LevelManager levelManager) {
        this.pipes = new ArrayList<>();
        this.numberOfPipes = 2; // Default number of pipes displayed simultaneously
        this.pipeSpacing = AppConstants.SCREEN_WIDTH / 2; // Default spacing between pipes
        this.levelManager = levelManager;

        // Determine if it's a boss level based on the current level
        this.isBossLevel = levelManager.isLastLevel();

        // Initialize pipes with the current gap size
        this.currentGapSize = levelManager.getCurrentLevel().getGapSize();
        initializePipes();
    }

    /**
     * Initializes or resets the pipe list based on the current level.
     */
    private void initializePipes() {
        synchronized (lock) {
            pipes.clear();

            for (int i = 0; i < numberOfPipes; i++) {
                Pipe pipe;
                if (isBossLevel) {
                    pipe = new BossPipe(AppConstants.SCREEN_HEIGHT, currentGapSize);
                } else {
                    pipe = new Pipe(AppConstants.SCREEN_HEIGHT, currentGapSize);
                }
                pipe.resetPosition(AppConstants.SCREEN_WIDTH + i * pipeSpacing, currentGapSize);
                pipes.add(pipe);
            }
        }
    }

    /**
     * Updates the positions of all pipes and handles the creation of new pipes when old ones go off-screen.
     */
    public void update() {
        synchronized (lock) {
            List<Pipe> pipesToAdd = new ArrayList<>(); // Temporary list to hold new pipes

            Iterator<Pipe> iterator = pipes.iterator();
            while (iterator.hasNext()) {
                Pipe pipe = iterator.next();
                pipe.update();

                if (pipe.isOffScreen()) {
                    iterator.remove(); // Remove the off-screen pipe

                    // Create a new pipe based on the current level
                    Pipe newPipe;
                    if (isBossLevel) {
                        newPipe = new BossPipe(AppConstants.SCREEN_HEIGHT, currentGapSize);
                    } else {
                        newPipe = new Pipe(AppConstants.SCREEN_HEIGHT, currentGapSize);
                    }
                    newPipe.resetPosition(getLastPipeX() + pipeSpacing, currentGapSize);
                    pipesToAdd.add(newPipe); // Add to temporary list
                }
            }

            // Add all new pipes after the iteration to avoid ConcurrentModificationException
            pipes.addAll(pipesToAdd);
        }
    }

    /**
     * Draws all the pipes onto the provided canvas.
     *
     * @param canvas The Canvas object where pipes will be drawn.
     */
    public void draw(Canvas canvas) {
        synchronized (lock) {
            for (Pipe pipe : pipes) {
                pipe.draw(canvas);
            }
        }
    }

    /**
     * Checks for collisions between the bird and any pipe.
     *
     * @param bird The Bird object to check collisions against.
     * @return True if a collision is detected, false otherwise.
     */
    public boolean checkCollision(Bird bird) {
        synchronized (lock) {
            if (bird.isInvincible()) {
                return false; // Bird is invincible; no collision
            }

            for (Pipe pipe : pipes) {
                // Retrieve the collision rectangles
                Rect birdRect = bird.getCollisionShape();
                Rect topPipeRect = pipe.getTopPipeRect();
                Rect bottomPipeRect = pipe.getBottomPipeRect();

                // Check for intersection between bird and pipes
                if (Rect.intersects(birdRect, topPipeRect) || Rect.intersects(birdRect, bottomPipeRect)) {
                    return true; // Collision detected
                }
            }
            return false;
        }
    }

    /**
     * Checks if the bird has successfully passed any pipe, incrementing the score accordingly.
     *
     * @param bird The Bird object to check against.
     * @return True if the bird has passed a pipe and the score should be incremented, false otherwise.
     */
    public boolean checkForScore(Bird bird) {
        synchronized (lock) {
            for (Pipe pipe : pipes) {
                if (!pipe.isScored() && pipe.getX() + pipe.getWidth() < bird.getX()) {
                    pipe.setScored(true); // Mark the pipe as scored
                    return true; // Successful pass
                }
            }
            return false;
        }
    }

    /**
     * Decreases the gap size by a specified amount.
     *
     * @param amount The amount to decrease the gap size by.
     */
    public void decreaseGap(int amount) {
        synchronized (lock) {
            currentGapSize -= amount;
            if (currentGapSize < (int) (AppConstants.SCREEN_HEIGHT * 0.15)) {
                currentGapSize = (int) (AppConstants.SCREEN_HEIGHT * 0.15f); // Minimum gap size
            }
            for (Pipe pipe : pipes) {
                pipe.setGapSize(currentGapSize); // Update gap size for all existing pipes
            }
        }
    }

    /**
     * Sets the gap size between pipes, used when changing levels.
     *
     * @param gapSize The new gap size in pixels.
     */
    public void setGapSize(int gapSize) {
        synchronized (lock) {
            currentGapSize = gapSize;
            for (Pipe pipe : pipes) {
                pipe.setGapSize(gapSize); // Update gap size for all existing pipes
            }
        }
    }

    /**
     * Retrieves the X-coordinate of the last pipe in the list.
     *
     * @return The X-coordinate of the last pipe, or the screen width if no pipes exist.
     */
    private int getLastPipeX() {
        synchronized (lock) {
            if (pipes.size() > 0) {
                return pipes.get(pipes.size() - 1).getX();
            } else {
                return AppConstants.SCREEN_WIDTH;
            }
        }
    }

    /**
     * Updates the pipe settings when advancing to the next level.
     */
    public void onLevelChange() {
        synchronized (lock) {
            // Update the boss level flag based on the new current level
            isBossLevel = levelManager.isLastLevel();
            // Update the current gap size based on the new level
            currentGapSize = levelManager.getCurrentLevel().getGapSize();
            initializePipes(); // Re-initialize pipes with new settings
        }
    }

    /**
     * Getter for currentGapSize.
     *
     * @return The current gap size between pipes in pixels.
     */
    public int getCurrentGapSize() {
        return currentGapSize;
    }
}
package com.example.flappybirdgame;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Log;

import java.util.Random;

/**
 * Represents a power-up item in the game, handling its position and rendering.
 */
public class PowerUp {
    private int x, y, width, height;
    private Bitmap bitmap;
    private PowerUpType type;
    private Rect collisionRect;
    private int speed;
    private boolean isActive = false;
    private Random random = new Random();

    /**
     * Constructor for PowerUp.
     * Initializes and scales the power-up bitmap based on its type.
     *
     * @param type The type of power-up.
     */
    public PowerUp(PowerUpType type) {
        this.type = type;

        // Fetch the appropriate bitmap based on the power-up type
        Bitmap original = null;
        switch (type) {
            case INVINCIBILITY:
                original = ResourceManager.getInstance(AppConstants.gameActivityContext)
                        .getBitmap(R.drawable.powerup_invincibility);
                break;
            case SLOW_MOTION:
                original = ResourceManager.getInstance(AppConstants.gameActivityContext)
                        .getBitmap(R.drawable.powerup_slow_motion);
                break;
            case SHRINK:
                original = ResourceManager.getInstance(AppConstants.gameActivityContext)
                        .getBitmap(R.drawable.powerup_shrink);
                break;
            case MAGNET:
                original = ResourceManager.getInstance(AppConstants.gameActivityContext)
                        .getBitmap(R.drawable.powerup_magnet);
                break;
        }

        if (original != null) {
            // Scale bitmap to 6% of screen width
            int desiredWidth = (int) (AppConstants.SCREEN_WIDTH * 0.06f);
            bitmap = Bitmap.createScaledBitmap(original, desiredWidth,
                    original.getHeight() * desiredWidth / original.getWidth(), false);

            width = bitmap.getWidth();
            height = bitmap.getHeight();

            x = AppConstants.SCREEN_WIDTH;
            y = getRandomYPosition();
            collisionRect = new Rect(x, y, x + width, y + height);
        } else {
            Log.e("PowerUp", "Bitmap for " + type + " is null. Using default size.");
            width = 50;
            height = 50;
            bitmap = null;
            x = AppConstants.SCREEN_WIDTH;
            y = getRandomYPosition();
            collisionRect = new Rect(x, y, x + width, y + height);
        }
    }

    /**
     * Generates a random Y position within screen bounds for the power-up.
     *
     * @return A random Y position.
     */
    private int getRandomYPosition() {
        int minY = 0;
        int maxY = AppConstants.GROUND_Y - height;
        return minY + random.nextInt(Math.max(maxY - minY, 1));
    }

    /**
     * Updates the power-up's position.
     */
    public void update() {
        speed = AppConstants.PIPE_SPEED;
        x -= speed;
        collisionRect.set(x, y, x + width, y + height);
    }

    /**
     * Draws the power-up onto the canvas.
     *
     * @param canvas The Canvas object where the power-up will be drawn.
     */
    public void draw(Canvas canvas) {
        if (bitmap != null) {
            canvas.drawBitmap(bitmap, x, y, null);
        }
    }

    /**
     * Retrieves the collision rectangle for collision detection.
     *
     * @return The collision Rect.
     */
    public Rect getCollisionRect() {
        return collisionRect;
    }

    /**
     * Retrieves the type of power-up.
     *
     * @return The PowerUpType.
     */
    public PowerUpType getType() {
        return type;
    }

    /**
     * Checks if the power-up is off-screen.
     *
     * @return True if off-screen, false otherwise.
     */
    public boolean isOffScreen() {
        return x + width < 0;
    }

    // Getters and Setters for Position
    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public void setX(int x) {
        this.x = x;
        collisionRect.left = x;
        collisionRect.right = x + width;
    }

    public void setY(int y) {
        this.y = y;
        collisionRect.top = y;
        collisionRect.bottom = y + height;
    }

    /**
     * Retrieves the bitmap associated with this power-up.
     *
     * @return The bitmap, or null if not available.
     */
    public Bitmap getBitmap() {
        return bitmap;
    }
}
package com.example.flappybirdgame;

public enum PowerUpType {
    INVINCIBILITY,
    SLOW_MOTION,
    SHRINK,
    MAGNET
}
package com.example.flappybirdgame;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

import java.util.HashMap;
import java.util.Map;

/**
 * Singleton class responsible for loading, caching, and recycling bitmap resources.
 */
public class ResourceManager {
    private static ResourceManager instance;
    private Map<Integer, Bitmap> bitmapCache;
    private Context context;

    private ResourceManager(Context context) {
        this.context = context.getApplicationContext();
        bitmapCache = new HashMap<>();
    }

    /**
     * Retrieves the singleton instance of ResourceManager.
     *
     * @param context The application context.
     * @return The ResourceManager instance.
     */
    public static synchronized ResourceManager getInstance(Context context) {
        if (instance == null) {
            instance = new ResourceManager(context);
        }
        return instance;
    }

    /**
     * Retrieves a cached bitmap resource, loading it if not already cached.
     *
     * @param resourceId The drawable resource ID.
     * @return The cached bitmap, or null if loading failed.
     */
    public Bitmap getBitmap(int resourceId) {
        Bitmap bitmap = bitmapCache.get(resourceId);
        if (bitmap == null) {
            bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId);
            if (bitmap != null) {
                bitmapCache.put(resourceId, bitmap);
                Log.d("ResourceManager", "Loaded bitmap: " + getResourceName(resourceId));
            } else {
                Log.e("ResourceManager", "Failed to load bitmap: " + getResourceName(resourceId));
            }
        }
        return bitmap;
    }

    /**
     * Recycles all cached bitmaps to free up memory.
     */
    public void recycleBitmaps() {
        for (Bitmap bmp : bitmapCache.values()) {
            if (bmp != null && !bmp.isRecycled()) {
                bmp.recycle();
                Log.d("ResourceManager", "Recycled bitmap.");
            }
        }
        bitmapCache.clear();
    }

    /**
     * Retrieves the resource name based on its ID.
     *
     * @param resourceId The drawable resource ID.
     * @return The resource name as a string.
     */
    private String getResourceName(int resourceId) {
        return context.getResources().getResourceEntryName(resourceId);
    }
}
package com.example.flappybirdgame;

import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.google.android.material.slider.Slider;

import java.util.Locale;

/**
 * Activity to handle game settings like sound, difficulty, sensitivity, controls, and language.
 */
public class SettingsActivity extends AppCompatActivity {

    private Switch soundSwitch;
    private Slider difficultySlider;
    private SeekBar sensitivitySeekBar;
    private Switch invertedControlsSwitch;
    private Spinner languageSpinner;
    private Button privacyPolicyButton;

    // Flag to prevent OnItemSelected from triggering on initial setup
    private boolean isSpinnerInitialized = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);

        // Initialize UI elements using findViewById
        soundSwitch = findViewById(R.id.soundSwitch);
        difficultySlider = findViewById(R.id.difficultySlider);
        sensitivitySeekBar = findViewById(R.id.sensitivitySeekBar);
        invertedControlsSwitch = findViewById(R.id.invertedControlsSwitch);
        languageSpinner = findViewById(R.id.languageSpinner);
        privacyPolicyButton = findViewById(R.id.privacyPolicyButton);

        // Load settings
        SharedPreferences prefs = getSharedPreferences("game", MODE_PRIVATE);
        boolean soundEnabled = prefs.getBoolean("soundEnabled", true);
        int difficulty = prefs.getInt("difficulty", 1);
        int sensitivity = prefs.getInt("sensitivity", 5);
        boolean invertedControls = prefs.getBoolean("invertedControls", false);
        String language = prefs.getString("language", "en");

        soundSwitch.setChecked(soundEnabled);
        difficultySlider.setValue(difficulty);
        sensitivitySeekBar.setProgress(sensitivity);
        invertedControlsSwitch.setChecked(invertedControls);

        // Set up language spinner
        String[] languages = {getString(R.string.english), getString(R.string.persian)}; // English, Persian
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_dropdown_item, languages);
        languageSpinner.setAdapter(adapter);

        // Determine spinner position based on saved language
        int spinnerPosition = language.equals("en") ? 0 : 1;
        languageSpinner.setSelection(spinnerPosition);
        isSpinnerInitialized = true; // Spinner is now initialized

        // Handle changes to the sound switch
        soundSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
            SharedPreferences.Editor editor = prefs.edit();
            editor.putBoolean("soundEnabled", isChecked);
            editor.apply();
            AppConstants.SOUND_ENABLED = isChecked;
            Toast.makeText(this, isChecked ? "Sound Enabled" : "Sound Disabled", Toast.LENGTH_SHORT).show();
        });

        // Handle changes to the difficulty slider
        difficultySlider.addOnChangeListener((Slider slider, float value, boolean fromUser) -> {
            if (fromUser) { // Only respond to user-initiated changes
                int difficultyLevel = (int) value;
                SharedPreferences.Editor editor = prefs.edit();
                editor.putInt("difficulty", difficultyLevel);
                editor.apply();
                AppConstants.DIFFICULTY = difficultyLevel;
                Toast.makeText(this, "Difficulty set to " + difficultyLevel, Toast.LENGTH_SHORT).show();
            }
        });

        // Handle changes to the sensitivity seekbar
        sensitivitySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                if (fromUser) { // Only respond to user-initiated changes
                    SharedPreferences.Editor editor = prefs.edit();
                    editor.putInt("sensitivity", progress);
                    editor.apply();
                    AppConstants.SENSITIVITY = progress;
                    Toast.makeText(SettingsActivity.this, "Sensitivity set to " + progress, Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                // Not needed
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                // Not needed
            }
        });

        // Handle changes to the inverted controls switch
        invertedControlsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
            SharedPreferences.Editor editor = prefs.edit();
            editor.putBoolean("invertedControls", isChecked);
            editor.apply();
            AppConstants.INVERTED_CONTROLS = isChecked;
            Toast.makeText(this, isChecked ? "Controls Inverted" : "Controls Normal", Toast.LENGTH_SHORT).show();
        });

        // Handle language selection
        languageSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) {
                if (!isSpinnerInitialized) return; // Ignore initial selection

                String selectedLanguage = position == 0 ? "en" : "fa"; // "en" for English, "fa" for Persian
                if (!AppConstants.LANGUAGE.equals(selectedLanguage)) {
                    setLocale(selectedLanguage);

                    // Save language preference
                    SharedPreferences.Editor editor = prefs.edit();
                    editor.putString("language", selectedLanguage);
                    editor.apply();
                    AppConstants.LANGUAGE = selectedLanguage;

                    Toast.makeText(SettingsActivity.this, "Language set to " + languages[position], Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onNothingSelected(AdapterView<?> adapterView) {
                // Not needed
            }
        });

        // Handle privacy policy button click
        privacyPolicyButton.setOnClickListener(v -> {
            Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://your_privacy_policy_url.com"));
            startActivity(browserIntent);
        });
    }

    /**
     * Sets the application's locale and restarts the activity to apply changes.
     *
     * @param lang The language code (e.g., "en" for English, "fa" for Persian).
     */
    private void setLocale(String lang) {
        Locale locale = new Locale(lang);
        Locale.setDefault(locale);

        Resources resources = getResources();
        Configuration config = resources.getConfiguration();
        config.setLocale(locale);

        resources.updateConfiguration(config, resources.getDisplayMetrics());

        // Restart the activity to apply changes
        Intent intent = getIntent();
        finish();
        startActivity(intent);
    }
}
package com.example.flappybirdgame;

public class Theme {
    public int birdResourceId; // We'll handle different birds separately
    public int backgroundResourceId;
    public int pipeTopResourceId;
    public int pipeBottomResourceId;

    public Theme(int backgroundResourceId, int pipeTopResourceId, int pipeBottomResourceId) {
        this.backgroundResourceId = backgroundResourceId;
        this.pipeTopResourceId = pipeTopResourceId;
        this.pipeBottomResourceId = pipeBottomResourceId;
    }
}<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Achievements Title -->
    <TextView
        android:id="@+id/achievementsTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/achievements"
        android:textSize="32sp"
        android:textColor="@color/colorPrimaryDark"
        android:textStyle="bold"
        android:fontFamily="@font/poppins_bold"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <!-- Achievements List -->
    <ListView
        android:id="@+id/achievementsListView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:divider="@android:color/darker_gray"
        android:dividerHeight="1dp"
        app:layout_constraintTop_toBottomOf="@id/achievementsTitle"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- Character Selection Title -->
    <TextView
        android:id="@+id/characterSelectionTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/character_selection"
        android:textSize="32sp"
        android:textColor="@color/colorPrimaryDark"
        android:textStyle="bold"
        android:fontFamily="@font/poppins_bold"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="16dp"/>

    <!-- Bird GridView -->
    <GridView
        android:id="@+id/birdGridView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:numColumns="3"
        android:verticalSpacing="16dp"
        android:horizontalSpacing="16dp"
        android:padding="16dp"
        android:stretchMode="columnWidth"
        android:gravity="center"/>

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- Back Button -->
    <ImageButton
        android:id="@+id/backButton"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:src="@android:drawable/ic_delete"
        android:contentDescription="Back"
        android:background="?attr/selectableItemBackgroundBorderless"
        app:tint="@color/colorPrimaryDark"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginTop="16dp"
        android:layout_marginStart="16dp"/>

    <!-- Help Title -->
    <TextView
        android:id="@+id/helpTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="How to Play"
        android:textSize="32sp"
        android:textColor="@color/colorPrimaryDark"
        android:textStyle="bold"
        android:fontFamily="@font/poppins_bold"
        android:layout_marginTop="16dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toEndOf="@id/backButton"
        app:layout_constraintEnd_toEndOf="parent"/>

    <!-- Help Content -->
    <ScrollView
        android:id="@+id/helpScrollView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toBottomOf="@id/helpTitle"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        android:layout_marginBottom="16dp">

        <TextView
            android:id="@+id/helpContent"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/help_text"
            android:textColor="@android:color/black"
            android:textSize="16sp"
            android:fontFamily="@font/poppins_regular"/>

    </ScrollView>

</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <!-- Background Image -->
    <ImageView
        android:id="@+id/backgroundImage"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:src="@drawable/background"
    android:scaleType="centerCrop"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent" />

    <!-- Game Title -->
    <TextView
        android:id="@+id/gameTitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/app_name"
        android:textSize="48sp"
        android:textColor="@android:color/white"
        android:textStyle="bold"
        android:fontFamily="sans-serif-medium"
        android:elevation="5dp"
        android:layout_marginTop="32dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <!-- High Score Text -->
    <TextView
        android:id="@+id/highScoreText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/high_score"
        android:textSize="20sp"
        android:textColor="@android:color/white"
        android:fontFamily="sans-serif"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toBottomOf="@id/gameTitle"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <!-- Play Button -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/playButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/play"
        android:textSize="18sp"
        android:textColor="@android:color/white"
        android:fontFamily="sans-serif-medium"
        android:backgroundTint="@color/colorPrimary"
    app:cornerRadius="12dp"
    app:icon="@drawable/ic_play_arrow"
    app:iconTint="@android:color/white"
    app:iconPadding="8dp"
    app:iconGravity="textStart"
    android:layout_marginTop="24dp"
    app:layout_constraintTop_toBottomOf="@id/highScoreText"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintWidth_percent="0.8"/>

    <!-- Settings Button -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/settingsButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/settings"
        android:textSize="18sp"
        android:textColor="@android:color/white"
        android:fontFamily="sans-serif-medium"
        android:backgroundTint="@color/colorPrimaryDark"
    app:cornerRadius="12dp"
    app:icon="@android:drawable/ic_menu_manage"
    app:iconTint="@android:color/white"
    app:iconPadding="8dp"
    app:iconGravity="textStart"
    android:layout_marginTop="16dp"
    app:layout_constraintTop_toBottomOf="@id/playButton"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintWidth_percent="0.8"/>

    <!-- Help Button -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/helpButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/help"
        android:textSize="18sp"
        android:textColor="@android:color/white"
        android:fontFamily="sans-serif-medium"
        android:backgroundTint="@color/colorAccent"
    app:cornerRadius="12dp"
    app:icon="@android:drawable/ic_menu_help"
    app:iconTint="@android:color/white"
    app:iconPadding="8dp"
    app:iconGravity="textStart"
    android:layout_marginTop="16dp"
    app:layout_constraintTop_toBottomOf="@id/settingsButton"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintWidth_percent="0.8"/>

    <!-- Achievements Button -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/achievementsButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/achievements"
        android:textSize="18sp"
        android:textColor="@android:color/white"
        android:fontFamily="sans-serif-medium"
        android:backgroundTint="@color/colorPrimaryVariant"
    app:cornerRadius="12dp"
    app:icon="@drawable/ic_trophy"
    app:iconTint="@android:color/white"
    app:iconPadding="8dp"
    app:iconGravity="textStart"
    android:layout_marginTop="16dp"
    app:layout_constraintTop_toBottomOf="@id/helpButton"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintWidth_percent="0.8"/>

    <!-- Character Selection Button -->
    <com.google.android.material.button.MaterialButton
        android:id="@+id/characterSelectionButton"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="@string/character_selection"
        android:textSize="18sp"
        android:textColor="@android:color/white"
        android:fontFamily="sans-serif-medium"
        android:backgroundTint="@color/colorSecondary"
    app:cornerRadius="12dp"
    app:icon="@drawable/ic_person1"
    app:iconTint="@android:color/white"
    app:iconPadding="8dp"
    app:iconGravity="textStart"
    android:layout_marginTop="16dp"
    app:layout_constraintTop_toBottomOf="@id/achievementsButton"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintWidth_percent="0.8"/>

</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:fillViewport="true">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="16dp">

        <!-- Settings Title -->
        <TextView
            android:id="@+id/settingsTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/settings"
            android:textSize="32sp"
            android:textColor="@color/colorPrimaryDark"
            android:textStyle="bold"
            android:fontFamily="@font/poppins_bold"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>

        <!-- Sound Setting -->
        <TextView
            android:id="@+id/soundLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/sound"
            android:textSize="18sp"
            android:textColor="@android:color/black"
            android:fontFamily="@font/poppins_medium"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@id/settingsTitle"
            app:layout_constraintStart_toStartOf="parent"/>

        <Switch
            android:id="@+id/soundSwitch"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:checked="true"
            app:layout_constraintTop_toTopOf="@id/soundLabel"
            app:layout_constraintBottom_toBottomOf="@id/soundLabel"
            app:layout_constraintEnd_toEndOf="parent"/>

        <!-- Difficulty Setting -->
        <TextView
            android:id="@+id/difficultyLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/difficulty"
            android:textSize="18sp"
            android:textColor="@android:color/black"
            android:fontFamily="@font/poppins_medium"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@id/soundLabel"
            app:layout_constraintStart_toStartOf="parent"/>

        <com.google.android.material.slider.Slider
            android:id="@+id/difficultySlider"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:value="1"
            android:valueFrom="1"
            android:valueTo="3"
            android:stepSize="1"
            android:theme="@style/Widget.MaterialComponents.Slider"
            app:layout_constraintTop_toBottomOf="@id/difficultyLabel"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

        <TextView
            android:id="@+id/difficultyDescription"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/difficulty_description"
            android:textSize="14sp"
            android:textColor="@android:color/darker_gray"
            android:fontFamily="@font/poppins_regular"
            android:layout_marginTop="8dp"
            app:layout_constraintTop_toBottomOf="@id/difficultySlider"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

        <!-- Sensitivity Setting -->
        <TextView
            android:id="@+id/sensitivityLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/sensitivity"
            android:textSize="18sp"
            android:textColor="@android:color/black"
            android:fontFamily="@font/poppins_medium"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@id/difficultyDescription"
            app:layout_constraintStart_toStartOf="parent"/>

        <SeekBar
            android:id="@+id/sensitivitySeekBar"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:max="10"
            android:progress="5"
            app:layout_constraintTop_toBottomOf="@id/sensitivityLabel"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

        <!-- Inverted Controls Setting -->
        <TextView
            android:id="@+id/invertedControlsLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/invert_controls"
            android:textSize="18sp"
            android:textColor="@android:color/black"
            android:fontFamily="@font/poppins_medium"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@id/sensitivitySeekBar"
            app:layout_constraintStart_toStartOf="parent"/>

        <Switch
            android:id="@+id/invertedControlsSwitch"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="@id/invertedControlsLabel"
            app:layout_constraintBottom_toBottomOf="@id/invertedControlsLabel"
            app:layout_constraintEnd_toEndOf="parent"/>

        <!-- Language Setting -->
        <TextView
            android:id="@+id/languageLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/language"
            android:textSize="18sp"
            android:textColor="@android:color/black"
            android:fontFamily="@font/poppins_medium"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@id/invertedControlsLabel"
            app:layout_constraintStart_toStartOf="parent"/>

        <Spinner
            android:id="@+id/languageSpinner"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="@id/languageLabel"
            app:layout_constraintBottom_toBottomOf="@id/languageLabel"
            app:layout_constraintEnd_toEndOf="parent"/>

        <!-- Privacy Policy Button -->
        <Button
            android:id="@+id/privacyPolicyButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/privacy_policy"
            android:layout_marginTop="32dp"
            app:layout_constraintTop_toBottomOf="@id/languageLabel"
            app:layout_constraintStart_toStartOf="parent"/>

    </androidx.constraintlayout.widget.ConstraintLayout>

</ScrollView><?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.flappybirdgame">

    <!-- Specify minimum and target SDK versions -->
    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="33" />

    <!-- Permissions -->
    <uses-permission android:name="android.permission.INTERNET" /> <!-- If you have internet features -->

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.FlappyBirdGame">

        <!-- Main Activity - Launcher -->
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.FlappyBirdGame.NoActionBar">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Game Activity -->
        <activity
            android:name=".GameActivity"
            android:exported="false"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.FlappyBirdGame">
        </activity>

        <!-- Help Activity -->
        <activity
            android:name=".HelpActivity"
            android:exported="false"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.FlappyBirdGame.NoActionBar">
        </activity>

        <!-- Settings Activity -->
        <activity
            android:name=".SettingsActivity"
            android:exported="false"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.FlappyBirdGame.NoActionBar">
        </activity>

        <!-- Achievements Activity -->
        <activity
            android:name=".AchievementsActivity"
            android:exported="false"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.FlappyBirdGame.NoActionBar">
        </activity>

        <!-- Character Selection Activity -->
        <activity
            android:name=".CharacterSelectionActivity"
            android:exported="false"
            android:screenOrientation="portrait"
            android:theme="@style/Theme.FlappyBirdGame.NoActionBar">
        </activity>

    </application>

</manifest>FATAL EXCEPTION: main
                                                                                                    Process: com.example.flappybirdgame, PID: 484
                                                                                                    java.lang.IllegalArgumentException: width and height must be > 0
                                                                                                      at android.graphics.Bitmap.createBitmap(Bitmap.java:877)
                                                                                                      at android.graphics.Bitmap.createBitmap(Bitmap.java:856)
                                                                                                      at android.graphics.Bitmap.createBitmap(Bitmap.java:787)
                                                                                                      at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:663)
                                                                                                      at com.example.flappybirdgame.Bird.resetSize(Bird.java:171)
                                                                                                      at com.example.flappybirdgame.GameView.lambda$activatePowerUp$10$com-example-flappybirdgame-GameView(GameView.java:562)
                                                                                                      at com.example.flappybirdgame.GameView$$ExternalSyntheticLambda4.run(D8$$SyntheticClass:0)
                                                                                                      at android.os.Handler.handleCallback(Handler.java:751)
                                                                                                      at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                                                      at android.os.Looper.loop(Looper.java:154)
                                                                                                      at android.app.ActivityThread.main(ActivityThread.java:6077)
                                                                                                      at java.lang.reflect.Method.invoke(Native Method)
                                                                                                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
                                                                                                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756) the size of the bird and pipes and ground and background and others have problem like bird is to small and some pipes are out of screen and some are in the screen and some are to close together . make the sizes match to the screen . and the movement of the pipes is not good and sometime a pipe with a pipe collision happen and also pipes with power ups collision and also fix other thing to in a best way. write all changed classes completely