fruitcut / streamlit_app.py
KRISH09bha's picture
Upload streamlit_app.py
30017e6 verified
import cv2
import streamlit as st
import numpy as np
import time
import random
import mediapipe as mp
import math
# Declare all global variables upfront
global last_spawn_time
last_spawn_time = 0 # Initialize the global variable before any other usage
@st.cache_data
def load_image(path):
img = cv2.imread(path, cv2.IMREAD_UNCHANGED)
if img is None:
raise FileNotFoundError(f"Image not found or invalid at path: {path}")
return img # Keep in BGRA format
# Load images
apple_img = load_image("apple.png")
banana_img = load_image("banana.png")
watermelon_img = load_image("watermelon.png")
bomb_img = load_image("bomb.png")
# Define fruit properties
fruit_map = {
"apple": {"img": apple_img, "points": 100, "size": 40, "color": (0, 0, 255)},
"banana": {"img": banana_img, "points": 150, "size": 50, "color": (0, 255, 255)},
"watermelon": {"img": watermelon_img, "points": 200, "size": 70, "color": (0, 255, 0)},
}
# Constants and Variables
SLASH_LENGTH = 15
LIVES = 3
SPAWN_INTERVAL = 1.2
fruits, splashes, explosions = [], [], []
score, lives = 0, LIVES
last_slice_time = 0
slash_points = []
slash_color = (255, 255, 255)
combo_count, combo_multiplier = 0, 1
combo_time_limit = 2
combo_message = ""
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=1,
model_complexity=1,
min_detection_confidence=0.5,
)
def overlay_image_alpha(background, overlay, pos, overlay_size):
x, y = pos
overlay = cv2.resize(overlay, overlay_size)
# Ensure the overlay image has an alpha channel
if overlay.shape[2] != 4:
raise ValueError(f"Overlay image must have 4 channels but has {overlay.shape[2]}!")
b, g, r, a = cv2.split(overlay)
overlay_rgb = cv2.merge((b, g, r))
mask = a / 255.0 # Normalize alpha channel
h, w = overlay.shape[:2]
# Validate dimensions to avoid out-of-bounds errors
if x < 0 or y < 0 or y + h > background.shape[0] or x + w > background.shape[1]:
# Do not overlay if it goes out of bounds
return
roi = background[y:y + h, x:x + w]
for c in range(3):
roi[:, :, c] = (1.0 - mask) * roi[:, :, c] + mask * overlay_rgb[:, :, c]
background[y:y + h, x:x + w] = roi
def distance(a, b):
return int(math.hypot(a[0] - b[0], a[1] - b[1]))
def spawn_fruit():
x = random.randint(50, 600)
is_bomb = random.random() < 0.2
fruit = {
"pos": [x, 600],
"vel": [random.randint(-4, 4), -random.randint(13, 18)],
"size": 40,
"bomb": is_bomb,
"img": bomb_img if is_bomb else None,
"points": 0,
"color": (255, 255, 255),
}
if not is_bomb:
kind = random.choice(list(fruit_map.keys()))
f = fruit_map[kind]
fruit.update({"img": f["img"], "points": f["points"], "size": f["size"], "color": f["color"], "kind": kind})
else:
fruit["kind"] = "bomb"
fruits.append(fruit)
def move_fruits():
global lives
for fruit in fruits[:]:
fruit["vel"][1] += 0.5
fruit["pos"][0] += fruit["vel"][0]
fruit["pos"][1] += fruit["vel"][1]
if fruit["pos"][1] > 700:
if not fruit["bomb"]:
lives -= 1
fruits.remove(fruit)
def add_splash(pos, color):
for _ in range(10):
splashes.append({
"pos": [pos[0] + random.randint(-10, 10), pos[1] + random.randint(-10, 10)],
"vel": [random.uniform(-3, 3), random.uniform(-3, 3)],
"radius": random.randint(3, 6),
"color": color,
"life": 15,
})
def add_explosion(pos):
for _ in range(30):
explosions.append({
"pos": [pos[0], pos[1]],
"vel": [random.uniform(-5, 5), random.uniform(-5, 5)],
"radius": random.randint(5, 10),
"color": (255, 255, 255),
"life": 20,
})
def update_effects(img):
for s in splashes[:]:
s["pos"][0] += s["vel"][0]
s["pos"][1] += s["vel"][1]
s["life"] -= 1
alpha = s["life"] / 15
cv2.circle(img, (int(s["pos"][0]), int(s["pos"][1])), s["radius"], tuple(int(c * alpha) for c in s["color"]),
-1)
if s["life"] <= 0:
splashes.remove(s)
for e in explosions[:]:
e["pos"][0] += e["vel"][0]
e["pos"][1] += e["vel"][1]
e["life"] -= 1
alpha = e["life"] / 20
cv2.circle(img, (int(e["pos"][0]), int(e["pos"][1])), e["radius"], tuple(int(c * alpha) for c in e["color"]),
-1)
if e["life"] <= 0:
explosions.remove(e)
def check_slices(index_pos):
global score, lives, slash_color, combo_count, combo_multiplier, combo_message, last_slice_time
combo_scored = False
for fruit in fruits[:]:
d = distance(index_pos, fruit["pos"])
if d < fruit["size"]:
if fruit["bomb"]:
lives -= 1
add_explosion(fruit["pos"])
fruits.clear()
splashes.clear()
return
else:
score += fruit["points"] * combo_multiplier
add_splash(fruit["pos"], fruit["color"])
fruits.remove(fruit)
combo_scored = True
if combo_scored:
if time.time() - last_slice_time <= combo_time_limit:
combo_count += 1
combo_multiplier = 1 + (combo_count // 2)
combo_message = f"{combo_multiplier}x Combo!"
else:
combo_count = 1
combo_multiplier = 1
combo_message = ""
last_slice_time = time.time()
def draw_fruits(img):
for fruit in fruits:
if fruit.get("img") is None or fruit.get("size") is None:
continue # Skip if image or size is invalid
x, y = int(fruit["pos"][0]), int(fruit["pos"][1])
size = fruit["size"] * 2
overlay_image_alpha(img, fruit["img"], (x - size // 2, y - size // 2), (size, size))
def draw_slash(img, index_pos):
global slash_points
slash_points.append(index_pos)
if len(slash_points) > SLASH_LENGTH:
slash_points.pop(0)
if len(slash_points) > 1:
pts = np.array(slash_points, np.int32).reshape((-1, 1, 2))
cv2.polylines(img, [pts], False, slash_color, 15)
def draw_ui(img, fps):
h, w = img.shape[:2]
cv2.putText(img, f"Score: {score}", (10, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 2)
cv2.putText(img, f"Lives: {lives}", (10, 90), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
cv2.putText(img, f"FPS: {int(fps)}", (w - 120, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)
if combo_message:
cv2.putText(img, combo_message, (w // 3, 90), cv2.FONT_HERSHEY_SIMPLEX, 1.5, (0, 255, 0), 3)
# Streamlit UI
st.title("Fruit Ninja in Streamlit πŸ‰")
run_game = st.button("Start Game")
frame_placeholder = st.empty()
if run_game:
cap = cv2.VideoCapture(0)
prev_time = time.time()
last_spawn_time = time.time() # Global variable used here
while cap.isOpened() and lives > 0:
ret, frame = cap.read()
if not ret:
break
frame = cv2.flip(frame, 1)
frame = cv2.convertScaleAbs(frame, alpha=1.2, beta=30) # Improve brightness and contrast
h, w, _ = frame.shape
img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = hands.process(img_rgb)
index_pos = None
if results.multi_hand_landmarks:
lm = results.multi_hand_landmarks[0].landmark[8]
index_pos = (int(lm.x * w), int(lm.y * h))
check_slices(index_pos)
draw_slash(frame, index_pos)
# Spawn fruit
if time.time() - last_spawn_time > SPAWN_INTERVAL:
spawn_fruit()
last_spawn_time = time.time()
move_fruits()
draw_fruits(frame)
update_effects(frame)
curr_time = time.time()
fps = 1 / (curr_time - prev_time)
prev_time = curr_time
draw_ui(frame, fps)
frame_placeholder.image(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB), channels="RGB")
cap.release()
st.write("Game Over! Your score:", score)