Last active
October 26, 2025 22:29
-
-
Save sarumaj/3ee162cf2b4c95155da0b5e0534a0820 to your computer and use it in GitHub Desktop.
The Farmer Was Replaced (https://store.steampowered.com/app/2060160/The_Farmer_Was_Replaced)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ## CONST | |
| WATER_THRESHOLD = 0.75 | |
| MIN_WATER_ITEMS = 100 | |
| MIN_POWER_LEVEL = 100000 | |
| MIN_FERTILIZER = 100 | |
| MIN_ITEM_THRESHOLD = 2500000000 | |
| HAY_OVERDUE_MARGIN = 1.2 | |
| REVERSE_CACTI_SORT = False | |
| MIN_MAZE_PROBABILITY = 0.0 | |
| MAX_MAZE_PROBABILITY = 0.5 | |
| PLANTS_REQUIRING_SOIL = [ | |
| Entities.Carrot, Entities.Pumpkin, | |
| Entities.Cactus, Entities.Sunflower | |
| ] | |
| DRONE_HATS = [ | |
| Hats.Brown_Hat, Hats.Carrot_Hat, Hats.Gold_Hat, | |
| Hats.Gray_Hat, Hats.Green_Hat, Hats.Pumpkin_Hat, | |
| Hats.Purple_Hat, Hats.Straw_Hat, Hats.Sunflower_Hat, | |
| Hats.Traffic_Cone, Hats.Tree_Hat, Hats.Wizard_Hat | |
| ] | |
| AUTO_UNLOCKS = [ | |
| Unlocks.Cactus, Unlocks.Carrots, Unlocks.Dinosaurs, | |
| Unlocks.Expand, Unlocks.Fertilizer, Unlocks.Grass, | |
| Unlocks.Leaderboard, Unlocks.Megafarm, Unlocks.Mazes, | |
| Unlocks.Polyculture, Unlocks.Pumpkins, Unlocks.Speed, | |
| Unlocks.Sunflowers, Unlocks.The_Farmers_Remains, Unlocks.Top_Hat, | |
| Unlocks.Trees, Unlocks.Watering | |
| ] | |
| OVERRIDE_CROP_SELECTION = "none" | |
| OVERRIDE_WORLD_SIZE = "none" | |
| OVERRIDE_EXECUTION_SPEED = "none" | |
| ## CALC | |
| def calculate_maze_probability(): | |
| return max( | |
| min( | |
| 1 - min(num_items(Items.Gold) * 100 / (num_items(Items.Wood) + 1), 1), | |
| MAX_MAZE_PROBABILITY | |
| ), | |
| MIN_MAZE_PROBABILITY | |
| ) | |
| def manhattan_distance(x, y, target_x, target_y): | |
| return abs(x - target_x) + abs(y - target_y) | |
| def max_num_drones(): | |
| return min(max_drones(), get_world_size()**2) | |
| def measure_safe(direction="none"): | |
| if direction == "none": | |
| return measure() or 0 | |
| return measure(direction) or 0 | |
| def find_optimal_grid(num_drones): | |
| sqrt_drones = (num_drones ** 0.5) // 1 | |
| # Check if perfect square | |
| if sqrt_drones * sqrt_drones == num_drones: | |
| return (sqrt_drones, sqrt_drones) | |
| # Find the best rectangular grid | |
| # Start from square root and work down to find factors | |
| best_cols = num_drones | |
| best_rows = 1 | |
| best_ratio = 0 | |
| for rows in range(1, sqrt_drones + 2): | |
| if num_drones % rows == 0: | |
| cols = num_drones // rows | |
| if cols >= rows: # Prefer cols >= rows | |
| ratio = cols / rows | |
| # Prefer ratios closer to 1 (more square-like) | |
| if ratio < best_ratio or best_ratio == 0: | |
| best_ratio = ratio | |
| best_cols = cols | |
| best_rows = rows | |
| return (best_cols, best_rows) | |
| def assign_lanes_to_drone(drone_index, world_size, num_drones_total, horizontal): | |
| if num_drones_total <= 0 or drone_index >= num_drones_total: | |
| return (0, 0, 0, 0) | |
| lanes_per_drone = world_size // num_drones_total | |
| extra_lanes = world_size % num_drones_total | |
| if drone_index < extra_lanes: | |
| # This drone gets an extra lane | |
| num_lanes = lanes_per_drone + 1 | |
| offset = drone_index * (lanes_per_drone + 1) | |
| else: | |
| # Standard allocation | |
| num_lanes = lanes_per_drone | |
| offset = extra_lanes * (lanes_per_drone + 1) + (drone_index - extra_lanes) * lanes_per_drone | |
| if horizontal: | |
| return (0, offset, world_size, num_lanes) | |
| return (offset, 0, num_lanes, world_size) | |
| def get_drone_grid_assignment(drone_index, world_size, num_drones_total): | |
| if num_drones_total <= 0: | |
| return (0, 0, world_size, world_size) | |
| if drone_index >= num_drones_total: | |
| return (0, 0, 0, 0) # Invalid drone index | |
| # Find optimal grid dimensions (prefer square, then rectangular with cols > rows) | |
| grid_cols, grid_rows = find_optimal_grid(num_drones_total) | |
| # Calculate this drone's position in the grid | |
| row = drone_index // grid_cols | |
| col = drone_index % grid_cols | |
| # Distribute space evenly, handling remainders | |
| base_width = world_size // grid_cols | |
| extra_width = world_size % grid_cols | |
| base_height = world_size // grid_rows | |
| extra_height = world_size % grid_rows | |
| # Calculate width for this column | |
| if col < extra_width: | |
| section_width = base_width + 1 | |
| offset_x = col * (base_width + 1) | |
| else: | |
| section_width = base_width | |
| offset_x = extra_width * (base_width + 1) + (col - extra_width) * base_width | |
| # Calculate height for this row | |
| if row < extra_height: | |
| section_height = base_height + 1 | |
| offset_y = row * (base_height + 1) | |
| else: | |
| section_height = base_height | |
| offset_y = extra_height * (base_height + 1) + (row - extra_height) * base_height | |
| return (offset_x, offset_y, section_width, section_height) | |
| def wait_n_seconds(n): | |
| for _ in range(n): | |
| do_a_flip() | |
| def get_speed_factor(): | |
| speed_factor = 1.0 | |
| for _ in range(num_unlocked(Unlocks.Speed)): | |
| speed_factor *= 1.5 | |
| if num_items(Items.Power) > 0: | |
| return speed_factor * 2 | |
| return speed_factor | |
| def round(f): | |
| n, r = f // 1, f % 1 | |
| if r >= 0.5: | |
| n += 1 | |
| return n | |
| ## COST | |
| def get_lowest_cost(): | |
| lowest = 0 | |
| for u in AUTO_UNLOCKS: | |
| cost = get_cost(u) or {} | |
| for key in cost: | |
| current = cost[key] | |
| if current < lowest or lowest == 0: | |
| lowest = current | |
| return lowest | |
| ## UPGRADES | |
| def automate_upgrades(): | |
| for u in AUTO_UNLOCKS: | |
| cost = get_cost(u) or {} | |
| can_be_unlocked = len(cost) > 0 | |
| for key in cost: | |
| if num_items(key) < cost[key]: | |
| can_be_unlocked = False | |
| break | |
| if can_be_unlocked: | |
| unlock(u) | |
| quick_print( | |
| get_time(), | |
| "New unlock:", u, | |
| "count:", num_unlocked(u) | |
| ) | |
| ## HATS | |
| def find_fashionable_hat(idx): | |
| while num_unlocked(DRONE_HATS[idx%len(DRONE_HATS)]) == 0: | |
| idx += 1 | |
| change_hat(DRONE_HATS[idx%len(DRONE_HATS)]) | |
| ## MOVEMENT | |
| def navigate_to_position(target_x, target_y): | |
| x, y = get_pos_x(), get_pos_y() | |
| while x != target_x or y != target_y: | |
| moved = False | |
| # Handle X movement | |
| if x < target_x and can_move(East): | |
| move(East) | |
| moved = True | |
| elif x > target_x and can_move(West): | |
| move(West) | |
| moved = True | |
| # Handle Y movement | |
| if y < target_y and can_move(North): | |
| move(North) | |
| moved = True | |
| elif y > target_y and can_move(South): | |
| move(South) | |
| moved = True | |
| # Update position | |
| x, y = get_pos_x(), get_pos_y() | |
| # If we couldn't move at all, we're stuck | |
| if not moved: | |
| break | |
| def move_in_snake_way(offset_x, offset_y, grid_size_x, grid_size_y, flip_parity=False): | |
| x = get_pos_x() - offset_x | |
| y = get_pos_y() - offset_y | |
| parity = 0 | |
| if flip_parity: | |
| parity = 1 | |
| # Forward snake pattern: alternate direction each row, moving North | |
| if y % 2 == parity: # Default even rows: move East | |
| if x < grid_size_x - 1: | |
| return move(East) | |
| # At eastern edge, move North if possible | |
| if y < grid_size_y - 1: | |
| return move(North) | |
| else: # Default odd rows: move West | |
| if x > 0: | |
| return move(West) | |
| # At western edge, move North if possible | |
| if y < grid_size_y - 1: | |
| return move(North) | |
| # Finished scanning | |
| return False | |
| ## MAZE | |
| def is_in_the_maze(): | |
| entity_type = get_entity_type() | |
| return entity_type == Entities.Treasure or entity_type == Entities.Hedge | |
| def use_weird_substance(): | |
| size = get_world_size() | |
| weird_substance = num_items(Items.Weird_Substance) | |
| substance = size * 2**(num_unlocked(Unlocks.Mazes) - 1) | |
| if substance > weird_substance: | |
| substance = weird_substance | |
| if substance <= 0: | |
| return 0, False | |
| use_item(Items.Weird_Substance, substance) | |
| return substance, True | |
| def solve_with_wall_following(): | |
| direction_order = [North, East, South, West] | |
| # State tracking | |
| visited = {} # Maps (pos, dir, follow_sign) -> step_number | |
| current_dir_idx = 0 | |
| follow_sign = 1 # 1: right-hand, -1: left-hand | |
| # Loop detection | |
| steps_since_last_flip = 0 | |
| flip_cooldown = 20 # Minimum steps before considering another flip | |
| loop_threshold = 8 # If we revisit a state within this many steps, it's a loop | |
| def detect_tight_loop(): | |
| current_pos = (get_pos_x(), get_pos_y()) | |
| current_state = (current_pos, current_dir_idx, follow_sign) | |
| if current_state in visited: | |
| steps_ago = steps_since_last_flip - visited[current_state] | |
| # Tight loop: revisited same state within threshold | |
| return steps_ago < loop_threshold | |
| return False | |
| step_count = 0 | |
| max_steps = 1000 # Safety limit | |
| while get_entity_type() == Entities.Hedge and step_count < max_steps: | |
| step_count += 1 | |
| current_pos = (get_pos_x(), get_pos_y()) | |
| current_state = (current_pos, current_dir_idx, follow_sign) | |
| # Check for loop and flip if needed | |
| if detect_tight_loop() and steps_since_last_flip >= flip_cooldown: | |
| follow_sign *= -1 | |
| steps_since_last_flip = 0 | |
| quick_print( | |
| get_time(), | |
| "Loop detected! Flipping to", {1: "right", -1:"left"}[follow_sign], | |
| "rule at position", current_pos | |
| ) | |
| # Don't clear visited - keep history to avoid re-entering same loops | |
| # Record this state | |
| visited[current_state] = steps_since_last_flip | |
| steps_since_last_flip += 1 | |
| # Wall-following logic | |
| # Try to turn toward the wall (right-hand or left-hand) | |
| side_idx = (current_dir_idx + follow_sign) % 4 | |
| if can_move(direction_order[side_idx]): | |
| # Can turn toward wall - do it | |
| move(direction_order[side_idx]) | |
| current_dir_idx = side_idx | |
| elif can_move(direction_order[current_dir_idx]): | |
| # Can't turn, but can go straight - do it | |
| move(direction_order[current_dir_idx]) | |
| else: | |
| # Can't turn or go straight - turn away from wall | |
| current_dir_idx = (current_dir_idx - follow_sign) % 4 | |
| # Note: We don't move here, just change direction | |
| # Next iteration will try to move in the new direction | |
| if step_count >= max_steps: | |
| quick_print(get_time(), "Hit step limit - maze unsolvable or too complex") | |
| return False | |
| return get_entity_type() == Entities.Treasure | |
| def solve_maze(): | |
| if is_in_the_maze(): | |
| solve_with_wall_following() | |
| if get_entity_type() == Entities.Treasure: | |
| harvest() | |
| quick_print(get_time(), "Solved maze, treasure collected") | |
| return True | |
| return False | |
| ## PLANTING | |
| def decide_what_to_plant(allow_dinosaur): | |
| if OVERRIDE_CROP_SELECTION != "none": | |
| if allow_dinosaur or OVERRIDE_CROP_SELECTION != Entities.Dinosaur: | |
| return OVERRIDE_CROP_SELECTION | |
| wood = num_items(Items.Wood) | |
| hay = num_items(Items.Hay) | |
| carrot = num_items(Items.Carrot) | |
| power = num_items(Items.Power) | |
| pumpkin = num_items(Items.Pumpkin) | |
| cactus = num_items(Items.Cactus) | |
| bones = num_items(Items.Bone) | |
| minimum_item_threshold = max(MIN_ITEM_THRESHOLD, get_lowest_cost()) | |
| hay_overdue_threshold = hay * HAY_OVERDUE_MARGIN | |
| if carrot > minimum_item_threshold and power < MIN_POWER_LEVEL: | |
| return Entities.Sunflower | |
| if hay > minimum_item_threshold and wood < hay_overdue_threshold: | |
| return Entities.Bush | |
| if wood > minimum_item_threshold and hay > minimum_item_threshold and carrot < hay_overdue_threshold: | |
| return Entities.Carrot | |
| if carrot > minimum_item_threshold and pumpkin < hay_overdue_threshold and not allow_dinosaur: | |
| return Entities.Pumpkin | |
| if pumpkin > minimum_item_threshold and cactus < hay_overdue_threshold and not allow_dinosaur: | |
| return Entities.Cactus | |
| if allow_dinosaur and cactus > minimum_item_threshold and bones < hay_overdue_threshold: | |
| return Entities.Dinosaur | |
| return Entities.Grass | |
| def vary_crop(crop): | |
| if crop in [Entities.Tree, Entities.Tree]: | |
| x, y = get_pos_x(), get_pos_y() | |
| if (x*y)%2 == 0: | |
| return Entities.Tree | |
| return Entities.Bush | |
| return crop | |
| def apply_fertilizer_if_possible(): | |
| if num_items(Items.Fertilizer) <= MIN_FERTILIZER: | |
| return False | |
| use_item(Items.Fertilizer) | |
| return True | |
| def plant_crop(crop): | |
| ground_type = get_ground_type() | |
| if crop in PLANTS_REQUIRING_SOIL and ground_type != Grounds.Soil: | |
| till() | |
| if can_harvest(): | |
| harvest() | |
| if crop == Entities.Dinosaur: | |
| return | |
| plant(crop) | |
| apply_fertilizer_if_possible() | |
| def plant_with_companion(crop): | |
| plant_crop(crop) | |
| if crop == Entities.Dinosaur: | |
| return ("none", 0, 0) | |
| companion_info = get_companion() or "none" | |
| if companion_info == "none": | |
| return ("none", 0, 0) | |
| companion_type, (target_x, target_y) = companion_info | |
| return (companion_type, target_x, target_y) | |
| def ensure_companion(companion_type, target_x, target_y, offset_x, offset_y, grid_size_x, grid_size_y): | |
| # Check if target is within the grid | |
| if not (offset_x <= target_x < offset_x + grid_size_x and | |
| offset_y <= target_y < offset_y + grid_size_y): | |
| return False | |
| navigate_to_position(target_x, target_y) | |
| entity_at_target = get_entity_type() | |
| if entity_at_target != companion_type: | |
| harvest_if_possible() | |
| ground_type = get_ground_type() | |
| if companion_type in PLANTS_REQUIRING_SOIL and ground_type != Grounds.Soil: | |
| till() | |
| plant(companion_type) | |
| return True | |
| def replant_dead_pumpkin(): | |
| if get_entity_type() == Entities.Dead_Pumpkin: | |
| plant(Entities.Pumpkin) | |
| return True | |
| return False | |
| ## SORT | |
| def custom_sort(arr, reverse=False, method="none"): | |
| if len(arr) <= 1: | |
| return arr | |
| # Choose pivot (using middle element for better average performance) | |
| pivot = arr[len(arr) // 2] | |
| # Partition into three groups using explicit loops | |
| left = [] | |
| middle = [] | |
| right = [] | |
| def method_(x): | |
| return x | |
| key_func = method_ | |
| if method != "none": | |
| key_func = method | |
| pivot_keyed = key_func(pivot) | |
| for x in arr: | |
| x_keyed = key_func(x) | |
| if x_keyed < pivot_keyed: | |
| left.append(x) | |
| elif x_keyed == pivot_keyed: | |
| middle.append(x) | |
| else: | |
| right.append(x) | |
| # Recursively sort and combine | |
| if reverse: | |
| return custom_sort(right, reverse, method) + middle + custom_sort(left, reverse, method) | |
| return custom_sort(left, reverse, method) + middle + custom_sort(right, reverse, method) | |
| def sort_cacti_field(offset_x, offset_y, grid_size_x, grid_size_y, sort_rows): | |
| def sort_one_direction(sort_rows, reverse): | |
| if sort_rows: | |
| outer_grid_size, inner_grid_size = grid_size_y, grid_size_x | |
| swap_direction = East # Sort each row (West to East) | |
| else: | |
| outer_grid_size, inner_grid_size = grid_size_x, grid_size_y | |
| swap_direction = North # Sort each column (South to North) | |
| for i in range(outer_grid_size): | |
| swapped = True | |
| pass_num = 0 | |
| while swapped and pass_num < inner_grid_size - 1: | |
| swapped = False | |
| for j in range(inner_grid_size - 1): | |
| if sort_rows: | |
| row, col = i, j | |
| else: | |
| row, col = j, i | |
| navigate_to_position(offset_x + col, offset_y + row) | |
| if get_entity_type() != Entities.Cactus: | |
| if can_harvest(): | |
| harvest() | |
| if get_ground_type() != Grounds.Soil: | |
| till() | |
| plant(Entities.Cactus) | |
| current_value = measure_safe() | |
| next_value = measure_safe(swap_direction) | |
| should_swap = False | |
| if reverse: | |
| should_swap = current_value < next_value | |
| else: | |
| should_swap = current_value > next_value | |
| if should_swap: | |
| swap(swap_direction) | |
| swapped = True | |
| pass_num += 1 | |
| sort_one_direction(sort_rows, REVERSE_CACTI_SORT) | |
| if sort_rows: | |
| return grid_size_y | |
| else: | |
| return grid_size_x | |
| ## WATERING | |
| def water_if_needed(): | |
| if get_water() > WATER_THRESHOLD or num_items(Items.Water) <= MIN_WATER_ITEMS: | |
| return | |
| use_item(Items.Water) | |
| ## HARVESTING | |
| def should_harvest(): | |
| if not can_harvest(): | |
| return False | |
| entity_type = get_entity_type() | |
| if entity_type == Entities.Hedge: | |
| return False | |
| if entity_type == Entities.Apple: | |
| return False | |
| return True | |
| def harvest_if_possible(expected_item = "none"): | |
| if not should_harvest(): | |
| return False | |
| count_before = 0 | |
| if expected_item != "none": | |
| count_before = num_items(expected_item) | |
| harvest() | |
| count_after = 1 | |
| if expected_item != "none": | |
| count_after = num_items(expected_item) | |
| return count_after > count_before | |
| def harvest_sunflowers_optimally(offset_x, offset_y, grid_size_x, grid_size_y): | |
| sunflower_positions = [] | |
| navigate_to_position(offset_x, offset_y) | |
| # Scan the grid and collect all sunflower positions with their petal counts | |
| while True: | |
| if get_entity_type() != Entities.Sunflower: | |
| plant_crop(Entities.Sunflower) | |
| continue | |
| water_if_needed() | |
| while get_entity_type() == Entities.Sunflower and not can_harvest(): | |
| wait_n_seconds(1) | |
| petals = measure_safe() | |
| pos = (get_pos_x(), get_pos_y()) | |
| sunflower_positions.append((pos, petals)) | |
| if not move_in_snake_way(offset_x, offset_y, grid_size_x, grid_size_y): | |
| navigate_to_position(offset_x, offset_y) | |
| break | |
| # Get unique petal counts unordered | |
| unique_petal_counts = set() | |
| for _, petals in sunflower_positions: | |
| unique_petal_counts.add(petals) | |
| # Iteratively harvest sunflowers with each petal count in descending order | |
| for current_petal_count in custom_sort(list(unique_petal_counts), True): | |
| for (pos_x, pos_y), petals in sunflower_positions[:]: | |
| if petals != current_petal_count: | |
| continue | |
| navigate_to_position(pos_x, pos_y) | |
| while get_entity_type() == Entities.Sunflower and not can_harvest(): | |
| wait_n_seconds(1) | |
| harvest() | |
| sunflower_positions.remove(((pos_x, pos_y), petals)) | |
| for (pos_x, pos_y), _ in sunflower_positions: | |
| navigate_to_position(pos_x, pos_y) | |
| if can_harvest(): | |
| harvest() | |
| navigate_to_position(offset_x, offset_y) | |
| def harvest_apples_optimally(index, offset_x, offset_y, grid_size_x, grid_size_y): | |
| change_hat(Hats.Dinosaur_Hat) | |
| navigate_to_position(offset_x, offset_y) | |
| ends_on_right = grid_size_y % 2 == 1 | |
| apples_collected = 0 | |
| flip_parity = False | |
| effective_grid_size_x = grid_size_x - 1 | |
| stuck = False | |
| apple_pos_x, apple_pos_y = (0, 0) | |
| def speedup(apple_pos_x, apple_pos_y): | |
| if (apple_pos_x >= effective_offset_x | |
| and get_pos_y() < apple_pos_y < grid_size_y - 1 | |
| and apples_collected < 0.25 * grid_size_x * grid_size_y): | |
| navigate_to_position(apple_pos_x, apple_pos_y) | |
| apple_pos_x, apple_pos_y = measure() | |
| return (apple_pos_x, apple_pos_y), True | |
| return (apple_pos_x, apple_pos_y), False | |
| while True: | |
| effective_offset_x = offset_x | |
| if not ends_on_right: | |
| effective_offset_x = offset_x + 1 | |
| if not move_in_snake_way(effective_offset_x, offset_y, effective_grid_size_x, grid_size_y, flip_parity): | |
| current_pos = get_pos_x(), get_pos_y() | |
| if current_pos[1] == grid_size_y - 1: # hit top | |
| if ends_on_right: # move furhter right and down | |
| flip_parity = True | |
| ends_on_right = not ends_on_right | |
| navigate_to_position(effective_grid_size_x, get_pos_y()) | |
| navigate_to_position(effective_grid_size_x, offset_y) | |
| else: # ends on left: move further left and down | |
| flip_parity = False | |
| ends_on_right = grid_size_y % 2 == 1 | |
| navigate_to_position(offset_x, get_pos_y()) | |
| navigate_to_position(offset_x, offset_y) | |
| navigate_to_position(effective_offset_x, offset_y) | |
| (apple_pos_x, apple_pos_y), boosted = speedup(apple_pos_x, apple_pos_y) | |
| if boosted: | |
| apples_collected += 1 | |
| else: # collision with tail mid-air, resolve by going one up or down | |
| if not move(North): | |
| move(South) | |
| if current_pos == (get_pos_x(), get_pos_y()): | |
| stuck = True | |
| break | |
| entity_type = get_entity_type() | |
| if entity_type == Entities.Apple and not stuck: | |
| apple_pos_x, apple_pos_y = measure() | |
| apples_collected += 1 | |
| (apple_pos_x, apple_pos_y), boosted = speedup(apple_pos_x, apple_pos_y) | |
| if boosted: | |
| apples_collected += 1 | |
| if entity_type != Entities.Apple and can_harvest(): | |
| harvest() | |
| find_fashionable_hat(index) | |
| quick_print( | |
| get_time(), | |
| "Acquired dinosaur tail of length:", apples_collected, | |
| ) | |
| return apples_collected | |
| ## HARVEST_PREPARATION | |
| def prepare_cacti(index, offset_x, offset_y, grid_size_x, grid_size_y): | |
| size = get_world_size() | |
| num_drones_total = num_drones() | |
| if index > 0: | |
| return True, offset_x, offset_y, grid_size_x, grid_size_y | |
| size = get_world_size() | |
| num_drones_to_spawn = max_num_drones() | |
| for sort_rows in (True, False): | |
| spawned_sort_drones = [] | |
| while num_drones() > 1: | |
| wait_n_seconds(1) | |
| def create_sort_task(idx, ox, oy, gx, gy, sr): | |
| def task(): | |
| find_fashionable_hat(idx) | |
| navigate_to_position(ox, oy) | |
| quick_print(get_time(), "Sort rone", idx, "arrived at:", get_pos_x(), get_pos_y()) | |
| lanes = sort_cacti_field(ox, oy, gx, gy, sr) | |
| quick_print(get_time(), "Sort drone:", idx, "completed", lanes, "lanes") | |
| return task | |
| for j in range(0, num_drones_to_spawn - num_drones()): | |
| drone_idx = j + 1 | |
| nox, noy, ngx, ngy = assign_lanes_to_drone(drone_idx, size, num_drones_to_spawn, sort_rows) | |
| drone = spawn_drone(create_sort_task(drone_idx, nox, noy, ngx, ngy, sort_rows)) | |
| if drone: | |
| spawned_sort_drones.append(drone) | |
| quick_print(get_time(), "Spawned sort drone", drone_idx, "was for rows:", sort_rows) | |
| nox, noy, ngx, ngy = assign_lanes_to_drone(0, size, num_drones_to_spawn, sort_rows) | |
| create_sort_task(0, nox, noy, ngx, ngy, sort_rows)() | |
| for drone in spawned_sort_drones[:]: | |
| wait_for(drone) | |
| spawned_sort_drones.remove(drone) | |
| quick_print(get_time(), "Sorted all, were rows:", sort_rows) | |
| return False, offset_x, offset_y, grid_size_x, grid_size_y | |
| def prepare_pumpkins(index, offset_x, offset_y, grid_size_x, grid_size_y): | |
| not_replanted_cnt = 0 | |
| while True: | |
| replanted = False | |
| while True: | |
| replanted = replanted or replant_dead_pumpkin() | |
| if not move_in_snake_way(offset_x, offset_y, grid_size_x, grid_size_y): | |
| navigate_to_position(offset_x, offset_y) | |
| break | |
| if not_replanted_cnt >= 1: | |
| break | |
| if not replanted: | |
| not_replanted_cnt += 1 | |
| else: | |
| not_replanted_cnt = 0 | |
| return False, offset_x, offset_y, grid_size_x, grid_size_y | |
| def prepare_dinosaur(index, offset_x, offset_y, grid_size_x, grid_size_y): | |
| if index > 0: | |
| return True, offset_x, offset_y, grid_size_x, grid_size_y | |
| size = get_world_size() | |
| return False, 0, 0, size, size | |
| ## PHASES | |
| def maze_phase(index, offset_x, offset_y): | |
| navigate_to_position(offset_x, offset_y) | |
| quick_print(get_time(), "Drone:", index, "in the maze at", offset_x, offset_y) | |
| while num_drones() < max_num_drones(): | |
| wait_n_seconds(1) | |
| maze_triggered = True | |
| size = get_world_size() | |
| speed_factor = round(get_speed_factor()) | |
| if not is_in_the_maze() and index == 0: | |
| wait_n_seconds(size // max(speed_factor, 2) + 1) | |
| if get_entity_type() != Entities.Bush: | |
| plant_crop(Entities.Bush) | |
| _, maze_triggered = use_weird_substance() | |
| quick_print(get_time(), "Drone:", index, "triggered maze", maze_triggered) | |
| waited_seconds = 0 | |
| while not is_in_the_maze(): | |
| wait_n_seconds(1) | |
| waited_seconds += 1 | |
| if num_drones() < max_num_drones() or waited_seconds >= size: | |
| break | |
| solved = solve_maze() | |
| if solved: | |
| quick_print(get_time(), "Drone:", index, "has saved the maze!") | |
| if index == 0: | |
| pet_the_piggy() | |
| navigate_to_position(offset_x, offset_y) | |
| else: | |
| quick_print(get_time(), "Drone:", index, "fought well in the maze, but failed.") | |
| def companion_planting_phase(crop, offset_x, offset_y, grid_size_x, grid_size_y): | |
| companion_map = {} | |
| while True: | |
| varied_crop = vary_crop(crop) | |
| companion_type, target_x, target_y = plant_with_companion(varied_crop) | |
| if companion_type != "none": | |
| plant_pos = get_pos_x(), get_pos_y() | |
| companion_map[plant_pos] = (companion_type, target_x, target_y) | |
| if not move_in_snake_way(offset_x, offset_y, grid_size_x, grid_size_y): | |
| navigate_to_position(offset_x, offset_y) | |
| break | |
| for plant_pos in companion_map: | |
| (companion_type, target_x, target_y) = companion_map[plant_pos] | |
| ensure_companion( | |
| companion_type, target_x, target_y, | |
| offset_x, offset_y, grid_size_x, grid_size_y, | |
| ) | |
| navigate_to_position(offset_x, offset_y) | |
| def harvest_preparation_phase(crop, index, offset_x, offset_y, grid_size_x, grid_size_y): | |
| if crop == Entities.Cactus: | |
| return prepare_cacti(index, offset_x, offset_y, grid_size_x, grid_size_y) | |
| if crop == Entities.Pumpkin: | |
| return prepare_pumpkins(index, offset_x, offset_y, grid_size_x, grid_size_y) | |
| if crop == Entities.Dinosaur: | |
| return prepare_dinosaur(index, offset_x, offset_y, grid_size_x, grid_size_y) | |
| return False, offset_x, offset_y, grid_size_x, grid_size_y | |
| def harvest_phase(crop, index, offset_x, offset_y, grid_size_x, grid_size_y): | |
| if crop == Entities.Sunflower: | |
| harvest_sunflowers_optimally(offset_x, offset_y, grid_size_x, grid_size_y) | |
| return False | |
| if crop == Entities.Dinosaur: | |
| harvest_apples_optimally(index, offset_x, offset_y, grid_size_x, grid_size_y) | |
| return False | |
| if crop == Entities.Pumpkin or crop == Entities.Cactus: | |
| if index > 0: | |
| return True | |
| while num_drones() > 1: | |
| wait_n_seconds(1) | |
| harvest_if_possible() | |
| return False | |
| missed_cnt = 0 | |
| while True: | |
| water_if_needed() | |
| harvested = harvest_if_possible() | |
| if not harvested: | |
| missed_cnt += 1 | |
| if missed_cnt >= grid_size_x * grid_size_y // 2: | |
| break | |
| if harvested and crop == Entities.Cactus: | |
| break | |
| if not move_in_snake_way(offset_x, offset_y, grid_size_x, grid_size_y): | |
| navigate_to_position(offset_x, offset_y) | |
| break | |
| return False | |
| ## TASKING | |
| def drone_task(index, do_maze, offset_x, offset_y, grid_size_x, grid_size_y): | |
| quick_print( | |
| get_time(), | |
| "Drone:", index, | |
| "is starting task at area:", offset_x, offset_y, grid_size_x, grid_size_y | |
| ) | |
| find_fashionable_hat(index) | |
| # Maze | |
| if do_maze: | |
| maze_phase(index, offset_x, offset_y) | |
| return | |
| # Crop selection | |
| crop = decide_what_to_plant(True) | |
| quick_print(get_time(), "Drone:", index, "selected crop:", crop) | |
| # Companion planting phase | |
| companion_planting_phase(crop, offset_x, offset_y, grid_size_x, grid_size_y) | |
| # Preparation phase | |
| early_exit, offset_x, offset_y, grid_size_x, grid_size_y = harvest_preparation_phase( | |
| crop, index, offset_x, offset_y, grid_size_x, grid_size_y | |
| ) | |
| if early_exit: | |
| return | |
| # Harvest phase | |
| early_exit = harvest_phase(crop, index, offset_x, offset_y, grid_size_x, grid_size_y) | |
| if early_exit: | |
| return | |
| quick_print(get_time(), "Harvested:", crop) | |
| ## MAIN | |
| def main_loop(once=False): | |
| if OVERRIDE_WORLD_SIZE != "none": | |
| set_world_size(OVERRIDE_WORLD_SIZE) | |
| if OVERRIDE_EXECUTION_SPEED != "none": | |
| set_execution_speed(OVERRIDE_EXECUTION_SPEED) | |
| clear() | |
| consecutive_mazes = 0 | |
| while True: | |
| automate_upgrades() | |
| guess = 1 - random() | |
| probability = calculate_maze_probability() / (consecutive_mazes + 1) | |
| do_maze = guess < probability | |
| if do_maze: | |
| consecutive_mazes += 1 | |
| else: | |
| consecutive_mazes = 0 | |
| if OVERRIDE_CROP_SELECTION != "none": | |
| do_maze = False | |
| num_drones_to_spawn = max_num_drones() | |
| quick_print( | |
| get_time(), | |
| "Starting application.", | |
| "Doing maze:", do_maze, "guess was:", guess, "probability:", probability, | |
| "Number of drones to spawn:", num_drones_to_spawn | |
| ) | |
| def create_drone_task(idx, ox, oy, gx, gy): | |
| def task(): | |
| navigate_to_position(ox, oy) | |
| quick_print(get_time(), "Drone", idx, "arrived at:", get_pos_x(), get_pos_y()) | |
| drone_task(idx, do_maze, ox, oy, gx, gy) | |
| return task | |
| # Spawn drones for different grid sections | |
| size = get_world_size() | |
| spawned_drones = [] | |
| for j in range(num_drones_to_spawn - num_drones()): | |
| drone_idx = j + 1 # Start from 1, main thread is 0 | |
| offset_x, offset_y, grid_x, grid_y = get_drone_grid_assignment( | |
| drone_idx, size, num_drones_to_spawn | |
| ) | |
| drone = spawn_drone(create_drone_task(drone_idx, offset_x, offset_y, grid_x, grid_y)) | |
| if drone: | |
| quick_print(get_time(), "Spawned drone", drone_idx, "at section", offset_x, offset_y, grid_x, grid_y) | |
| spawned_drones.append(drone) | |
| # Main thread handles its own section | |
| offset_x, offset_y, grid_x, grid_y = get_drone_grid_assignment( | |
| 0, size, num_drones_to_spawn | |
| ) | |
| create_drone_task(0, offset_x, offset_y, grid_x, grid_y)() | |
| # wait for drones | |
| for drone in spawned_drones[:]: | |
| wait_for(drone) | |
| spawned_drones.remove(drone) | |
| if once: | |
| break | |
| ## EXEC | |
| main_loop(False) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment