import cv2
import gradio as gr
import mediapipe as mp
import numpy as np
import random
import time

mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_hands = mp.solutions.hands

# Load and resize fruit images
fruits = ['apple', 'orange', 'pineapple', 'banana', 'watermelon']
fruit_images = {}
splash_image = cv2.imread('splash.png', cv2.IMREAD_UNCHANGED)  # Load splash image
splash_image = cv2.resize(splash_image, (100, 100))  # Resize splash image

# Load button images
start_button_img = cv2.imread('start_button.png', cv2.IMREAD_UNCHANGED)
start_button_img = cv2.resize(start_button_img, (150, 80))
retry_button_img = cv2.imread('retry_button.png', cv2.IMREAD_UNCHANGED)
retry_button_img = cv2.resize(retry_button_img, (150, 80))


for fruit in fruits:
    img = cv2.imread(f'{fruit}.png', cv2.IMREAD_UNCHANGED)
    if img.shape[2] == 3:  # If image doesn't have an alpha channel
        img = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA)
    fruit_images[fruit] = cv2.resize(img, (100, 100))


class Fruit:
    def __init__(self, name):
        self.name = name
        self.x = random.randint(50, 590)
        self.y = img.shape[0] + 50  # Start below the screen
        self.initial_speed_y = random.uniform(-20, -10)  # Initial upward speed
        self.speed_y = self.initial_speed_y
        self.speed_x = random.uniform(-3, 3)
        self.rotation = random.uniform(0, 360)
        self.rotation_speed = random.uniform(-5, 5)
        self.sliced = False
        self.slice_time = 0
        self.slice_direction = 0

    def move(self):
        self.y += self.speed_y
        self.x += self.speed_x
        self.rotation += self.rotation_speed
        if self.sliced:
            self.speed_y += 1  # Gravity effect on sliced fruits
        else:
            self.speed_y += 0.5  # Gravity effect on whole fruits

    def slice(self, direction):
        self.sliced = True
        self.slice_time = time.time()
        self.slice_direction = direction
        self.speed_y = -5  # Slight upward boost when sliced
        self.speed_x = random.uniform(-5, 5)  # Random horizontal direction


def spawn_fruit():
    return Fruit(random.choice(fruits))


def reset_game(game_state):
    game_state["score"] = 0
    game_state["missed_fruits"] = 0
    game_state["game_over"] = False
    game_state["game_started"] = False  # Add game_started flag
    game_state["active_fruits"] = []
    game_state["last_spawn_time"] = time.time()
    game_state["combo"] = 0
    game_state["last_slice_time"] = 0
    game_state["slice_effects"] = []
    return game_state


def start_game(game_state):
    game_state["game_started"] = True
    return game_state


def fun(img, game_state):
    if game_state["game_over"]:
        # Display game over screen with retry button
        img = overlay_image(img, retry_button_img, 250, 300)
        cv2.putText(img, 'GAME OVER', (200, 240),
                    cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 3)
        return img, game_state

    if not game_state["game_started"]:
        # Display start screen with start button
        img = overlay_image(img, start_button_img, 250, 300)
        cv2.putText(img, 'FRUIT NINJA', (180, 240),
                    cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 3)
        return img, game_state

    current_time = time.time()

    with mp_hands.Hands(
        model_complexity=0,  # Use 0 for faster processing
        min_detection_confidence=0.5,
        min_tracking_confidence=0.5
    ) as hands:
        imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # Convert to RGB
        results = hands.process(imgRGB)

        game_img = img.copy()  # Use original image colors

        # Spawn new fruits (limit to 3 fruits at a time)
        if current_time - game_state["last_spawn_time"] > random.uniform(1.5, 2.5) and len(game_state["active_fruits"]) < 3:
            game_state["active_fruits"].append(spawn_fruit())
            game_state["last_spawn_time"] = current_time

        # Move and draw fruits
        fruits_to_remove = []
        for fruit in game_state["active_fruits"]:
            fruit.move()
            if fruit.y > img.shape[0] + 50 and fruit.sliced:  # Remove sliced fruits below the screen after 1 second
                if current_time - fruit.slice_time > 1:
                    fruits_to_remove.append(fruit)
            elif fruit.y > img.shape[0] + 50:  # Remove whole fruits below the screen
                game_state["missed_fruits"] += 1
                if game_state["missed_fruits"] >= 5:
                    game_state["game_over"] = True
                fruits_to_remove.append(fruit)
            else:
                fruit_img = fruit_images[fruit.name]
                rotated_fruit = rotate_image(
                    fruit_img, fruit.rotation)

                if fruit.sliced:
                    # Split the fruit image into two halves
                    half_height = rotated_fruit.shape[0] // 2
                    top_half = rotated_fruit[:half_height, :]
                    bottom_half = rotated_fruit[half_height:, :]

                    # Apply a slight offset to create a splitting effect
                    offset = int((current_time -
                                  fruit.slice_time) * 75)  # Increased offset speed
                    game_img = overlay_image(
                        game_img, top_half, int(fruit.x) - offset, int(fruit.y) - offset)
                    game_img = overlay_image(
                        game_img, bottom_half, int(fruit.x) + offset, int(fruit.y) + offset)

                    # Draw splash effect
                    if current_time - fruit.slice_time < 0.7:  # Show splash for a short duration
                        game_img = overlay_image(
                            game_img, splash_image, int(fruit.x) - 50, int(fruit.y) - 50)

                else:
                    game_img = overlay_image(
                        game_img, rotated_fruit, int(fruit.x), int(fruit.y))

        for fruit in fruits_to_remove:
            game_state["active_fruits"].remove(fruit)

        # Hand tracking and slicing
        if results.multi_hand_landmarks:
            for hand_landmarks in results.multi_hand_landmarks:
                # Get index finger MCP and tip coordinates for the line
                index_mcp = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_MCP]
                index_tip = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]

                index_mcp_x, index_mcp_y = int(index_mcp.x * game_img.shape[1]), int(index_mcp.y * game_img.shape[0])
                index_tip_x, index_tip_y = int(index_tip.x * game_img.shape[1]), int(index_tip.y * game_img.shape[0])

                # Draw the line (make it thicker and brighter)
                cv2.line(game_img, (index_mcp_x, index_mcp_y), (index_tip_x, index_tip_y), (0, 255, 0), 4)

                # Draw hand landmarks and connections
                mp_drawing.draw_landmarks(
                    game_img,
                    hand_landmarks,
                    mp_hands.HAND_CONNECTIONS,
                    mp_drawing_styles.get_default_hand_landmarks_style(),
                    mp_drawing_styles.get_default_hand_connections_style())

                # Check for collisions with fruits - Increased collision radius
                for fruit in game_state["active_fruits"]:
                    if not fruit.sliced and abs(index_tip_x -
                                                fruit.x) < 70 and abs(index_tip_y - fruit.y) < 70:
                        fruit.slice(random.uniform(0, 360))
                        game_state["score"] += 1
                        if current_time - game_state["last_slice_time"] < 1.1:
                            game_state["combo"] += 1
                        else:
                            game_state["combo"] = 1
                        game_state["last_slice_time"] = current_time
                        game_state["slice_effects"].append(
                            (index_tip_x, index_tip_y, current_time))

                # Check for button clicks (using the line from wrist to index fingertip)
                if not game_state["game_started"]:
                    if 250 < index_tip_x < 400 and 300 < index_tip_y < 380:
                        game_state = start_game(game_state)
                elif game_state["game_over"]:
                    if 250 < index_tip_x < 400 and 300 < index_tip_y < 380:
                        game_state = reset_game(game_state)

        # Draw slice effects
        game_state["slice_effects"] = [(x, y, t) for x, y,
                                      t in game_state["slice_effects"] if current_time - t < 0.5]
        for x, y, t in game_state["slice_effects"]:
            cv2.circle(game_img, (x, y), int((current_time - t) * 50),
                       (255, 255, 255), 2)

        # Display score, combo, and missed fruits
        cv2.putText(game_img, f'Score: {game_state["score"]}', (10, 30),
                    cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
        cv2.putText(game_img, f'Missed: {game_state["missed_fruits"]}/5',
                    (10, 70), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
        if game_state["combo"] > 1:
            cv2.putText(game_img, f'Combo: {game_state["combo"]}x',
                        (10, 110), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 2)

    return game_img, game_state


def overlay_image(background, foreground, x, y):
    bg_h, bg_w, _ = background.shape
    fg_h, fg_w, _ = foreground.shape

    x = max(0, min(x, bg_w - fg_w))
    y = max(0, min(y, bg_h - fg_h))

    if x >= bg_w or y >= bg_h:
        return background

    h = min(fg_h, bg_h - y)
    w = min(fg_w, bg_w - x)

    alpha_channel = foreground[:h, :w, 3] / 255.0
    for c in range(3):
        background[y:y+h, x:x+w, c] = (1 -
                                     alpha_channel) * background[y:y+h, x:x+w, c] + alpha_channel * foreground[:h, :w, c]

    return background


def rotate_image(image, angle):
    image_center = tuple(np.array(image.shape[1::-1]) / 2)
    rot_mat = cv2.getRotationMatrix2D(image_center, angle, 1.0)
    result = cv2.warpAffine(image, rot_mat, image.shape[1::-1],
                            flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_TRANSPARENT)
    return result


with gr.Blocks() as demo:
    game_state = gr.State({
        "score": 0,
        "missed_fruits": 0,
        "game_over": False,
        "game_started": False,  # Add game_started flag
        "active_fruits": [],
        "last_spawn_time": time.time(),
        "combo": 0,
        "last_slice_time": 0,
        "slice_effects": []
    })

    with gr.Row():
        with gr.Column():
            input_img = gr.Image(streaming=True, image_mode="RGB")

            input_img.stream(fun, [input_img, game_state],
                             [input_img, game_state], time_limit=120, stream_every=0.2, concurrency_limit=None, trigger_mode="multiple")

demo.launch()




Find the reson why its not showing any line on my hand and not able to click start button. Debug full code.