Select adjacent crops first
This commit is contained in:
@@ -280,4 +280,5 @@ class JobStates(LegacyJobStates):
|
|||||||
self.g.path = []
|
self.g.path = []
|
||||||
self.g.look_at = None
|
self.g.look_at = None
|
||||||
self.gather_crop_states.state = self.gather_crop_states.idle
|
self.gather_crop_states.state = self.gather_crop_states.idle
|
||||||
|
self.gather_crop_states.active_section_anchor = None
|
||||||
self.state = self.idle
|
self.state = self.idle
|
||||||
|
|||||||
@@ -23,6 +23,61 @@ class GatherCropStates:
|
|||||||
def init(self):
|
def init(self):
|
||||||
self.state = self.find_new_crop
|
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):
|
def find_new_crop(self):
|
||||||
print('Finding new crop...')
|
print('Finding new crop...')
|
||||||
w = self.g.world
|
w = self.g.world
|
||||||
@@ -36,17 +91,23 @@ class GatherCropStates:
|
|||||||
]
|
]
|
||||||
|
|
||||||
candidates = w.find_blocks_indexed(p, mature_crops, 50)
|
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 is None:
|
||||||
if crop not in self.bad_crops:
|
|
||||||
break
|
|
||||||
else: # for
|
|
||||||
print('No good crops left; retrying after a short wait.')
|
print('No good crops left; retrying after a short wait.')
|
||||||
self.retry_after = time.monotonic() + 15.0
|
self.retry_after = time.monotonic() + 15.0
|
||||||
self.state = self.wait_for_crops
|
self.state = self.wait_for_crops
|
||||||
return
|
return
|
||||||
|
|
||||||
print('Found crop:', crop)
|
print('Found crop:', crop,
|
||||||
|
'(same-species section size={})'.format(section_size))
|
||||||
self.crop = crop
|
self.crop = crop
|
||||||
self.type_id = w.block_at(*crop)
|
self.type_id = w.block_at(*crop)
|
||||||
self.state = self.nav_to_crop
|
self.state = self.nav_to_crop
|
||||||
@@ -178,6 +239,7 @@ class GatherCropStates:
|
|||||||
self.target_seed = None
|
self.target_seed = None
|
||||||
self.seed_deadline = None
|
self.seed_deadline = None
|
||||||
self.seed_wait_logged = False
|
self.seed_wait_logged = False
|
||||||
|
self.active_section_anchor = None
|
||||||
self.retry_after = 0.0
|
self.retry_after = 0.0
|
||||||
self.bad_crops = []
|
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