diff --git a/robot/__init__.py b/robot/__init__.py index cf913f6..7c45a0c 100644 --- a/robot/__init__.py +++ b/robot/__init__.py @@ -29,7 +29,7 @@ MARKER, BASE_MARKER, ARENA_MARKER, - POTATO_MARKER, + TARGET_MARKER, MARKER_TYPE, TEAM ) diff --git a/robot/apriltags3.py b/robot/apriltags3.py index 5d20c71..4169a71 100755 --- a/robot/apriltags3.py +++ b/robot/apriltags3.py @@ -21,7 +21,7 @@ import numpy as np import scipy.spatial.transform as transform -from robot.game_config import MARKER +from robot.game_config import MARKER, MARKER_TYPE ###################################################################### @@ -435,7 +435,7 @@ def detect(self, img, estimate_tag_pose=False, camera_params=None): if camera_params is None: raise ValueError( "camera_params must be provided to detect if estimate_tag_pose is set to True") - tag_size = MARKER(tag.id).size + tag_size = MARKER.get_size(tag.id) camera_fx, camera_fy, camera_cx, camera_cy = [ c for c in camera_params] diff --git a/robot/game_config/__init__.py b/robot/game_config/__init__.py index c84d70c..f97ba60 100644 --- a/robot/game_config/__init__.py +++ b/robot/game_config/__init__.py @@ -1,9 +1,10 @@ from .teams import TEAM +from .targets import TARGET_TYPE from .markers import ( MARKER, MARKER_TYPE, ARENA_MARKER, - POTATO_MARKER, + TARGET_MARKER, BASE_MARKER, ) from .startup_poems import POEM_ON_STARTUP @@ -18,8 +19,9 @@ __all__ = ( "TEAM", + "TARGET_TYPE", "MARKER", - "POTATO_MARKER", + "TARGET_MARKER", "MARKER_TYPE", "BASE_MARKER", "ARENA_MARKER", diff --git a/robot/game_config/markers.py b/robot/game_config/markers.py index 7fd870a..5a7d5fa 100644 --- a/robot/game_config/markers.py +++ b/robot/game_config/markers.py @@ -2,6 +2,7 @@ import typing from .teams import TEAM +from .targets import TARGET_TYPE """ Hiiii! @@ -13,20 +14,24 @@ Byee! - Holly (2023-2024) + +Don't know what to say. - Nathan Gill (2024 - 2025) +No semi-colons please - Scott Wilson (2024 - 2025) + [Put your future messages here] """ class MARKER_TYPE(enum.Enum): # Keep something like this to determine if a marker is a wall or not. - POTATO = enum.auto() + TARGET = enum.auto() ARENA = enum.auto() -class BASE_MARKER: # Base marker class that POTATO_MARKER and ARENA_MARKER derive from. +class BASE_MARKER: # Base marker class that TARGET_MARKER and ARENA_MARKER derive from. team_marker_colors: dict = { # Colour definitions for each team defined in TEAMS - TEAM.RUSSET: (255, 64, 0), # RED - TEAM.SWEET: (255, 255, 32), # YELLOW - TEAM.MARIS_PIPER: (50,255,0), # GREEN - TEAM.PURPLE: (255, 32, 255), # PURPLE + TEAM.RUBY: (0, 0, 255), # RED + TEAM.JADE: (0, 255, 0), # GREEN + TEAM.TOPAZ: (0, 255, 255), # YELLOW + TEAM.DIAMOND: (255, 0, 0), # BLUE } def __init__( @@ -37,20 +42,21 @@ def __init__( self.id = id self.type = type self.owning_team: typing.Union[TEAM, None] = None + self.target_type: typing.Union[TARGET_TYPE, None] = None # Sizes are in meters self.size = 0.2 if self.type == MARKER_TYPE.ARENA else 0.08 def __repr__(self) -> str: - return f"" + return f"" @property def bounding_box_color(self) -> tuple: if self.type == MARKER_TYPE.ARENA: # If it is a wall - return tuple(reversed((125, 249, 225))) # Turquoise - elif self.owning_team==TEAM.ARENA: # If it is a Hot Potato (game object owned by ARENA) + return tuple(reversed((225, 249, 125))) # Turquoise + elif self.owning_team==TEAM.ARENA: # If it is a Sheep (game object owned by ARENA) return tuple(reversed((255,255,255))) # White - else: # If it is a Jacket Potato (game object owned by a team.) + else: # If it is a Jewel (game object owned by a team.) return tuple(reversed(self.team_marker_colors[self.owning_team])) # Picks the team colour from above class ARENA_MARKER(BASE_MARKER): # Not much going on here. This represents a wall. @@ -61,17 +67,25 @@ def __repr__(self) -> str: return f"" -class POTATO_MARKER(BASE_MARKER): # This is a game object rather than a wall. Add properties you want to keep track of +class TARGET_MARKER(BASE_MARKER): # This is a game object rather than a wall. Add properties you want to keep track of def __init__( - self, id: int, owner: TEAM + self, id: int, owner: TEAM, target_type: TARGET_TYPE ) -> None: - super().__init__(id, MARKER_TYPE.POTATO) + super().__init__(id, MARKER_TYPE.TARGET) self.owning_team = owner + self.target_type = target_type def __repr__(self) -> str: - return f"" + return f"" class MARKER(BASE_MARKER): # This is literally just how the code gets the different marker types. + @staticmethod + def get_size(id: int): + tg = MARKER.by_id(id) + if tg.owning_team == TEAM["ARENA"]: + return 0.2 + return 0.08 + @staticmethod def by_id(id: int, team: typing.Union[TEAM, None] = None) -> BASE_MARKER: # team is currently unused, but it is referenced throughout the code. It is the team of the robot I believe (check this) """ @@ -95,16 +109,36 @@ def by_id(id: int, team: typing.Union[TEAM, None] = None) -> BASE_MARKER: # team spaced as in 2021-2022's competition (6 markers on each side of the Arena, the first 50cm away from the wall and each subsequent marker 1m away from there). In practice these markers start at 100. + + + As of 2025: Sheep have IDs 0-23 inclusive; Jewels have IDs 24-31 inclusive; Lair IDs are 50-53 inclusive + """ - ARENA_WALL_LOWER_ID = 40 - if id >= ARENA_WALL_LOWER_ID: + if id > 99: return ARENA_MARKER(id) - - wrappingId = id % 20 # Make sure that the ID range wraps after 20 values. - if wrappingId<4: # If it is a Jacket Potato (has a team) - owning_team = TEAM[f"T{wrappingId}"] # Set to the corresponding TEAM enum. - else: # If it is a Hot Potato (Has no team) - owning_team = TEAM["ARENA"] - return POTATO_MARKER(id, owning_team) + if id < 24: + owning_team = TEAM["ARENA"] + elif id == 30 or id == 31 or id == 53: + owning_team = TEAM["T3"] + elif id == 28 or id == 29 or id == 52: + owning_team = TEAM["T2"] + elif id == 26 or id == 27 or id == 51: + owning_team = TEAM["T1"] + elif id == 24 or id == 25 or id == 50: + owning_team = TEAM["T0"] + else: + print(f"Marker ID {id} is not defined.") + owning_team = TEAM["NONE"] + + if id >= 50 and id <= 53: # lair + target_type = TARGET_TYPE["T2"] + elif id >= 0 and id <= 23: # sheep + target_type = TARGET_TYPE["T0"] + elif id >= 24 and id <= 31: # jewel + target_type = TARGET_TYPE["T1"] + else: + target_type = TARGET_TYPE["NONE"] + + return TARGET_MARKER(id, owning_team, target_type) \ No newline at end of file diff --git a/robot/game_config/startup_poems.py b/robot/game_config/startup_poems.py index 2b9ccde..6e14a51 100644 --- a/robot/game_config/startup_poems.py +++ b/robot/game_config/startup_poems.py @@ -1,17 +1,20 @@ from random import randint class POEM_ON_STARTUP: + ## These jokes are terrible, (ChatGPT 4o Generated) jokes = [ - "Why did the potato cross the road? \ - He saw a fork up ahead.", - "What do you say to a baked potato that's angry? \ - Anything you like, just butter it up.", - "Funny Potato Joke", - "Farm Facts: There are around 5000 different varieties of potato", - "Farm Facts: Potatoes were first domesticated in Peru around 4500 years ago around Lake Titicaca", - "Farm Facts: The word potato originates from the Taino \"batata\", meaning sweet potato.", - "Farm Facts: China is the leading producer of potatoes, with 94.3 million tonnes produced in 2021", - "Farm Facts: The maximum theoretical voltage " + "Why don't dragons tell secrets? \ + Because they always breathe fire!", + "Why did the ruby go to school? \ + To become a little more polished!", + "Why do sheep make terrible detectives? \ + Because they always follow the herd!", + "What's a dragon's favourite type of exercise? \ + Flame-ups!", + "What did the diamond say to the ruby at the party? \ + You're looking radiant tonight!", + "Why was the sheep so quiet? \ + Because it was feeling a little wool-gathered!", ] @staticmethod diff --git a/robot/game_config/targets.py b/robot/game_config/targets.py new file mode 100644 index 0000000..28cbce7 --- /dev/null +++ b/robot/game_config/targets.py @@ -0,0 +1,19 @@ +import enum + +""" +Defines each target type, for example sheep, lair or gem. +""" +class TARGET_TYPE(enum.Enum): + + ## These easter eggs are locations that the corresponding gems were found (ChatGPT 4o Generated) + SHEEP = "Baa" # This matches T0, for example. + GEM = "Shiny" + LAIR = "Dangerous" + # There is no T value for ARENA, so there is no way that the assignment of team to a marker can accidentally assign ARENA if the logic goes wrong. + + NONE = "NONE" ## This is only used when no other owning team is available for undefined IDs, this should never actually happen in-game. + + T0 = "Baa" + T1 = "Shiny" + T2 = "Dangerous" + diff --git a/robot/game_config/teams.py b/robot/game_config/teams.py index 23e04bf..3e26edd 100644 --- a/robot/game_config/teams.py +++ b/robot/game_config/teams.py @@ -6,15 +6,19 @@ that the school will be competing as. """ class TEAM(enum.Enum): - RUSSET = "Solanum Tuberosum 'Ranger Russet'" # This matches T0, for example. - SWEET = "Ipomoea batatas" - MARIS_PIPER = "Solanum Tuberosum 'Maris Piper'" - PURPLE = "Solanum Tuberosum 'Vitolette'" - ARENA = "HOTTTTT!" + + ## These easter eggs are locations that the corresponding gems were found (ChatGPT 4o Generated) + RUBY = "Mogok Valley, Myanmar (Burma)" # This matches T0, for example. + JADE = "Hetian (Hotan), Xinjiang, China" + TOPAZ = "St. John's Island (Zabargad Island), Egypt" + DIAMOND = "Golconda, India" + ARENA = "Nothing!" # There is no T value for ARENA, so there is no way that the assignment of team to a marker can accidentally assign ARENA if the logic goes wrong. - T0 = "Solanum Tuberosum 'Ranger Russet'" - T1 = "Ipomoea batatas" - T2 = "Solanum Tuberosum 'Maris Piper'" - T3 = "Solanum Tuberosum 'Vitolette'" + NONE = "NONE" ## This is only used when no other owning team is available for undefined IDs, this should never actually happen in-game. + + T0 = "Mogok Valley, Myanmar (Burma)" + T1 = "Hetian (Hotan), Xinjiang, China" + T2 = "St. John's Island (Zabargad Island), Egypt" + T3 = "Golconda, India" diff --git a/robot/vision.py b/robot/vision.py index cd86be1..9ffa7c0 100755 --- a/robot/vision.py +++ b/robot/vision.py @@ -359,14 +359,14 @@ def _draw_bounding_box(self, frame, detections): """ polygon_is_closed = True for detection in detections: - marker_info = MARKER(detection.id, self.zone) + marker_info = MARKER.by_id(detection.id, self.zone) marker_info_colour = marker_info.bounding_box_color marker_code = detection.id # The reverse is because OpenCV expects BGR but we use RGB - colour = reversed(marker_info_colour + colour = tuple(reversed(marker_info_colour if marker_info_colour is not None - else DEFAULT_BOUNDING_BOX_COLOUR) + else DEFAULT_BOUNDING_BOX_COLOUR)) # need to have this EXACT integer_corners syntax due to opencv bug # https://stackoverflow.com/questions/17241830/ diff --git a/robot/wrapper.py b/robot/wrapper.py index cd400ce..08e5972 100644 --- a/robot/wrapper.py +++ b/robot/wrapper.py @@ -67,7 +67,7 @@ def __init__(self, start_enable_5v = True, ): - self.zone = game_config.TEAM.RUSSET + self.zone = game_config.TEAM.RUBY self.mode = "competition" self._max_motor_voltage = max_motor_voltage