123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
import tkinter as tk
import math
import numpy as np
from dataclasses import dataclass
from typing import List, Tuple
import random
import sys
# Constants
WINDOW_WIDTH = 800
WINDOW_HEIGHT = 800
GRAVITY = 9.8 * 30 # Scaled for better visual effect
GROUND_Y = WINDOW_HEIGHT - 50
WATERMELON_RADIUS = 40
TIME_STEP = 1/60 # 60 FPS
FRICTION = 0.95 # Friction coefficient for fragments
BOUNCE = 0.6 # Bounce coefficient
FRAGMENT_COUNT = 20 # Number of fragments when watermelon bursts
SEED_COUNT = 30 # Number of seeds to spawn on impact
SEED_LIFETIME = 1.5 # Seconds seeds remain visible
WATERMELON_MASS = 10.0 # Mass of watermelon
@dataclass
class Vector2D:
x: float
y: float
def __add__(self, other):
return Vector2D(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Vector2D(self.x - other.x, self.y - other.y)
def __mul__(self, scalar):
return Vector2D(self.x * scalar, self.y * scalar)
def length(self):
return math.sqrt(self.x**2 + self.y**2)
def normalize(self):
length = self.length()
if length > 0:
return Vector2D(self.x / length, self.y / length)
return Vector2D(0, 0)
class Fragment:
def __init__(self, position, velocity, size, color, rotation=0, rotation_speed=0):
self.position = position
self.velocity = velocity
self.size = size
self.color = color
self.rotation = rotation
self.rotation_speed = rotation_speed
self.id = None
self.active = True
def update(self):
# Apply gravity
self.velocity.y += GRAVITY * TIME_STEP
# Update position
self.position.x += self.velocity.x * TIME_STEP
self.position.y += self.velocity.y * TIME_STEP
# Update rotation
self.rotation += self.rotation_speed * TIME_STEP
# Check ground collision
if self.position.y + self.size > GROUND_Y:
self.position.y = GROUND_Y - self.size
self.velocity.y = -self.velocity.y * BOUNCE
self.velocity.x *= FRICTION
# Apply friction to rotation
self.rotation_speed *= FRICTION
# Check wall collisions
if self.position.x - self.size < 0:
self.position.x = self.size
self.velocity.x = -self.velocity.x * BOUNCE
elif self.position.x + self.size > WINDOW_WIDTH:
self.position.x = WINDOW_WIDTH - self.size
self.velocity.x = -self.velocity.x * BOUNCE
# Deactivate if almost stopped
if (abs(self.velocity.x) < 5 and abs(self.velocity.y) < 5 and
abs(self.rotation_speed) < 0.1 and self.position.y + self.size >= GROUND_Y - 1):
self.velocity.x = 0
self.velocity.y = 0
self.rotation_speed = 0
class Seed:
def __init__(self, position, velocity, size):
self.position = position
self.velocity = velocity
self.size = size
self.id = None
self.lifetime = SEED_LIFETIME
self.active = True
def update(self):
# Apply gravity (lighter than fragments)
self.velocity.y += GRAVITY * TIME_STEP * 0.5
# Update position
self.position.x += self.velocity.x * TIME_STEP
self.position.y += self.velocity.y * TIME_STEP
# Check ground collision
if self.position.y + self.size > GROUND_Y:
self.position.y = GROUND_Y - self.size
self.velocity.y = -self.velocity.y * 0.3 # Less bounce
self.velocity.x *= 0.8 # More friction
# Check wall collisions
if self.position.x - self.size < 0:
self.position.x = self.size
self.velocity.x = -self.velocity.x * 0.3
elif self.position.x + self.size > WINDOW_WIDTH:
self.position.x = WINDOW_WIDTH - self.size
self.velocity.x = -self.velocity.x * 0.3
# Reduce lifetime
self.lifetime -= TIME_STEP
if self.lifetime <= 0:
self.active = False
class Watermelon:
def __init__(self, x, y):
self.position = Vector2D(x, y)
self.velocity = Vector2D(0, 0)
self.radius = WATERMELON_RADIUS
self.mass = WATERMELON_MASS
self.burst = False
self.id_outer = None
self.id_inner = None
self.id_seeds = []
def update(self):
if not self.burst:
# Apply gravity
self.velocity.y += GRAVITY * TIME_STEP
# Update position
self.position.x += self.velocity.x * TIME_STEP
self.position.y += self.velocity.y * TIME_STEP
# Check ground collision
if self.position.y + self.radius > GROUND_Y:
self.burst = True
# Check wall collisions
if self.position.x - self.radius < 0:
self.position.x = self.radius
self.velocity.x = -self.velocity.x * BOUNCE
elif self.position.x + self.radius > WINDOW_WIDTH:
self.position.x = WINDOW_WIDTH - self.radius
self.velocity.x = -self.velocity.x * BOUNCE
class WatermelonSimulation:
def __init__(self, root):
self.root = root
self.root.title("Watermelon Splash Simulation")
self.root.geometry(f"{WINDOW_WIDTH}x{WINDOW_HEIGHT}")
self.canvas = tk.Canvas(root, width=WINDOW_WIDTH, height=WINDOW_HEIGHT, bg="skyblue")
self.canvas.pack()
# Create ground
self.ground = self.canvas.create_rectangle(0, GROUND_Y, WINDOW_WIDTH, WINDOW_HEIGHT, fill="sienna")
# Create watermelon
self.watermelon = Watermelon(WINDOW_WIDTH // 2, 100)
self.draw_watermelon()
# Lists for fragments and seeds
self.fragments = []
self.seeds = []
# Start simulation
self.is_running = True
self.update()
# Bind restart on click
self.canvas.bind("<Button-1>", self.restart)
def draw_watermelon(self):
x, y = self.watermelon.position.x, self.watermelon.position.y
r = self.watermelon.radius
# Draw outer green part
self.watermelon.id_outer = self.canvas.create_oval(
x - r, y - r, x + r, y + r,
fill="green", outline="darkgreen", width=2
)
# Draw inner red part (slightly smaller)
inner_r = r * 0.85
self.watermelon.id_inner = self.canvas.create_oval(
x - inner_r, y - inner_r, x + inner_r, y + inner_r,
fill="red", outline=""
)
# Draw some seed-like details
self.watermelon.id_seeds = []
for _ in range(8):
angle = random.uniform(0, 2 * math.pi)
distance = random.uniform(0.3, 0.7) * inner_r
seed_x = x + math.cos(angle) * distance
seed_y = y + math.sin(angle) * distance
seed_size = random.uniform(2, 4)
seed = self.canvas.create_oval(
seed_x - seed_size, seed_y - seed_size,
seed_x + seed_size, seed_y + seed_size,
fill="black", outline=""
)
self.watermelon.id_seeds.append(seed)
def burst_watermelon(self):
x, y = self.watermelon.position.x, self.watermelon.position.y
# Create fragments
for _ in range(FRAGMENT_COUNT):
# Random direction
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(100, 300)
# Calculate velocity based on watermelon's velocity and explosion
vel_x = self.watermelon.velocity.x + math.cos(angle) * speed
vel_y = self.watermelon.velocity.y + math.sin(angle) * speed
# Random size for fragment
size = random.uniform(self.watermelon.radius * 0.2, self.watermelon.radius * 0.4)
# Random color (green or red)
color = random.choice(["green", "red"])
# Random rotation
rotation = random.uniform(0, 360)
rotation_speed = random.uniform(-180, 180)
# Create fragment at watermelon's position
fragment = Fragment(
Vector2D(x, y),
Vector2D(vel_x, vel_y),
size,
color,
rotation,
rotation_speed
)
# Draw the fragment
fragment.id = self.canvas.create_oval(
x - size, y - size, x + size, y + size,
fill=color, outline="darkgreen" if color == "green" else "darkred"
)
self.fragments.append(fragment)
# Create seeds
for _ in range(SEED_COUNT):
# Random direction
angle = random.uniform(0, 2 * math.pi)
speed = random.uniform(50, 200)
# Calculate velocity
vel_x = math.cos(angle) * speed
vel_y = math.sin(angle) * speed
# Create seed
seed = Seed(
Vector2D(x, y),
Vector2D(vel_x, vel_y),
random.uniform(1.5, 3)
)
# Draw the seed
seed.id = self.canvas.create_oval(
x - seed.size, y - seed.size, x + seed.size, y + seed.size,
fill="black", outline=""
)
self.seeds.append(seed)
def update(self):
if not self.is_running:
return
# Update watermelon
self.watermelon.update()
if self.watermelon.burst:
# If just burst, create fragments
if not self.fragments:
self.burst_watermelon()
# Remove original watermelon
self.canvas.delete(self.watermelon.id_outer)
self.canvas.delete(self.watermelon.id_inner)
for seed in self.watermelon.id_seeds:
self.canvas.delete(seed)
else:
# Update watermelon position on canvas
x, y = self.watermelon.position.x, self.watermelon.position.y
r = self.watermelon.radius
self.canvas.coords(
self.watermelon.id_outer,
x - r, y - r, x + r, y + r
)
self.canvas.coords(
self.watermelon.id_inner,
x - r * 0.85, y - r * 0.85, x + r * 0.85, y + r * 0.85
)
# Update seed positions
for i, seed_id in enumerate(self.watermelon.id_seeds):
angle = (i / len(self.watermelon.id_seeds)) * 2 * math.pi
distance = self.watermelon.radius * 0.6
seed_x = x + math.cos(angle) * distance
seed_y = y + math.sin(angle) * distance
seed_size = 3
self.canvas.coords(
seed_id,
seed_x - seed_size, seed_y - seed_size,
seed_x + seed_size, seed_y + seed_size
)
# Update fragments
for fragment in self.fragments[:]:
fragment.update()
if fragment.active:
# Update fragment position on canvas
x, y = fragment.position.x, fragment.position.y
# For simplicity, we're using ovals for fragments
# In a more complex simulation, we could use polygons with rotation
self.canvas.coords(
fragment.id,
x - fragment.size, y - fragment.size,
x + fragment.size, y + fragment.size
)
else:
# Remove inactive fragment
self.canvas.delete(fragment.id)
self.fragments.remove(fragment)
# Update seeds
for seed in self.seeds[:]:
seed.update()
if seed.active:
# Update seed position on canvas
x, y = seed.position.x, seed.position.y
self.canvas.coords(
seed.id,
x - seed.size, y - seed.size,
x + seed.size, y + seed.size
)
# Fade out based on lifetime
alpha = min(1.0, seed.lifetime / SEED_LIFETIME)
# We can't do true alpha with tkinter, but we can adjust color intensity
intensity = int(255 * alpha)
color = f"#{intensity:02x}{intensity:02x}{intensity:02x}"
self.canvas.itemconfig(seed.id, fill=color)
else:
# Remove inactive seed
self.canvas.delete(seed.id)
self.seeds.remove(seed)
# Continue simulation
self.root.after(int(TIME_STEP * 1000), self.update)
def restart(self, event=None):
# Clear all objects
for fragment in self.fragments:
self.canvas.delete(fragment.id)
for seed in self.seeds:
self.canvas.delete(seed.id)
self.fragments = []
self.seeds = []
# Reset watermelon
self.watermelon = Watermelon(WINDOW_WIDTH // 2, 100)
self.draw_watermelon()
def main():
root = tk.Tk()
app = WatermelonSimulation(root)
root.mainloop()
if __name__ == "__main__":
main()