Select adjacent crops first
This commit is contained in:
@@ -280,4 +280,5 @@ class JobStates(LegacyJobStates):
|
||||
self.g.path = []
|
||||
self.g.look_at = None
|
||||
self.gather_crop_states.state = self.gather_crop_states.idle
|
||||
self.gather_crop_states.active_section_anchor = None
|
||||
self.state = self.idle
|
||||
|
||||
@@ -23,6 +23,61 @@ class GatherCropStates:
|
||||
def init(self):
|
||||
self.state = self.find_new_crop
|
||||
|
||||
def _select_crop(self, candidates, crop_types, center):
|
||||
"""Stay with a connected same-species patch, nearest patch first."""
|
||||
adjacent_offsets = ((1, 0, 0), (-1, 0, 0),
|
||||
(0, 0, 1), (0, 0, -1))
|
||||
remaining = set(crop_types)
|
||||
component_for = {}
|
||||
component_sizes = {}
|
||||
component_id = 0
|
||||
|
||||
while remaining:
|
||||
seed = remaining.pop()
|
||||
species = crop_types[seed]
|
||||
stack = [seed]
|
||||
component = [seed]
|
||||
component_for[seed] = component_id
|
||||
while stack:
|
||||
x, y, z = stack.pop()
|
||||
for dx, dy, dz in adjacent_offsets:
|
||||
adjacent = (x + dx, y + dy, z + dz)
|
||||
if (adjacent in remaining and
|
||||
crop_types[adjacent] == species):
|
||||
remaining.remove(adjacent)
|
||||
component_for[adjacent] = component_id
|
||||
component.append(adjacent)
|
||||
stack.append(adjacent)
|
||||
component_sizes[component_id] = len(component)
|
||||
component_id += 1
|
||||
|
||||
eligible = [position for position in candidates
|
||||
if position not in self.bad_crops and
|
||||
position in component_for]
|
||||
if not eligible:
|
||||
return None, 0
|
||||
|
||||
active_component = component_for.get(self.active_section_anchor)
|
||||
active_candidates = [position for position in eligible
|
||||
if component_for[position] == active_component]
|
||||
if active_candidates:
|
||||
section_candidates = active_candidates
|
||||
else:
|
||||
# Begin with the nearest available patch. Once selected, keep its
|
||||
# connected component active until no mature crops remain there.
|
||||
first_crop = min(eligible, key=lambda position: (
|
||||
utils.phyp(center, position), position))
|
||||
selected_component = component_for[first_crop]
|
||||
section_candidates = [
|
||||
position for position in eligible
|
||||
if component_for[position] == selected_component
|
||||
]
|
||||
|
||||
crop = min(section_candidates, key=lambda position: (
|
||||
utils.phyp(center, position), position))
|
||||
self.active_section_anchor = crop
|
||||
return crop, component_sizes[component_for[crop]]
|
||||
|
||||
def find_new_crop(self):
|
||||
print('Finding new crop...')
|
||||
w = self.g.world
|
||||
@@ -36,17 +91,23 @@ class GatherCropStates:
|
||||
]
|
||||
|
||||
candidates = w.find_blocks_indexed(p, mature_crops, 50)
|
||||
crop_ids = (blocks.WHEAT_IDS | blocks.POTATO_IDS |
|
||||
blocks.CARROT_IDS | blocks.BEETROOT_IDS)
|
||||
crop_positions = w.find_blocks_indexed(p, crop_ids, 50)
|
||||
crop_types = {
|
||||
position: blocks.BLOCKS.get(w.block_at(*position))
|
||||
for position in crop_positions
|
||||
}
|
||||
crop, section_size = self._select_crop(candidates, crop_types, p)
|
||||
|
||||
for crop in candidates:
|
||||
if crop not in self.bad_crops:
|
||||
break
|
||||
else: # for
|
||||
if crop is None:
|
||||
print('No good crops left; retrying after a short wait.')
|
||||
self.retry_after = time.monotonic() + 15.0
|
||||
self.state = self.wait_for_crops
|
||||
return
|
||||
|
||||
print('Found crop:', crop)
|
||||
print('Found crop:', crop,
|
||||
'(same-species section size={})'.format(section_size))
|
||||
self.crop = crop
|
||||
self.type_id = w.block_at(*crop)
|
||||
self.state = self.nav_to_crop
|
||||
@@ -178,6 +239,7 @@ class GatherCropStates:
|
||||
self.target_seed = None
|
||||
self.seed_deadline = None
|
||||
self.seed_wait_logged = False
|
||||
self.active_section_anchor = None
|
||||
self.retry_after = 0.0
|
||||
self.bad_crops = []
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import unittest
|
||||
|
||||
from munch import Munch
|
||||
|
||||
from mosfet.jobs.gather_crop import GatherCropStates
|
||||
|
||||
|
||||
class TestCropSectionPriority(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.job = GatherCropStates(Munch())
|
||||
self.job.bad_crops = []
|
||||
|
||||
def test_stays_in_active_patch_before_switching(self):
|
||||
wheat_a = (0, 64, 0)
|
||||
wheat_b = (1, 64, 0)
|
||||
carrot_a = (10, 64, 0)
|
||||
crop_types = {
|
||||
wheat_a: 'wheat',
|
||||
wheat_b: 'wheat',
|
||||
carrot_a: 'carrots',
|
||||
(10, 64, 1): 'carrots',
|
||||
(10, 64, -1): 'carrots',
|
||||
(11, 64, 0): 'carrots',
|
||||
}
|
||||
|
||||
first, first_size = self.job._select_crop(
|
||||
[wheat_a, carrot_a], crop_types, (0, 64, 0))
|
||||
self.assertEqual(first, wheat_a)
|
||||
self.assertEqual(first_size, 2)
|
||||
self.job.init()
|
||||
self.assertEqual(self.job.active_section_anchor, wheat_a)
|
||||
|
||||
# The carrot patch is now closer, but the bot should finish the wheat
|
||||
# patch it already selected first.
|
||||
next_crop, next_size = self.job._select_crop(
|
||||
[wheat_b, carrot_a], crop_types, (10, 64, 0))
|
||||
self.assertEqual(next_crop, wheat_b)
|
||||
self.assertEqual(next_size, 2)
|
||||
|
||||
# Once the active patch has no mature candidates, move to the next one.
|
||||
next_patch, next_patch_size = self.job._select_crop(
|
||||
[carrot_a], crop_types, (10, 64, 0))
|
||||
self.assertEqual(next_patch, carrot_a)
|
||||
self.assertEqual(next_patch_size, 4)
|
||||
|
||||
def test_unripe_same_species_blocks_keep_patch_connected(self):
|
||||
mature = (0, 64, 0)
|
||||
replanted = (1, 64, 0)
|
||||
isolated = (4, 64, 0)
|
||||
crop_types = {
|
||||
mature: 'wheat',
|
||||
replanted: 'wheat',
|
||||
isolated: 'wheat',
|
||||
}
|
||||
|
||||
crop, size = self.job._select_crop([mature, isolated], crop_types,
|
||||
(0, 64, 0))
|
||||
|
||||
self.assertEqual(crop, mature)
|
||||
self.assertEqual(size, 2)
|
||||
self.assertEqual(self.job.active_section_anchor, mature)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user