""" #File Name: sandpypi.py # Sandpypi by Stanton. # Project name is a placeholder. # This has been a multimonth or year project i have time blindness sorta. # This is my most functional system for falling sand in python yet i took some things i learned in JS. # This needs further optimizations to core performance sections. The main function to run the Sandpypi program. This function initializes the Pygame environment, creates the simulation and rendering objects, and enters the main event loop. It handles user input events such as mouse clicks, mouse wheel scrolling, and keyboard presses. It also updates the simulation, draws the particles, buttons, and other UI elements, and manages the settings menu. The main loop runs at a target frame rate of 60 FPS, with the actual frame rate displayed in the debug overlay. """ from settings import pygame, engine_settings from rendering import Rendering from sim import Simulation def main(): # Main function to run the program pygame.init() clock = pygame.time.Clock() width = 1024 height = 768 screen = pygame.display.set_mode((width, height), pygame.HWSURFACE | pygame.DOUBLEBUF) sim = Simulation(width, height) rendering = Rendering(width, height) mouse_down_left = False mouse_down_right = False mouse_down_middle = False mouse_down_wheel_up = False mouse_down_wheel_down = False over_button = False zoom_active = False zoom_locked = False zoom_pos = None settings_visible = False settings_menu_y = 100 running = True while running: fps = clock.get_fps() # Get the current frame rate dt = clock.tick(300) / 1000 # sets the target frame rate to 60 FPS keys = pygame.key.get_pressed() zoom_active = keys[pygame.K_z] # Handle events for event in pygame.event.get(): if event.type == pygame.MOUSEBUTTONDOWN: mouse_pos = pygame.mouse.get_pos() # Check if clicking in settings area when menu is visible in_settings_area = False if settings_visible: settings_rect = pygame.Rect(rendering.width - 320, 100, 300, 400) in_settings_area = settings_rect.collidepoint(mouse_pos) if event.button == 4: # Mouse wheel up sim.brush_size = min(sim.brush_size + 1, sim.max_brush_size) elif event.button == 5: # Mouse wheel down sim.brush_size = max(sim.brush_size - 1, 1) elif event.button == 1: # Left click over_button = False # Check settings button first if rendering.settings_button.collidepoint(mouse_pos): settings_visible = not settings_visible over_button = True if zoom_active: zoom_locked = not zoom_locked if zoom_locked: zoom_pos = mouse_pos # Handle settings menu interactions elif settings_visible and in_settings_area: relative_y = mouse_pos[1] - settings_menu_y setting_index = relative_y // 30 if 0 <= setting_index < len(engine_settings): setting_name = list(engine_settings.keys())[setting_index] engine_settings[setting_name] = not engine_settings[setting_name] over_button = True elif not in_settings_area: # Check category buttons for category, button in rendering.category_buttons.items(): if button.collidepoint(mouse_pos): rendering.current_category = category over_button = True break # Check particle buttons if not over_button: for particle_type, button in rendering.buttons.items(): if button.collidepoint(mouse_pos): sim.current_particle_type = particle_type over_button = True break # Check clear screen button if rendering.clear_screen_button.collidepoint(mouse_pos): rendering.clear_screen(sim) over_button = True if not over_button and not in_settings_area: mouse_down_left = True elif event.button == 3: # Right click mouse_down_right = True elif event.button == 2: # Middle click mouse_down_middle = True elif event.type == pygame.MOUSEBUTTONUP and event.button == 1: mouse_down_left = False elif event.type == pygame.MOUSEBUTTONUP and event.button == 3: mouse_down_right = False elif event.type == pygame.MOUSEBUTTONUP and event.button == 2: mouse_down_middle = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: print("Escape button pressed") print(f"Exiting Program {__file__}") running = False elif event.key == pygame.K_SPACE: print(f"Pause button pressed but not functional 'Space'") pass elif event.key == pygame.K_c: rendering.clear_screen(sim) elif event.key == pygame.K_z: zoom_active = not zoom_active if zoom_active: zoom_locked = False zoom_pos = pygame.mouse.get_pos() elif event.type == pygame.QUIT: running = False if mouse_down_left and not over_button: x, y = pygame.mouse.get_pos() # Check if current particle type is wind or air if sim.current_particle_type not in ['wind', 'air']: sim.create_particle_circle(x, y) else: # Handle wind/air differently sim.add_wind_zone(x, y) if mouse_down_right: x, y = pygame.mouse.get_pos() sim.clear_particles_circle(x, y) if mouse_down_middle: x, y = pygame.mouse.get_pos() sim.create_particle(x, y) if zoom_active or zoom_locked: mouse_pos = zoom_pos if zoom_locked else pygame.mouse.get_pos() zoom_surface = rendering.draw_zoom_window(sim.particles, sim.particle_size, rendering.particle_colors, mouse_pos) # Position zoom window zoom_x = 10 if mouse_pos[0] > width/2 else width - 110 zoom_y = 10 if mouse_pos[1] > height/2 else height - 110 screen.blit(zoom_surface, (zoom_x, zoom_y)) # Draw Settings if settings_visible: settings_menu = rendering.draw_settings_menu() rendering.screen.blit(settings_menu, (rendering.width - 320, 100)) rendering.draw_debug_overlay(fps, sim.particles) pygame.display.flip() # Update and draw particles sim.simulate_step(dt=0.016) # Draw particles rendering.draw_particles(sim.particles, sim.active_particles, sim.particle_size, rendering.particle_colors) # Draw buttons rendering.draw_buttons() # Draw brush size slider rendering.draw_brush_size_slider(sim.brush_size) # Get current mouse position mouse_x, mouse_y = pygame.mouse.get_pos() # Draw brush cursor at mouse position rendering.render_brush_cursor(mouse_x, mouse_y, sim.brush_size * sim.particle_size) pygame.quit() if __name__ == "__main__": main()