diff --git a/factorio-clone/main.py b/factorio-clone/main.py new file mode 100644 index 0000000..d3dd366 --- /dev/null +++ b/factorio-clone/main.py @@ -0,0 +1,300 @@ +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() diff --git a/factorio-drones/main.py b/factorio-drones/main.py new file mode 100644 index 0000000..13bee40 --- /dev/null +++ b/factorio-drones/main.py @@ -0,0 +1,173 @@ +import pygame +import random +import math +from collections import deque + +# --- Simulation Classes --- + +class Task: + __slots__ = ('source', 'destination', 'item', 'amount') + def __init__(self, source, destination, item, amount): + self.source = source + self.destination = destination + self.item = item + self.amount = amount + +class Storage: + def __init__(self, name, pos): + self.name = name + self.position = pos # (x, y) position on screen + self.inventory = {"item": 1000} # starting inventory + + def add_item(self, item, amount): + self.inventory[item] = self.inventory.get(item, 0) + amount + + def remove_item(self, item, amount): + if self.inventory.get(item, 0) >= amount: + self.inventory[item] -= amount + return True + return False + + def request_transfer(self, destination, item, amount, task_queue): + if self.inventory.get(item, 0) >= amount: + # Remove items immediately, simulating reservation. + self.remove_item(item, amount) + task = Task(self, destination, item, amount) + task_queue.append(task) + +class Drone: + __slots__ = ('id', 'x', 'y', 'task', 'speed') + def __init__(self, id, pos): + self.id = id + self.x, self.y = pos + self.task = None + self.speed = 2.0 # pixels per frame + + def assign_task(self, task): + self.task = task + # Set drone's position to the source storage position. + self.x, self.y = task.source.position + + def update(self): + if self.task: + dest_x, dest_y = self.task.destination.position + dx = dest_x - self.x + dy = dest_y - self.y + dist = math.hypot(dx, dy) + if dist < self.speed: + # Arrived: set position exactly to destination and deliver item. + self.x, self.y = dest_x, dest_y + self.task.destination.add_item(self.task.item, self.task.amount) + self.task = None + else: + # Move a step toward the destination. + self.x += self.speed * dx / dist + self.y += self.speed * dy / dist + +# --- Pygame Simulation --- + +def main(): + pygame.init() + screen_width, screen_height = 800, 600 + screen = pygame.display.set_mode((screen_width, screen_height)) + pygame.display.set_caption("Drone Simulation") + clock = pygame.time.Clock() + font = pygame.font.SysFont("Arial", 16) + + # Global simulation state + task_queue = deque() + storages = [] + drones = [] + + # Unique id counters + drone_id_counter = 0 + storage_id_counter = 0 + + # Create some initial storages at random positions + for _ in range(5): + pos = (random.randint(50, screen_width - 50), random.randint(50, screen_height - 50)) + storages.append(Storage(f"Storage-{storage_id_counter}", pos)) + storage_id_counter += 1 + + # Create some initial drones at random positions + for _ in range(20): + pos = (random.randint(0, screen_width), random.randint(0, screen_height)) + drones.append(Drone(drone_id_counter, pos)) + drone_id_counter += 1 + + running = True + while running: + # --- Event Handling --- + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + elif event.type == pygame.KEYDOWN: + if event.key == pygame.K_t: + # Request a random transfer if at least two storages exist. + if len(storages) >= 2: + src, dest = random.sample(storages, 2) + amount = random.randint(1, 10) + src.request_transfer(dest, "item", amount, task_queue) + elif event.key == pygame.K_d: + # Add a new drone at a random position. + pos = (random.randint(0, screen_width), random.randint(0, screen_height)) + drones.append(Drone(drone_id_counter, pos)) + drone_id_counter += 1 + elif event.key == pygame.K_s: + # Add a new storage at a random position. + pos = (random.randint(50, screen_width - 50), random.randint(50, screen_height - 50)) + storages.append(Storage(f"Storage-{storage_id_counter}", pos)) + storage_id_counter += 1 + elif event.key == pygame.K_i: + # Add 50 items to each storage. + for s in storages: + s.add_item("item", 50) + + # --- Update Simulation --- + for drone in drones: + if drone.task is None and task_queue: + # Assign a task from the queue to the idle drone. + task = task_queue.popleft() + drone.assign_task(task) + drone.update() + + # --- Drawing --- + screen.fill((30, 30, 30)) # dark background + + # Draw storages (blue circles) with name and inventory. + for s in storages: + pygame.draw.circle(screen, (0, 100, 255), s.position, 20) + info = font.render(f"{s.name}: {s.inventory.get('item', 0)}", True, (255, 255, 255)) + screen.blit(info, (s.position[0] - 30, s.position[1] - 40)) + + # Draw drones. + # Idle drones are drawn in green; drones with tasks (in transit) are red. + for d in drones: + color = (255, 0, 0) if d.task else (0, 255, 0) + pygame.draw.circle(screen, color, (int(d.x), int(d.y)), 5) + + # Debug info: show task queue length, number of drones, storages. + debug_info = font.render( + f"Task Queue: {len(task_queue)} | Drones: {len(drones)} | Storages: {len(storages)}", + True, (255, 255, 0)) + screen.blit(debug_info, (10, 10)) + + # Display instructions. + instructions = [ + "Controls:", + "T - Request Transfer", + "D - Add Drone", + "S - Add Storage", + "I - Add Items to all Storages" + ] + for i, line in enumerate(instructions): + instr_text = font.render(line, True, (200, 200, 200)) + screen.blit(instr_text, (10, 30 + i * 20)) + + pygame.display.flip() + clock.tick(60) + + pygame.quit() + +if __name__ == "__main__": + main()