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']
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
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_BGR2BGRA)
    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["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 fun(img, game_state):
    if game_state["game_over"]:
        game_state = reset_game(game_state)

    current_time = time.time()

    with mp_hands.Hands(model_complexity=1, min_detection_confidence=0.4, min_tracking_confidence=0.4) as hands:
        img.flags.writeable = False
        results = hands.process(img)
        img.flags.writeable = True

        # 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

        # Create a copy of the original image
        game_img = img.copy()

        # 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.3:  # 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:
                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())

                # Get index finger tip position
                index_tip = hand_landmarks.landmark[mp_hands.HandLandmark.INDEX_FINGER_TIP]
                index_x, index_y = int(index_tip.x * game_img.shape[1]), int(index_tip.y * game_img.shape[0])

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

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

        if game_state["game_over"]:
            cv2.putText(game_img, 'GAME OVER', (200, 240), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 0, 255), 3)

    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,
        "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.3, concurrency_limit=None, trigger_mode="multiple")  

demo.launch()






Make it very very better. and also The images of fruit render by this is not of correct color.