import pygame, sys, math #! Note: Broken # --------------------------- # Configuration & Constants # --------------------------- CELL_SIZE = 50 GRID_WIDTH = 16 GRID_HEIGHT = 12 SCREEN_WIDTH = CELL_SIZE * GRID_WIDTH SCREEN_HEIGHT = CELL_SIZE * GRID_HEIGHT FPS = 60 MIN_DISTANCE = 20 # minimum spacing (in pixels) between items on a belt # Direction vectors for movement DIRECTIONS = { "right": (1, 0), "left": (-1, 0), "up": (0, -1), "down": (0, 1), } # --------------------------- # Simulation Object Classes # --------------------------- class Item: def __init__(self, x, y, dx, dy, speed=60): # Position in pixels; starts at the center of a grid cell. self.x = x self.y = y self.dx = dx # normalized direction vector (e.g., from DIRECTIONS) self.dy = dy self.speed = speed def draw(self, surface): # Draw the item as a small circle (gold color) pygame.draw.circle(surface, (255, 215, 0), (int(self.x), int(self.y)), 10) class Conveyor: def __init__(self, grid_x, grid_y, direction): self.grid_x = grid_x self.grid_y = grid_y self.direction = direction # e.g. "right", "down", etc. self.rect = pygame.Rect(grid_x * CELL_SIZE, grid_y * CELL_SIZE, CELL_SIZE, CELL_SIZE) def update(self, dt): # Conveyors in this demo do not update objects directly. pass def draw(self, surface, debug=False): # Draw the cell as a light gray block pygame.draw.rect(surface, (169, 169, 169), self.rect) # Draw an arrow indicating the conveyor's direction center = self.rect.center offset = 15 dx, dy = DIRECTIONS[self.direction] end_point = (center[0] + dx * offset, center[1] + dy * offset) pygame.draw.line(surface, (0, 0, 0), center, end_point, 3) if debug: font = pygame.font.SysFont("monospace", 12) text = font.render(f"{self.grid_x},{self.grid_y}", True, (0, 0, 0)) surface.blit(text, (self.rect.x + 2, self.rect.y + 2)) class Arm: def __init__(self, grid_x, grid_y, pick_pos, place_pos): self.grid_x = grid_x self.grid_y = grid_y self.pick_pos = pick_pos # grid cell from which to pick an item self.place_pos = place_pos # grid cell where the item is dropped self.state = "idle" # states: "idle", "picking", "placing" self.holding_item = None self.timer = 0 def update(self, dt, items): # When idle, scan the pick cell for an item to pick up. if self.state == "idle": for item in items: cell_x = int(item.x // CELL_SIZE) cell_y = int(item.y // CELL_SIZE) if (cell_x, cell_y) == self.pick_pos: self.holding_item = item items.remove(item) self.state = "picking" self.timer = 0.5 # time to pick up break elif self.state == "picking": self.timer -= dt if self.timer <= 0: self.state = "placing" self.timer = 0.5 # time to place the item elif self.state == "placing": self.timer -= dt if self.timer <= 0: # Drop the item at the center of the place cell, with a new direction. if self.holding_item: self.holding_item.x = self.place_pos[0] * CELL_SIZE + CELL_SIZE / 2 self.holding_item.y = self.place_pos[1] * CELL_SIZE + CELL_SIZE / 2 # For this layout, the vertical belt moves items downward. self.holding_item.dx, self.holding_item.dy = DIRECTIONS["down"] items.append(self.holding_item) self.holding_item = None self.state = "idle" def draw(self, surface, debug=False): # Base center of the arm (drawn as a circle) base_center = (self.grid_x * CELL_SIZE + CELL_SIZE // 2, self.grid_y * CELL_SIZE + CELL_SIZE // 2) pygame.draw.circle(surface, (135, 206, 235), base_center, CELL_SIZE // 3) # Compute centers for pick and place zones pick_center = (self.pick_pos[0] * CELL_SIZE + CELL_SIZE // 2, self.pick_pos[1] * CELL_SIZE + CELL_SIZE // 2) place_center = (self.place_pos[0] * CELL_SIZE + CELL_SIZE // 2, self.place_pos[1] * CELL_SIZE + CELL_SIZE // 2) # Draw the arm's "arm" as a line indicating its action. if self.state == "picking": pygame.draw.line(surface, (255, 0, 0), base_center, pick_center, 4) elif self.state == "placing": pygame.draw.line(surface, (0, 255, 0), base_center, place_center, 4) else: # Idle: draw faint guide lines toward both pick and place positions. pygame.draw.line(surface, (100, 100, 100), base_center, pick_center, 2) pygame.draw.line(surface, (100, 100, 100), base_center, place_center, 2) if debug: font = pygame.font.SysFont("monospace", 12) text = font.render(f"Arm: {self.state}", True, (0, 0, 0)) surface.blit(text, (self.grid_x * CELL_SIZE, self.grid_y * CELL_SIZE)) # Draw outlines for the pick (green) and place (red) zones. pick_rect = pygame.Rect(self.pick_pos[0] * CELL_SIZE, self.pick_pos[1] * CELL_SIZE, CELL_SIZE, CELL_SIZE) place_rect = pygame.Rect(self.place_pos[0] * CELL_SIZE, self.place_pos[1] * CELL_SIZE, CELL_SIZE, CELL_SIZE) pygame.draw.rect(surface, (0, 255, 0), pick_rect, 2) pygame.draw.rect(surface, (255, 0, 0), place_rect, 2) class Factory: def __init__(self, grid_x, grid_y, output_direction="right"): self.grid_x = grid_x self.grid_y = grid_y self.output_direction = output_direction self.rect = pygame.Rect(grid_x * CELL_SIZE, grid_y * CELL_SIZE, CELL_SIZE, CELL_SIZE) self.production_timer = 1.0 # seconds until next item production self.production_interval = 0.5 def update(self, dt, items): self.production_timer -= dt if self.production_timer <= 0: # Create a new item at the center of the factory cell. center_x = self.grid_x * CELL_SIZE + CELL_SIZE / 2 center_y = self.grid_y * CELL_SIZE + CELL_SIZE / 2 dx, dy = DIRECTIONS[self.output_direction] new_item = Item(center_x, center_y, dx, dy, speed=60) items.append(new_item) self.production_timer = self.production_interval def draw(self, surface, debug=False): pygame.draw.rect(surface, (192, 192, 192), self.rect) if debug: font = pygame.font.SysFont("monospace", 12) text = font.render(f"{self.production_timer:.1f}", True, (0, 0, 0)) surface.blit(text, (self.rect.x + 2, self.rect.y + 2)) # --------------------------- # Helper: Draw Grid Lines # --------------------------- def draw_grid(surface): for x in range(0, SCREEN_WIDTH, CELL_SIZE): pygame.draw.line(surface, (200, 200, 200), (x, 0), (x, SCREEN_HEIGHT)) for y in range(0, SCREEN_HEIGHT, CELL_SIZE): pygame.draw.line(surface, (200, 200, 200), (0, y), (SCREEN_WIDTH, y)) # --------------------------- # Update Items with Collision (Queueing) Behavior # --------------------------- def update_items(items, dt): for item in items: # Compute the proposed new position. new_x = item.x + item.dx * item.speed * dt new_y = item.y + item.dy * item.speed * dt can_move = True # Check all other items to see if one is ahead and too close. for other in items: if other is item: continue # Calculate vector from this item to the other. rel_x = other.x - item.x rel_y = other.y - item.y # Dot product to see if other is ahead along the item's movement direction. if (rel_x * item.dx + rel_y * item.dy) > 0: dist = math.hypot(rel_x, rel_y) if dist < MIN_DISTANCE: can_move = False break if can_move: item.x = new_x item.y = new_y # --------------------------- # Main Simulation Loop # --------------------------- def main(): pygame.init() screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption("Factorio-like Simulation with Queuing & Mechanical Arm") clock = pygame.time.Clock() debug_mode = True # Lists to hold simulation objects conveyors = [] items = [] arms = [] factories = [] # -- Layout Setup -- # Horizontal conveyor belt from (2,5) to (10,5) for i in range(2, 11): conveyors.append(Conveyor(i, 5, "right")) # Vertical conveyor belt from (10,5) to (10,9) for j in range(5, 10): conveyors.append(Conveyor(10, j, "down")) # Factory at (1,5) outputs items to the right (onto the horizontal belt) factories.append(Factory(1, 5, output_direction="right")) # Arm at (10,4): picks from cell (10,5) and places to cell (10,6) arms.append(Arm(10, 4, (10, 5), (10, 6))) running = True while running: dt = clock.tick(FPS) / 1000.0 # Delta time in seconds # Event handling for event in pygame.event.get(): if event.type == pygame.QUIT: running = False # Toggle debug mode with the D key. if event.type == pygame.KEYDOWN: if event.key == pygame.K_d: debug_mode = not debug_mode # Update simulation objects. for factory in factories: factory.update(dt, items) for arm in arms: arm.update(dt, items) # Update items with queuing/collision behavior. update_items(items, dt) # --------------------------- # Drawing Phase # --------------------------- screen.fill((50, 50, 50)) # Clear the screen with a dark background. # Draw grid lines if debug mode is on. if debug_mode: draw_grid(screen) # Draw conveyors. for conveyor in conveyors: conveyor.draw(screen, debug_mode) # Draw factories. for factory in factories: factory.draw(screen, debug_mode) # Draw arms. for arm in arms: arm.draw(screen, debug_mode) # Draw items. for item in items: item.draw(screen) # Global debug info. if debug_mode: font = pygame.font.SysFont("monospace", 18) debug_text = font.render("DEBUG MODE ON - Press D to toggle", True, (255, 255, 255)) screen.blit(debug_text, (10, SCREEN_HEIGHT - 30)) pygame.display.flip() pygame.quit() sys.exit() if __name__ == "__main__": main()