""" #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. """ import cProfile import pstats from settings import pygame, engine_settings from rendering import Rendering """ This is for the future physics engine until i figure out a better method used for testing right now. #import os #import sys #sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) """ from sim import Simulation def update_simulation(sim, dt, engine_settings): """Update simulation state""" sim.simulate_step(dt, engine_settings) def render_frame(rendering, sim, mouse_pos): """Render all visual elements""" # Draw particles rendering.draw_particles(sim.particles, sim.active_particles, sim.particle_size, rendering.particle_colors) # Draw UI elements rendering.draw_buttons() rendering.draw_brush_size_slider(sim.brush_size) # Draw brush cursor mouse_x, mouse_y = mouse_pos rendering.render_brush_cursor(mouse_x, mouse_y, sim.brush_size * sim.particle_size) def handle_input(event, sim, rendering, settings_visible, zoom_active, zoom_locked, zoom_pos): """Handle all input events""" if event.type == pygame.MOUSEBUTTONDOWN: return handle_mouse_down(event, sim, rendering, settings_visible, zoom_active) elif event.type == pygame.MOUSEBUTTONUP: return handle_mouse_up(event) elif event.type == pygame.KEYDOWN: return handle_key_press(event, rendering, sim) return None def handle_mouse_down(event, sim, rendering, settings_visible, zoom_active): """Handle mouse button down events""" mouse_pos = pygame.mouse.get_pos() 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 return handle_left_click(mouse_pos, sim, rendering, settings_visible, in_settings_area, zoom_active) elif event.button == 3: # Right click return {'mouse_down_right': True} elif event.button == 2: # Middle click return {'mouse_down_middle': True} return {} def handle_left_click(mouse_pos, sim, rendering, settings_visible, in_settings_area, zoom_active): """Handle left click interactions""" result = {'mouse_down_left': False, 'settings_visible': settings_visible, 'over_button': False} if rendering.settings_button.collidepoint(mouse_pos): result['settings_visible'] = not settings_visible result['over_button'] = True return result if zoom_active: result['zoom_locked'] = not result.get('zoom_locked', False) if result['zoom_locked']: result['zoom_pos'] = mouse_pos return result if settings_visible and in_settings_area: handle_settings_click(mouse_pos, sim) result['over_button'] = True return result if not in_settings_area: if handle_ui_click(mouse_pos, sim, rendering): result['over_button'] = True else: result['mouse_down_left'] = True return result def handle_settings_click(mouse_pos, sim): """Handle clicks in settings menu""" settings_menu_y = 100 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] def handle_ui_click(mouse_pos, sim, rendering): """Handle clicks on UI elements""" for category, button in rendering.category_buttons.items(): if button.collidepoint(mouse_pos): rendering.current_category = category return True for particle_type, button in rendering.buttons.items(): if button.collidepoint(mouse_pos): sim.current_particle_type = particle_type return True if rendering.clear_screen_button.collidepoint(mouse_pos): rendering.clear_screen(sim) return True return False def handle_mouse_up(event): """Handle mouse button up events""" if event.button == 1: return {'mouse_down_left': False} elif event.button == 3: return {'mouse_down_right': False} elif event.button == 2: return {'mouse_down_middle': False} return {} def handle_key_press(event, rendering, sim): """Handle keyboard press events""" if event.key == pygame.K_ESCAPE: print("Escape button pressed") print(f"Exiting Program {__file__}") return {'running': False} elif event.key == pygame.K_SPACE: engine_settings['pause_sim'] = not engine_settings['pause_sim'] elif event.key == pygame.K_c: rendering.clear_screen(sim) sim.reset_particle_count() elif event.key == pygame.K_z: return {'zoom_active': True, 'zoom_locked': False, 'zoom_pos': pygame.mouse.get_pos()} return {} def main(): 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) # State variables mouse_down_left = False mouse_down_right = False mouse_down_middle = False over_button = False zoom_active = False zoom_locked = False zoom_pos = None settings_visible = False running = True while running: # Clear screen at start of frame screen.fill((0, 0, 0)) fps = clock.get_fps() dt = clock.tick(1000) / 1000 keys = pygame.key.get_pressed() mouse_pos = pygame.mouse.get_pos() zoom_active = keys[pygame.K_z] # Handle events for event in pygame.event.get(): if event.type == pygame.QUIT: running = False continue if event.type == pygame.MOUSEBUTTONDOWN: mouse_pos = pygame.mouse.get_pos() 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 elif 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] - 100 # 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) particle_count = 0 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: if event.button == 1: mouse_down_left = False elif event.button == 3: mouse_down_right = False elif event.button == 2: mouse_down_middle = False elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: running = False elif event.key == pygame.K_SPACE: engine_settings['pause_sim'] = not engine_settings['pause_sim'] elif event.key == pygame.K_c: rendering.clear_screen(sim) elif event.key == pygame.K_z: zoom_active = True zoom_locked = False zoom_pos = pygame.mouse.get_pos() # Update simulation if not paused if not engine_settings['pause_sim']: sim.simulate_step(dt, engine_settings) # Handle continuous mouse input if mouse_down_left and not over_button: x, y = mouse_pos if sim.current_particle_type not in ['wind', 'air']: sim.create_particle_circle(x, y) else: sim.add_wind_zone(x, y) if mouse_down_right: x, y = mouse_pos sim.clear_particles_circle(x, y) if mouse_down_middle: x, y = mouse_pos sim.create_particle(x, y) # Draw everything in correct order rendering.draw_particles(sim.particles, sim.active_particles, sim.particle_size, rendering.particle_colors) rendering.draw_buttons() rendering.draw_brush_size_slider(sim.brush_size) rendering.render_brush_cursor(mouse_pos[0], mouse_pos[1], sim.brush_size * sim.particle_size) # Handle zoom window if zoom_active or zoom_locked: current_zoom_pos = zoom_pos if zoom_locked else mouse_pos zoom_surface = rendering.draw_zoom_window(sim.particles, sim.particle_size, rendering.particle_colors, current_zoom_pos) zoom_x = 10 if current_zoom_pos[0] > width/2 else width - 110 zoom_y = 10 if current_zoom_pos[1] > height/2 else height - 110 screen.blit(zoom_surface, (zoom_x, zoom_y)) # Draw settings and debug overlay last if settings_visible: settings_menu = rendering.draw_settings_menu() rendering.screen.blit(settings_menu, (rendering.width - 320, 100)) rendering.draw_debug_overlay(fps, sim) pygame.display.flip() pygame.quit() if __name__ == "__main__": # Profile the application profiler = cProfile.Profile() profiler.enable() main() profiler.disable() # Write profiling results to file with open('profile_results.log', 'w') as f: stats = pstats.Stats(profiler, stream=f) stats.sort_stats('cumulative') stats.print_stats()