package com.example.flappybirdgame;

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

/**
 * Represents an individual achievement in the game.
 */
public class Achievement {

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

    /**
     * Constructs a new Achievement instance.
     *
     * @param name        The display name of the achievement.
     * @param description A brief description of the achievement.
     * @param key         The unique key associated with this achievement.
     */
    public Achievement(String name, String description, String key) {
        this.name = name;
        this.description = description;
        this.key = key;
        this.unlocked = false;
    }

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

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

    /**
     * Unlocks the achievement if not already unlocked.
     *
     * @param context The application context.
     */
    public void unlock(Context context) {
        if (!unlocked) {
            unlocked = true;
            save(context); // Persist 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

    /**
     * Sets the unlock status of the achievement.
     *
     * @param unlocked The new unlock status.
     */
    public void setUnlocked(boolean unlocked) {
        this.unlocked = unlocked;
    }
}
package com.example.flappybirdgame;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

/**
 * RecyclerView Adapter for displaying achievements.
 */
public class AchievementAdapter extends RecyclerView.Adapter<AchievementAdapter.ViewHolder> {

    private List<Achievement> achievements;
    private final Context context;

    /**
     * Constructor for AchievementAdapter.
     *
     * @param achievements The list of achievements to display.
     * @param context      The context from the activity.
     */
    public AchievementAdapter(List<Achievement> achievements, Context context) {
        this.achievements = achievements;
        this.context = context;
    }

    /**
     * Updates the adapter's data and refreshes the RecyclerView.
     *
     * @param newAchievements The updated list of achievements.
     */
    public void updateData(List<Achievement> newAchievements) {
        this.achievements = newAchievements;
        notifyDataSetChanged();
    }

    /**
     * ViewHolder class for holding achievement item views.
     */
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView achievementName;
        public TextView achievementDescription;
        public ImageView achievementStatus;

        public ViewHolder(View itemView) {
            super(itemView);
            achievementName = itemView.findViewById(R.id.achievementName);
            achievementDescription = itemView.findViewById(R.id.achievementDescription);
            achievementStatus = itemView.findViewById(R.id.achievementStatus);
        }
    }

    @Override
    public AchievementAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // Inflate the achievement_item layout
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.achievement_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(AchievementAdapter.ViewHolder holder, int position) {
        Achievement achievement = achievements.get(position);
        holder.achievementName.setText(achievement.getName());
        holder.achievementDescription.setText(achievement.getDescription());

        if (achievement.isUnlocked()) {
            holder.achievementStatus.setImageResource(R.drawable.ic_unlocked); // Replace with your unlocked icon
        } else {
            holder.achievementStatus.setImageResource(R.drawable.ic_locked); // Replace with your locked icon
        }
    }

    @Override
    public int getItemCount() {
        return achievements.size();
    }
}
package com.example.flappybirdgame;

/**
 * Defines constant keys for achievements to ensure consistency and prevent typos.
 */
public class AchievementKeys {
    public static final String FIRST_FLAP = "achievement_first_flap";
    public static final String FLAPS_500 = "achievement_500_flaps";
    public static final String HIGH_SCORER = "achievement_high_scorer";
    public static final String COOL_BIRD_UNLOCK = "bird_unlocked_cool_bird";
    // Add more achievement keys as needed
}
package com.example.flappybirdgame;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

/**
 * Manages all achievements within the game, handling their loading, saving, and evaluation.
 */
public class AchievementManager {

    private final Map<String, Achievement> achievementsMap = new HashMap<>();
    private final Context context;
    private final Handler mainHandler; // Handler to post Toasts to the main thread

    /**
     * Constructs a new AchievementManager and initializes all achievements.
     *
     * @param context The application context.
     */
    public AchievementManager(Context context) {
        this.context = context.getApplicationContext();
        this.mainHandler = new Handler(Looper.getMainLooper());
        initializeAchievements();
        loadAllAchievements();
    }

    /**
     * Initializes the achievements and populates the achievements map.
     */
    private void initializeAchievements() {
        // Define achievements with their keys, names, and descriptions
        addAchievement(new Achievement(
                context.getString(R.string.flap_1_time),
                context.getString(R.string.flap_1_time_desc),
                AchievementKeys.FIRST_FLAP
        ));

        addAchievement(new Achievement(
                context.getString(R.string.flap_500_times),
                context.getString(R.string.flap_500_times_desc),
                AchievementKeys.FLAPS_500
        ));

        addAchievement(new Achievement(
                context.getString(R.string.score_100_points),
                context.getString(R.string.score_100_points_desc),
                AchievementKeys.HIGH_SCORER
        ));

        // Add more achievements as needed
    }

    /**
     * Adds an achievement to the achievements map.
     *
     * @param achievement The Achievement instance to add.
     */
    private void addAchievement(Achievement achievement) {
        achievementsMap.put(achievement.getKey(), achievement);
    }

    /**
     * Loads the unlocked status for all achievements from SharedPreferences.
     */
    public void loadAllAchievements() {
        SharedPreferences prefs = context.getSharedPreferences("achievements", Context.MODE_PRIVATE);
        for (Achievement achievement : achievementsMap.values()) {
            achievement.setUnlocked(prefs.getBoolean(achievement.getKey(), false));
        }
    }

    /**
     * Saves the unlocked status for all achievements to SharedPreferences.
     */
    public void saveAllAchievements() {
        SharedPreferences prefs = context.getSharedPreferences("achievements", Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = prefs.edit();
        for (Achievement achievement : achievementsMap.values()) {
            editor.putBoolean(achievement.getKey(), achievement.isUnlocked());
        }
        editor.apply();
    }

    /**
     * Checks and updates achievements based on the player's performance.
     *
     * @param flapCount The number of flaps made by the player.
     * @param score     The current score of the player.
     */
    public void checkAchievements(int flapCount, int score) {
        for (Achievement achievement : achievementsMap.values()) {
            if (!achievement.isUnlocked()) {
                boolean shouldUnlock = false;

                switch (achievement.getKey()) {
                    case AchievementKeys.FIRST_FLAP:
                        if (flapCount >= 1) {
                            shouldUnlock = true;
                        }
                        break;

                    case AchievementKeys.FLAPS_500:
                        if (flapCount >= 500) {
                            shouldUnlock = true;
                        }
                        break;

                    case AchievementKeys.HIGH_SCORER:
                        if (score >= 100) {
                            shouldUnlock = true;
                        }
                        break;

                    // Handle additional achievements here

                    default:
                        break;
                }

                if (shouldUnlock) {
                    achievement.unlock(context); // Unlock and save status
                    notifyPlayer(achievement); // Notify the player on the main thread
                }
            }
        }

        // Persist changes after checking all achievements
        saveAllAchievements();
    }

    /**
     * Notifies the player about an unlocked achievement on the main thread.
     *
     * @param achievement The Achievement that was unlocked.
     */
    private void notifyPlayer(Achievement achievement) {
        String message = String.format(context.getString(R.string.achievement_unlocked), achievement.getName());
        mainHandler.post(() -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show());
    }

    /**
     * Retrieves a list of all achievements.
     *
     * @return A list containing all Achievement instances.
     */
    public List<Achievement> getAllAchievements() {
        return new ArrayList<>(achievementsMap.values());
    }
}
package com.example.flappybirdgame;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

/**
 * Activity to display all the achievements using RecyclerView.
 */
public class AchievementsActivity extends AppCompatActivity {
    private RecyclerView achievementsRecyclerView;
    private AchievementAdapter achievementAdapter;
    private AchievementManager achievementManager;

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

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

        // Set up RecyclerView with a vertical LinearLayoutManager
        achievementsRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        // Initialize adapter with the list of achievements
        List<Achievement> achievementsList = achievementManager.getAllAchievements();
        achievementAdapter = new AchievementAdapter(achievementsList, this);
        achievementsRecyclerView.setAdapter(achievementAdapter);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // Reload achievements in case new ones were unlocked
        achievementManager.loadAllAchievements();
        achievementAdapter.updateData(achievementManager.getAllAchievements());
    }
}
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 float BACKGROUND_SPEED = 5.0f; // Changed to float for smooth adjustments
    public static int PIPE_SPEED = 6;            // Changed to int as speed is used in pixels/frame

    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
        ));

        // Add more themes as needed

        // Initialize BirdManager using Singleton Pattern
        BirdManager.getInstance().initialize(context);

        // 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 (already done above, remove duplicate if present)
        // BirdManager.initialize(context); // Remove this line if present

        // Initialize BirdManager elsewhere if not already done
    }

    // 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.graphics.Paint;
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;
    private Paint paint;

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

        if (original == null) {
            Log.e("Background", "Background bitmap is null. Using default background color.");
            background = null;
        } 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 sky blue if background bitmap is unavailable
            paint.setColor(Color.parseColor("#87CEEB")); // Sky blue
            canvas.drawColor(Color.parseColor("#87CEEB"));
        }
    }

    /**
     * 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.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
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;       // Current 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.
     * Initializes and scales the bird bitmap to screen size.
     *
     * @param screenY The height of the screen.
     */
    public Bird(int screenY) {
        // Fetch the current selected bird's frames from BirdManager Singleton
        List<Integer> currentBirdFrames = BirdManager.getInstance().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 with a minimum size for visibility
                int desiredWidth = Math.max((int) (AppConstants.SCREEN_WIDTH * 0.05f), 100);
                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], 100, 100, false);
                }
            }
        }

        if (birdFrames.length > 0 && birdFrames[0] != null) {
            width = birdFrames[0].getWidth();
            height = birdFrames[0].getHeight();
            originalWidth = width;       // Initialize original width
            originalHeight = height;     // Initialize original height
        } else {
            width = 100;
            height = 100;
            originalWidth = width;
            originalHeight = height;
            birdFrames[0] = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(AppConstants.gameActivityContext.getResources(),
                    android.R.drawable.sym_def_app_icon), width, height, false);
        }

        // Initialize position based on screen dimensions
        x = AppConstants.SCREEN_WIDTH * 0.1f;
        y = screenY / 2f - height / 2f;

        collisionRect = new Rect();
        updateCollisionRect();
    }

    /**
     * Updates the collision rectangle based on current bird position.
     */
    private void updateCollisionRect() {
        collisionRect.set((int) x, (int) y, (int) x + width, (int) y + height);
    }

    /**
     * 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
        updateCollisionRect();

        // 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++) {
                if (birdFrames[i] != null) {
                    birdFrames[i] = Bitmap.createScaledBitmap(birdFrames[i], width, height, false);
                }
            }
            updateCollisionRect();
        }
    }

    /**
     * Resets the bird's size after a shrink power-up ends.
     */
    public void resetSize() {
        if (isShrunk) {
            if (originalWidth > 0 && originalHeight > 0) { // Ensure original sizes are set
                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.getInstance().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);
                    } else {
                        Log.e("Bird", "Original frame " + i + " is null during resetSize.");
                        birdFrames[i] = BitmapFactory.decodeResource(AppConstants.gameActivityContext.getResources(),
                                android.R.drawable.sym_def_app_icon);
                        if (birdFrames[i] != null) {
                            birdFrames[i] = Bitmap.createScaledBitmap(birdFrames[i], originalWidth, originalHeight, false);
                        }
                    }
                }
                isShrunk = false;
                updateCollisionRect(); // Update collision rect after reset
            } else {
                Log.e("Bird", "Original width or height not set. Cannot reset size.");
            }
        }
    }

    /**
     * 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.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.Toast;

import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

/**
 * RecyclerView Adapter for displaying bird character previews.
 */
public class BirdAdapter extends RecyclerView.Adapter<BirdAdapter.ViewHolder> {

    private List<List<Integer>> availableBirds;
    private final Context context;
    private final OnBirdClickListener listener;

    /**
     * Interface for handling bird click events.
     */
    public interface OnBirdClickListener {
        void onBirdClick(int position);
    }

    /**
     * Constructor for BirdAdapter.
     *
     * @param availableBirds The list of available birds, each represented by their frame resource IDs.
     * @param context        The context from the activity.
     * @param listener       The click listener for bird selection.
     */
    public BirdAdapter(List<List<Integer>> availableBirds, Context context, OnBirdClickListener listener) {
        this.availableBirds = availableBirds;
        this.context = context;
        this.listener = listener;
    }

    /**
     * Updates the adapter's data and refreshes the RecyclerView.
     *
     * @param newAvailableBirds The updated list of available birds.
     */
    public void updateData(List<List<Integer>> newAvailableBirds) {
        this.availableBirds = newAvailableBirds;
        notifyDataSetChanged();
    }

    /**
     * ViewHolder class for holding bird item views.
     */
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public ImageView birdPreview;
        public ImageView lockIcon;

        public ViewHolder(View itemView) {
            super(itemView);
            birdPreview = itemView.findViewById(R.id.birdPreviewImageView);
            lockIcon = itemView.findViewById(R.id.birdLockIcon);
        }
    }

    @Override
    public BirdAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // Inflate the bird_item layout
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.bird_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(BirdAdapter.ViewHolder holder, int position) {
        List<Integer> birdFrames = availableBirds.get(position);
        // Display the first frame as the preview image
        Bitmap birdBitmap = ResourceManager.getInstance(context).getBitmap(birdFrames.get(0));
        if (birdBitmap != null) {
            holder.birdPreview.setImageBitmap(birdBitmap);
        } else {
            holder.birdPreview.setImageResource(android.R.drawable.sym_def_app_icon); // Fallback image
        }

        // Determine if the bird is locked based on its availability
        boolean isUnlocked = BirdManager.isBirdUnlocked(BirdManager.getUnlockKey(position), context);
        holder.lockIcon.setVisibility(isUnlocked ? View.GONE : View.VISIBLE);

        // Set click listener
        holder.itemView.setOnClickListener(v -> {
            if (isUnlocked) {
                listener.onBirdClick(position);
            } else {
                Toast.makeText(context, "Unlock this bird to select it!", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public int getItemCount() {
        return availableBirds.size();
    }
}
package com.example.flappybirdgame;

import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;

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

/**
 * Manages the loading, unlocking, and selection of bird characters.
 * Implements the Singleton Pattern to ensure a single instance throughout the app.
 */
public class BirdManager {
    private static BirdManager instance;
    private final ArrayList<List<Integer>> availableBirds = new ArrayList<>();
    private int currentBirdIndex = 0;
    private static final String[] unlockKeys = {
            AchievementKeys.COOL_BIRD_UNLOCK
            // Add more unlock keys corresponding to additional birds
    };
    private boolean isInitialized = false; // Flag to prevent multiple initializations

    /**
     * Private constructor to prevent direct instantiation.
     */
    private BirdManager() {
        // Private constructor to enforce Singleton Pattern
    }

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

    /**
     * Initializes the BirdManager by loading available birds and their frames.
     * This method should be called once with a valid context.
     *
     * @param context The application context.
     */
    public void initialize(Context context) {
        if (isInitialized) {
            return; // Prevent re-initialization
        }
        isInitialized = true;

        SharedPreferences prefs = context.getSharedPreferences("game", Context.MODE_PRIVATE);

        // Load available birds with their respective frames

        // 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(AchievementKeys.COOL_BIRD_UNLOCK, 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);
        }

        // Add more birds as they are unlocked
    }

    /**
     * Retrieves the current bird's animation frames.
     *
     * @return A list of resource IDs for the current bird's frames.
     */
    public 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 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 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();
            Log.d("BirdManager", "Unlocked new bird with key: " + key);
            // Optionally notify observers or listeners about the new bird unlock
        }
    }

    /**
     * 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 a list of all available birds.
     *
     * @return A list of all available birds, each represented by their frame resource IDs.
     */
    public List<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 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;
    }

    /**
     * Retrieves the unlock key for a bird based on its position.
     *
     * @param position The position of the bird in the list.
     * @return The corresponding unlock key.
     */
    public static String getUnlockKey(int position) {
        if ((position - 1) < unlockKeys.length && (position - 1) >= 0) {
            return unlockKeys[position - 1];
        }
        return ""; // Default or return null based on preference
    }
}
package com.example.flappybirdgame;

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

/**
 * Represents a boss pipe in the game with vertical movement.
 */
public class BossPipe extends Pipe {
    private int verticalSpeed = 2; // Reduced vertical speed for better control
    private boolean movingDown = true; // Direction flag for vertical movement
    private int movementBoundaryTop;
    private int movementBoundaryBottom;

    /**
     * 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);
        // Define movement boundaries (e.g., can't move beyond 20% from top and 20% from bottom)
        movementBoundaryTop = (int) (screenHeight * 0.2f);
        movementBoundaryBottom = AppConstants.GROUND_Y - gapSize - pipeBottom.getHeight() - (int) (screenHeight * 0.2f);
    }

    /**
     * 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() >= movementBoundaryBottom) {
                movingDown = false; // Reverse direction
            }
        } else {
            y -= verticalSpeed;
            // Check upper boundary
            if (y <= movementBoundaryTop) {
                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, like a health bar
        Paint paint = new Paint();
        paint.setColor(Color.RED);
        paint.setStrokeWidth(4);
        canvas.drawLine(x, y - 20, x + width, y - 20, paint); // Example: Health bar above the pipe
    }
}
package com.example.flappybirdgame;

import android.os.Bundle;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

/**
 * Activity to display and select different bird characters using RecyclerView.
 */
public class CharacterSelectionActivity extends AppCompatActivity {

    private RecyclerView birdRecyclerView;
    private BirdAdapter birdAdapter;
    private BirdManager birdManager;

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

        // Obtain the singleton instance and initialize it with context
        birdManager = BirdManager.getInstance();
        birdManager.initialize(this);

        birdRecyclerView = findViewById(R.id.birdRecyclerView);

        // Set up RecyclerView with a GridLayoutManager (e.g., 3 columns)
        int numberOfColumns = 3;
        birdRecyclerView.setLayoutManager(new GridLayoutManager(this, numberOfColumns));

        // Initialize adapter with the list of available birds
        List<List<Integer>> availableBirds = birdManager.getAvailableBirds();
        birdAdapter = new BirdAdapter(availableBirds, this, new BirdAdapter.OnBirdClickListener() {
            @Override
            public void onBirdClick(int position) {
                // Set the selected bird
                birdManager.setCurrentBird(position, CharacterSelectionActivity.this);
                Toast.makeText(CharacterSelectionActivity.this, getString(R.string.bird_selected), Toast.LENGTH_SHORT).show();
                finish(); // Exit the Character Selection screen
            }
        });
        birdRecyclerView.setAdapter(birdAdapter);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // Refresh bird previews in case new birds were unlocked
        birdManager.initialize(this); // Re-initialize to load any new unlocked birds
        birdAdapter.updateData(birdManager.getAvailableBirds());
    }
}
package com.example.flappybirdgame;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
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 long activationTime; // Timestamp when the collectible was activated
    private boolean isCollected = false;

    /**
     * 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 with a minimum size for visibility
            int desiredWidth = Math.max((int) (AppConstants.SCREEN_WIDTH * 0.05f), 40);
            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("Collectible", "Coin bitmap 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 collectible.
     *
     * @return A random Y position.
     */
    private int getRandomYPosition() {
        int minY = 0;
        int maxY = AppConstants.GROUND_Y - height;
        return minY + new 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;
    }

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

    /**
     * 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;
    }
}
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 static android.content.Context.MODE_PRIVATE;

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

import androidx.annotation.NonNull;

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(@NonNull SurfaceHolder holder) {
        isPlaying = true;
        thread = new Thread(this);
        thread.start();
    }

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

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

    /**
     * The main game loop.
     */
    @Override
    public void run() {
        // Frame rate control variables
        long lastTime = System.nanoTime();
        double nsPerFrame = 1000000000.0 / 60.0; // Targeting ~60 FPS

        while (isPlaying) {
            long now = System.nanoTime();
            double delta = (now - lastTime) / nsPerFrame;

            if (delta >= 1) {
                if (!isPaused) {
                    update();
                }
                drawCanvas();
                lastTime = now;
            }

            // Calculate sleep time to maintain frame rate
            long sleepTime = lastTime + (long) nsPerFrame - System.nanoTime();
            if (sleepTime > 0) {
                try {
                    Thread.sleep(sleepTime / 1000000, (int) (sleepTime % 1000000));
                } catch (InterruptedException e) {
                    Log.e("GameView", "Thread sleep interrupted", e);
                }
            }
        }
    }

    /**
     * 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

        // Update pipe speed for existing pipes handled internally
    }

    /**
     * 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
            pipeManager.setGapSize(pipeManager.getCurrentGapSize()); // Update existing pipes' speed

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

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

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

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

        // Update power-ups
        synchronized (powerUps) {
            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
        synchronized (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);
                    }
                }
            }
        }

        // Update active power-ups based on timestamps
        updateActivePowerUps();
    }

    /**
     * Updates the status of active power-ups based on elapsed time.
     */
    private void updateActivePowerUps() {
        // Handling is already managed within activatePowerUp method using Handler.postDelayed
        // For a more robust solution, consider tracking activation times and durations here
        // Especially useful if handling game pauses
    }

    /**
     * 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
            synchronized (powerUps) {
                for (PowerUp powerUp : powerUps) {
                    if (powerUp.getBitmap() != null) { // Ensure getBitmap() exists
                        powerUp.draw(canvas);
                    }
                }
            }

            // Draw collectibles
            synchronized (collectibles) {
                for (Collectible collectible : collectibles) {
                    if (collectible.getBitmap() != null) {
                        canvas.drawBitmap(collectible.getBitmap(), collectible.getX(), collectible.getY(), null);
                    }
                }
            }

            // 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", 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++;

                    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 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(AchievementKeys.COOL_BIRD_UNLOCK, 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.getInstance().unlockBird(coolBirdFrames, AchievementKeys.COOL_BIRD_UNLOCK, activity);
            mainHandler.post(() -> Toast.makeText(activity, activity.getString(R.string.new_bird_unlocked), Toast.LENGTH_SHORT).show());
        }
    }

    /**
     * Handles updating the game's level progression.
     */
    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);
        synchronized (powerUps) {
            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);
                pipeManager.setGapSize(pipeManager.getCurrentGapSize()); // Trigger internal speed updates
                mainHandler.post(() -> Toast.makeText(activity, "Slow Motion Activated!", Toast.LENGTH_SHORT).show());
                mainHandler.postDelayed(() -> {
                    AppConstants.PIPE_SPEED *= 2;
                    AppConstants.BACKGROUND_SPEED *= 2;
                    pipeManager.setGapSize(pipeManager.getCurrentGapSize()); // Trigger internal speed updates
                }, 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;
        }
    }

    /**
     * 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).recycleAllBitmaps();
    }
}
package com.example.flappybirdgame;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
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;
    private Paint paint;

    /**
     * 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);

        paint = new Paint();

        if (original != null) {
            // 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);
            y = AppConstants.SCREEN_HEIGHT - ground.getHeight();
            AppConstants.GROUND_Y = y; // Initialize GROUND_Y
        } else {
            Log.e("Ground", "Ground bitmap is null. Using default ground color.");
            y = AppConstants.SCREEN_HEIGHT - (int) (AppConstants.SCREEN_HEIGHT * 0.2f);
        }
    }

    /**
     * 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
            paint.setColor(Color.GREEN);
            canvas.drawRect(0, y, AppConstants.SCREEN_WIDTH, AppConstants.GROUND_Y + (int) (AppConstants.SCREEN_HEIGHT * 0.2f), paint);
        }
    }

    /**
     * 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;

/**
 * Manages the progression of levels within the game.
 */
public class LevelManager {
    private ArrayList<Level> levels = new ArrayList<>();
    private int currentLevelIndex = 0;

    public LevelManager() {
        // Define levels with increasing difficulty
        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;
    }

    public int getCurrentLevelIndex() {
        return currentLevelIndex;
    }
}
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;

/**
 * The main activity that serves as the game's main menu.
 */
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);
        });
    }

    /**
     * Checks if the player is eligible for a daily reward and grants it if so.
     */
    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();
        }
    }

    /**
     * Determines if two timestamps are on the same day.
     *
     * @param time1 First timestamp in milliseconds.
     * @param time2 Second timestamp in milliseconds.
     * @return True if both timestamps are on the same day, false otherwise.
     */
    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.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;

import java.util.Random;

/**
 * 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 = Math.max((int) (AppConstants.SCREEN_WIDTH * 0.08f), 60);
            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.");
            width = 80; // Default width
            height = 300; // Default height
            pipeTop = null;
            pipeBottom = null;
            x = AppConstants.SCREEN_WIDTH;
            y = (int) (screenHeight * 0.3f);
        }
    }

    /**
     * Generates a random Y position for the top pipe ensuring it stays within screen bounds.
     *
     * @param screenHeight The height of the screen.
     * @return A valid Y position for the top pipe.
     */
    protected int getRandomYPosition(int screenHeight) {
        // Define minimum and maximum heights for the top pipe
        int minTopPipeHeight = (int) (screenHeight * 0.15f); // Reduced to allow more variation
        int maxTopPipeHeight = AppConstants.GROUND_Y - gap - pipeBottom.getHeight() - (int) (screenHeight * 0.15f); // Allowing more variability

        if (maxTopPipeHeight < minTopPipeHeight) {
            maxTopPipeHeight = minTopPipeHeight;
        }

        Random random = new Random();
        int topPipeHeight = minTopPipeHeight + random.nextInt(Math.max(maxTopPipeHeight - minTopPipeHeight, 1));

        // y should be set so that top pipe has height topPipeHeight
        int yPosition = AppConstants.GROUND_Y - gap - pipeBottom.getHeight() - topPipeHeight;

        return yPosition;
    }

    /**
     * Sets the gap size between the top and bottom pipes.
     *
     * @param gapSize        The new gap size in pixels.
     * @param lastGapYPosition The Y position of the last gap to prevent similarity.
     */
    public void setGapSize(int gapSize, int lastGapYPosition) {
        this.gap = gapSize;
        // Recalculate Y position based on new gap and lastGapYPosition
        y = getRandomYPosition(AppConstants.GROUND_Y + (int)(AppConstants.SCREEN_HEIGHT * 0.2f), lastGapYPosition);
    }

    /**
     * Sets the pipe's horizontal speed.
     *
     * @param speed The new speed in pixels per update.
     */
    public void setPipeSpeed(int speed) {
        this.pipeSpeed = speed;
    }

    /**
     * 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 {
            // Fallback: Draw rectangles if pipe images not available
            Paint paint = new Paint();
            paint.setColor(Color.GREEN);
            canvas.drawRect(x, y, x + width, y + height, paint); // Top Pipe
            canvas.drawRect(x, y + height + gap, x + width, y + height + gap + height, paint); // Bottom Pipe
        }
    }

    // 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 + height);
    }

    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, AppConstants.GROUND_Y / 2); // Assuming lastGapYPosition is half the screen
        this.scored = false;
    }

    /**
     * Overloaded method to set gap size without considering lastGapYPosition.
     * Useful during initial creation.
     *
     * @param gapSize The new gap size in pixels.
     */
    public void setGapSize(int gapSize) {
        this.gap = gapSize;
        // Recalculate Y position based on new gap without considering lastGapYPosition
        y = getRandomYPosition(AppConstants.SCREEN_HEIGHT, AppConstants.GROUND_Y / 2);
    }

    /**
     * Generates a random Y position with consideration to prevent similarity with the last gap position.
     *
     * @param screenHeight     The height of the screen.
     * @param lastGapYPosition The Y position of the last gap.
     * @return A valid Y position for the top pipe.
     */
    protected int getRandomYPosition(int screenHeight, int lastGapYPosition) {
        // Define minimum and maximum heights for the top pipe
        int minTopPipeHeight = (int) (screenHeight * 0.15f); // Reduced to allow more variation
        int maxTopPipeHeight = AppConstants.GROUND_Y - gap - pipeBottom.getHeight() - (int) (screenHeight * 0.15f); // Allowing more variability

        if (maxTopPipeHeight < minTopPipeHeight) {
            maxTopPipeHeight = minTopPipeHeight;
        }

        Random random = new Random();
        int topPipeHeight = minTopPipeHeight + random.nextInt(Math.max(maxTopPipeHeight - minTopPipeHeight, 1));

        // y should be set so that top pipe has height topPipeHeight
        int yPosition = AppConstants.GROUND_Y - gap - pipeBottom.getHeight() - topPipeHeight;

        // Ensure that the new gap position is not too close to the last one
        int currentGapY = AppConstants.GROUND_Y - gap - pipeBottom.getHeight() - topPipeHeight;
        if (Math.abs(currentGapY - lastGapYPosition) < (int)(screenHeight * 0.05f)) {
            // If too similar, adjust by a small random offset
            yPosition += (random.nextBoolean() ? 50 : -50);
            // Ensure yPosition remains within bounds
            yPosition = Math.max((int)(screenHeight * 0.05f), Math.min(yPosition, AppConstants.GROUND_Y - gap - pipeBottom.getHeight() - (int)(screenHeight * 0.05f)));
        }

        return yPosition;
    }
}
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;
import java.util.Random;

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

    private final List<Pipe> pipes;          // List of pipes
    private final Object lock = new Object(); // Synchronization lock
    private final LevelManager levelManager;  // Reference to LevelManager
    private int pipeSpacing;                  // Spacing between pipes in pixels
    private int gapSize;                      // Current gap size between pipes in pixels
    private boolean isBossLevel = false;      // Flag to indicate boss level

    private static final int MIN_PIPE_SPACING = 300; // Minimum spacing to prevent overlapping

    private int lastGapYPosition = AppConstants.GROUND_Y / 2; // Initialize to center

    /**
     * Constructor for PipeManager.
     *
     * @param levelManager The LevelManager instance to manage level-related attributes.
     */
    public PipeManager(LevelManager levelManager) {
        this.pipes = new ArrayList<>();
        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.gapSize = levelManager.getCurrentLevel().getGapSize();
        // Adjust pipeSpacing based on screen width for better distribution
        this.pipeSpacing = Math.max(this.gapSize, MIN_PIPE_SPACING * 2);
        initializePipes();
    }

    /**
     * Initializes the initial pipes ensuring some are visible on-screen.
     */
    private void initializePipes() {
        synchronized (lock) {
            pipes.clear();

            // Calculate the number of pipes needed to cover the screen with current spacing
            int numberOfPipes = (int) Math.ceil(AppConstants.SCREEN_WIDTH / (double) pipeSpacing) + 2;

            for (int i = 0; i < numberOfPipes; i++) {
                Pipe pipe = createPipe();
                // Position pipes evenly with some already visible
                int pipeX = AppConstants.SCREEN_WIDTH + i * pipeSpacing - (int)(pipeSpacing * 0.5f);
                pipe.resetPosition(pipeX, gapSize);
                pipes.add(pipe);

                // Update lastGapYPosition based on the first alternate pipe
                if (i == 0) {
                    lastGapYPosition = AppConstants.GROUND_Y - gapSize - pipe.pipeBottom.getHeight() - pipe.height;
                }
            }
        }
    }

    /**
     * Creates a new Pipe instance based on whether it's a boss level.
     *
     * @return A new Pipe or BossPipe instance.
     */
    private Pipe createPipe() {
        if (isBossLevel) {
            return new BossPipe(AppConstants.SCREEN_HEIGHT, gapSize);
        } else {
            // Randomize gap size within a specified range
            int minGap = (int) (AppConstants.SCREEN_HEIGHT * 0.15f);
            int maxGap = (int) (AppConstants.SCREEN_HEIGHT * 0.25f);
            int randomizedGap = minGap + new Random().nextInt(maxGap - minGap + 1);

            Pipe newPipe = new Pipe(AppConstants.SCREEN_HEIGHT, randomizedGap);
            newPipe.setGapSize(randomizedGap, lastGapYPosition);

            // Update lastGapYPosition
            lastGapYPosition = AppConstants.GROUND_Y - randomizedGap - newPipe.pipeBottom.getHeight() - newPipe.height;

            return newPipe;
        }
    }

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

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

                if (pipe.isOffScreen()) {
                    iterator.remove();
                    Pipe newPipe = createPipe();
                    // Position new_pipe spaced after the last pipe
                    newPipe.resetPosition(getLastPipeX() + pipeSpacing, gapSize);
                    pipesToAdd.add(newPipe);
                }
            }

            // Add all new pipes after 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) {
        boolean scoredPipe = false;
        synchronized (lock) {
            for (Pipe pipe : pipes) {
                if (!pipe.isScored() && pipe.getX() + pipe.getWidth() < bird.getX()) {
                    pipe.setScored(true); // Mark the pipe as scored
                    scoredPipe = true;
                }
            }
        }
        return scoredPipe;
    }

    /**
     * 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) {
            gapSize -= amount;
            if (gapSize < (int) (AppConstants.SCREEN_HEIGHT * 0.15)) {
                gapSize = (int) (AppConstants.SCREEN_HEIGHT * 0.15f); // Minimum gap size
            }
            for (Pipe pipe : pipes) {
                pipe.setGapSize(gapSize, lastGapYPosition);
                // Update lastGapYPosition for the next pipe
                lastGapYPosition = AppConstants.GROUND_Y - gapSize - pipe.pipeBottom.getHeight() - pipe.height;
            }
        }
    }

    /**
     * Sets the gap size between pipes, used when changing levels.
     *
     * @param newGapSize The new gap size in pixels.
     */
    public void setGapSize(int newGapSize) {
        synchronized (lock) {
            this.gapSize = newGapSize;
            for (Pipe pipe : pipes) {
                pipe.setGapSize(newGapSize, lastGapYPosition);
                // Update lastGapYPosition for the next pipe
                lastGapYPosition = AppConstants.GROUND_Y - newGapSize - pipe.pipeBottom.getHeight() - pipe.height;
            }
        }
    }

    /**
     * Gets the current gap size.
     *
     * @return The current gap size in pixels.
     */
    public int getCurrentGapSize() {
        synchronized (lock) {
            return gapSize;
        }
    }

    /**
     * Retrieves a copy of the current pipes list.
     *
     * @return A copy of the pipes list.
     */
    public List<Pipe> getPipesCopy() {
        synchronized (lock) {
            return new ArrayList<>(pipes);
        }
    }

    /**
     * Handles level changes by updating settings and reinitializing pipes.
     */
    public void onLevelChange() {
        synchronized (lock) {
            isBossLevel = levelManager.isLastLevel();
            gapSize = levelManager.getCurrentLevel().getGapSize();
            // Adjust pipeSpacing based on screen width for better distribution
            this.pipeSpacing = Math.max(this.gapSize, MIN_PIPE_SPACING * 2);
            initializePipes();
        }
    }

    /**
     * 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;
            }
        }
    }
}
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 long activationTime; // Timestamp when the power-up was activated
    private boolean isActive = false;

    /**
     * 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 with a minimum size
            int desiredWidth = Math.max((int) (AppConstants.SCREEN_WIDTH * 0.06f), 40);
            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 + new 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;
    }

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

    /**
     * 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;
    }
}
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.LruCache;
import android.util.Log;

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

    /**
     * Private constructor to enforce singleton pattern.
     *
     * @param context The application context.
     */
    private ResourceManager(Context context) {
        this.context = context.getApplicationContext();

        // Calculate the maximum available memory for caching (1/8th of total memory)
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        final int cacheSize = maxMemory / 8;

        // Initialize LruCache for bitmap caching
        this.bitmapCache = new LruCache<Integer, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(Integer key, Bitmap value) {
                // The cache size will be measured in kilobytes
                return value.getByteCount() / 1024;
            }
        };
    }

    /**
     * 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 bitmap from the cache or decodes it from resources if not present.
     *
     * @param resourceId The drawable resource ID.
     * @return The bitmap associated with the resource ID, or null if decoding fails.
     */
    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 and cached bitmap: " + getResourceName(resourceId));
            } else {
                Log.e("ResourceManager", "Failed to load bitmap: " + getResourceName(resourceId));
            }
        }
        return bitmap;
    }

    /**
     * Recycles a specific bitmap and removes it from the cache.
     *
     * @param resourceId The drawable resource ID of the bitmap to recycle.
     */
    public void recycleBitmap(int resourceId) {
        Bitmap bitmap = bitmapCache.get(resourceId);
        if (bitmap != null && !bitmap.isRecycled()) {
            bitmap.recycle();
            bitmapCache.remove(resourceId);
            Log.d("ResourceManager", "Recycled bitmap: " + getResourceName(resourceId));
        }
    }

    /**
     * Recycles all cached bitmaps, freeing up memory.
     */
    public void recycleAllBitmaps() {
        bitmapCache.evictAll();
        Log.d("ResourceManager", "Recycled all cached bitmaps.");
    }

    /**
     * Retrieves the resource name based on its ID for logging purposes.
     *
     * @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="wrap_content"
    android:padding="16dp">

    <!-- Achievement Status Icon -->
    <ImageView
        android:id="@+id/achievementStatus"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:contentDescription="@string/achievement_status"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <!-- Achievement Name -->
    <TextView
        android:id="@+id/achievementName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Achievement Name"
        android:textSize="20sp"
        android:textStyle="bold"
        android:fontFamily="@font/poppins_bold"
        app:layout_constraintTop_toTopOf="@id/achievementStatus"
        app:layout_constraintStart_toEndOf="@id/achievementStatus"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginStart="16dp"/>

    <!-- Achievement Description -->
    <TextView
        android:id="@+id/achievementDescription"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Achievement Description"
        android:textSize="16sp"
        android:fontFamily="@font/poppins_regular"
        app:layout_constraintTop_toBottomOf="@id/achievementName"
        app:layout_constraintStart_toStartOf="@id/achievementName"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="4dp"/>

</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"
    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"/>

    <!-- RecyclerView for Achievements -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/achievementsRecyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="16dp"
        android:scrollbars="vertical"
        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"?>
<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">

    <!-- 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_marginTop="16dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <!-- RecyclerView for Birds -->
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/birdRecyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_margin="16dp"
        android:scrollbars="vertical"
        app:layout_constraintTop_toBottomOf="@id/characterSelectionTitle"
        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"?>
<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"?>
<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="wrap_content"
    android:layout_height="wrap_content"
    android:padding="8dp">

    <!-- Bird Preview Image -->
    <ImageView
        android:id="@+id/birdPreviewImageView"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:scaleType="fitCenter"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>

    <!-- Lock Icon (Overlay) -->
    <ImageView
        android:id="@+id/birdLockIcon"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:src="@drawable/ic_lock"
        android:contentDescription="@string/lock_icon"
        app:layout_constraintTop_toTopOf="@id/birdPreviewImageView"
        app:layout_constraintEnd_toEndOf="@id/birdPreviewImageView"
        android:visibility="gone"
        app:tint="@android:color/holo_red_dark" />

</androidx.constraintlayout.widget.ConstraintLayout>
<?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>