import operator import logging import random # Logging to console logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO) # Set loglevel to INFO or higher to prevent console spam. """ Helper functions """ # Return some hex colour """ Waiting for Stanford to fix a bug in the environment CIP bug: https://codeinplace.stanford.edu/cip6/report?post=ac7c4ffd-1f90-475a-b4ee-2e5b73c5348f from matplotlib import colors import decimal def get_random_colour(): return decimal.Decimal(random.randrange(0,10)/10), decimal.Decimal(random.randrange(0,10)/10), decimal.Decimal(random.randrange(0,10)/10) colour = colors.rgb2hex((get_random_colour())) background = colors.rgb2hex((get_random_colour())) """ def generate_random_colour(): rgb_values = [random.randrange(256), random.randrange(256), random.randrange(256)] hex_colours = [hex(value)[2:] for value in rgb_values] # Strip the '0x' prefix for i in range(len(hex_colours)): if len(str(hex_colours[i])) == 1: hex_colours[i] = f"0{hex_colours[i]}" return '#' + ''.join(hex_colours) """ Karel functions """ # Erase a passed Karel from the canvas def erase_karel(karel): canvas = karel[1][0] for shape in karel[0].values(): canvas.delete(shape) logger.debug(f"Erased karel: {karel}") # Move a passed Karel on the canvas """ Waiting for Stanford to fix a bug in the environment CIP bug: https://codeinplace.stanford.edu/cip6/report?post=7043ece9-7653-4e39-8a63-5f4544b1d74b def move_karel(canvas, karel, x, y): for shape in karel[0].values(): canvas.move(shape, x, y) logger.debug(f"Moved: {karel} by {x} horizontally, {y} vertically") """ def move_karel(karel, x:int=0, y:int=0): # Update coordinates in list karel[1][1] += x karel[1][2] += y erase_karel(karel) new_karel = draw_karel(karel[1][0], karel[1][1], karel[1][2], karel[1][3], karel[1][4], karel[1][5], karel[1][6], karel[1][7]) logger.debug(f"Moved: {karel} by {x} horizontally, {y} vertically, to {new_karel}") return new_karel # Change the orientation of a passed Karel def rotate_karel(karel, direction): # Relative turn if direction == "right" or direction == "left": # East if karel[1][4] == "east": if direction == "right": karel[1][4] = "south" print(1) if direction == "left": karel[1][4] = "north" elif karel[1][4] == "east-flipped": if direction == "right": karel[1][4] = "south-flipped" if direction == "left": karel[1][4] = "north-flipped" # North elif karel[1][4] == "north": if direction == "right": karel[1][4] = "east" if direction == "left": karel[1][4] = "west" elif karel[1][4] == "north-flipped": if direction == "right": karel[1][4] = "east-flipped" if direction == "left": karel[1][4] = "west-flipped" # West elif karel[1][4] == "west": if direction == "right": karel[1][4] = "north" if direction == "left": karel[1][4] = "soutch" elif karel[1][4] == "west-flipped": if direction == "right": karel[1][4] = "north-flipped" if direction == "left": karel[1][4] = "south-flipped" # South elif karel[1][4] == "south": if direction == "right": karel[1][4] = "west" if direction == "left": karel[1][4] = "east" elif karel[1][4] == "north-flipped": if direction == "right": karel[1][4] = "west-flipped" if direction == "left": karel[1][4] = "east-flipped" # Absolute rotation elif direction == "east" or direction == "east-flipped" or direction == "north" or direction == "north-flipped" or direction == "west" or direction == "west-flipped" or direction == "south" or direction == "south-flipped": karel[1][4] = direction # Syntax error else: logger.error(f"Invalid rotation direction: {direction}") erase_karel(karel) new_karel = draw_karel(karel[1][0], karel[1][1], karel[1][2], karel[1][3], karel[1][4], karel[1][5], karel[1][6], karel[1][7]) logger.debug(f"Rotated Karel: {karel} by {direction} as {karel[1][4]} to {new_karel}") return new_karel # Recolour a passed Karel on the canvas """ Waiting for Stanford to fix a bug in the environment CIP bug: https://codeinplace.stanford.edu/cip6/report?post=c36ed931-3019-4830-8ba6-655c1a513471 def recolour_karel(canvas, karel, colour:str="black", background:str="white"): for name, shape in karel[0].items(): # Loop over Karel dict if name.endswith("_fill"): # Background shape canvas.set_color(shape, background) canvas.set_outline_color(shape, background) else: # Foreground shape if name.endswith("_line") or name.endswith("_corner") or name == "mouth": canvas.set_color(shape, colour) elif name == "eye": #canvas.set_color(shape, "transparent") canvas.set_outline_color(shape, colour) else: # Legs and feet canvas.set_color(shape, colour) canvas.set_outline_color(shape, colour) """ def recolour_karel(karel, colour:str="black", background:str="white"): # Random colours if colour == "random": colour = generate_random_colour() if background == "random": background = generate_random_colour() # Update colours in list karel[1][5] = colour karel[1][6] = background erase_karel(karel) new_karel = draw_karel(karel[1][0], karel[1][1], karel[1][2], karel[1][3], karel[1][4], karel[1][5], karel[1][6], karel[1][7]) logger.debug(f"Re-coloured Karel: {karel} with {colour} and {background} to {new_karel}") return new_karel # Draw a Karel def draw_karel( canvas, centre_x:int=25, centre_y:int=25, size:int=50, orientation:str="east", colour:str="black", background:str="white", transparent:bool=False ): # Body constants MARGIN = size / 8 APPENDAGE_MULTIPLIER = MARGIN * 0.6 # Random colours if colour == "random": colour = generate_random_colour() if background == "random": background = generate_random_colour() ''' Flipper case In order to be able to flip Karel, the operands in the forumlas must be able to switch around. Orientations are in relation to the centre of Karel. ''' match orientation.lower(): case "east" | "south-flipped": left_operand = operator.sub # Left / Top top_operand = operator.sub # Top / Left right_operand = operator.add # Right / Bottom bottom_operand = operator.add # Bottom / Right case "east-flipped" | "south": left_operand = operator.sub # Left / Top top_operand = operator.add # Bottom / Right right_operand = operator.add # Right / Bottom bottom_operand = operator.sub # Top / Left case "west" | "north-flipped": left_operand = operator.add # Right top_operand = operator.add # Bottom right_operand = operator.sub # Left bottom_operand = operator.sub # Top case "west-flipped" | "north": left_operand = operator.add # Right / Bottom top_operand = operator.sub # Top / Left right_operand = operator.sub # Left / Top bottom_operand = operator.add # Bottom / Right # Coords case if orientation.lower() == "north" or orientation.lower() == "north-flipped" or orientation.lower() == "south" or orientation.lower() == "south-flipped": temp_x = centre_x centre_x = centre_y centre_y = temp_x #pass # Borders left = left_operand(centre_x, size / 2) top = top_operand(centre_y, size / 2) right = right_operand(centre_x, size / 2) bottom = bottom_operand(centre_y, size / 2) margin = size / 8 appendage_multiplier = margin * 0.6 # Body borders body_left = left_operand(centre_x, size / 3) body_top = top body_right = right_operand(centre_x, size / 3) body_bottom = bottom_operand(centre_y, size / 2.6) eye_left = right_operand(body_left, margin) eye_top = bottom_operand(body_top, margin) eye_right = left_operand(body_right, margin) eye_bottom = top_operand(body_bottom, margin * 2) # Body coordinates top_left_corner = body_left, body_top left_diagonal_top = body_left, top_operand(body_bottom, margin) left_diagonal_bottom = eye_left, body_bottom right_diagonal_top = eye_right, body_top right_diagonal_bottom = body_right, bottom_operand(body_top, margin) bottom_right_corner = body_right, body_bottom mouth_left_corner = centre_x, bottom_operand(eye_bottom, margin) mouth_right_corner = eye_right, mouth_left_corner[1] # Left appendage borders leftLeg_left = left_operand(body_left, margin) leftLeg_top = eye_bottom leftLeg_right = body_left leftLeg_bottom = bottom_operand(leftLeg_top, appendage_multiplier) leftFoot_left = leftLeg_left leftFoot_top = leftLeg_bottom leftFoot_right = right_operand(leftLeg_left, appendage_multiplier) leftFoot_bottom = bottom_operand(leftFoot_top, appendage_multiplier) # Right appendage borders rightLeg_left = centre_x rightLeg_top = body_bottom rightLeg_right = right_operand(rightLeg_left, appendage_multiplier) rightLeg_bottom = bottom rightFoot_left = rightLeg_right rightFoot_top = top_operand(bottom, appendage_multiplier) rigthFoot_right = right_operand(rightLeg_right, appendage_multiplier) rightFoot_bottom = bottom # Draw Karel match orientation.lower(): case "east" | "east-flipped" | "west" | "west-flipped": if not transparent: # Meat top_fill = canvas.create_rectangle( top_left_corner[0], top_left_corner[1], right_diagonal_top[0], eye_top, background, background ) top_corner_fill = canvas.create_polygon( right_diagonal_top[0] , right_diagonal_top[1], right_diagonal_bottom[0], right_diagonal_bottom[1], eye_right, eye_top, color = background, outline = background ) left_fill = canvas.create_rectangle( top_left_corner[0], eye_top, eye_left, left_diagonal_top[1], background, background ) right_fill = canvas.create_rectangle( eye_right, eye_top, right_diagonal_bottom[0], eye_bottom, background, background ) bottom_fill = canvas.create_rectangle( eye_left, eye_bottom, bottom_right_corner[0], bottom_right_corner[1], background, background ) bottom_corner_fill = canvas.create_polygon( left_diagonal_top[0], left_diagonal_top[1], eye_left, left_diagonal_top[1], left_diagonal_bottom[0], left_diagonal_bottom[1], color = background, outline = background ) # Outlines top_line = canvas.create_line( top_left_corner[0], top_left_corner[1], right_diagonal_top[0], right_diagonal_top[1], colour ) top_corner = canvas.create_line( right_diagonal_top[0], right_diagonal_top[1], right_diagonal_bottom[0], right_diagonal_bottom[1], colour ) left_line = canvas.create_line( top_left_corner[0], top_left_corner[1], left_diagonal_top[0], left_diagonal_top[1], colour ) bottom_corner = canvas.create_line( left_diagonal_top[0], left_diagonal_top[1], left_diagonal_bottom[0], left_diagonal_bottom[1], colour ) bottom_line = canvas.create_line( left_diagonal_bottom[0], left_diagonal_bottom[1], bottom_right_corner[0], bottom_right_corner[1], colour ) right_line = canvas.create_line( right_diagonal_bottom[0], right_diagonal_bottom[1], bottom_right_corner[0], bottom_right_corner[1], colour ) left_leg = canvas.create_rectangle( leftLeg_left, leftLeg_top, leftLeg_right, leftLeg_bottom, colour, colour ) left_foot = canvas.create_rectangle( leftFoot_left, leftFoot_top, leftFoot_right, leftFoot_bottom, colour, colour ) right_leg = canvas.create_rectangle( rightLeg_left, rightLeg_top, rightLeg_right, rightLeg_bottom, colour, colour ) right_foot = canvas.create_rectangle( rightFoot_left, rightFoot_top, rigthFoot_right, rightFoot_bottom, colour, colour ) eye = canvas.create_rectangle( eye_left, eye_top, eye_right, eye_bottom, "transparent", colour, ) mouth = canvas.create_line( mouth_left_corner[0], mouth_left_corner[1], mouth_right_corner[0], mouth_right_corner[1], colour ) case "north" | "north-flipped" | "south" | "south-flipped": if not transparent: # Meat top_fill = canvas.create_rectangle( top_left_corner[1], # Top Y right_diagonal_top[0], # Right X eye_top, # Bottom Y top_left_corner[0], # Left X background, background ) top_corner_fill = canvas.create_polygon( right_diagonal_top[1] , right_diagonal_top[0], right_diagonal_bottom[1], right_diagonal_bottom[0], eye_top, eye_right, color = background, outline = background ) left_fill = canvas.create_rectangle( eye_top, eye_left, left_diagonal_top[1], top_left_corner[0], background, background ) right_fill = canvas.create_rectangle( eye_top, right_diagonal_bottom[0], eye_bottom, eye_right, background, background ) bottom_fill = canvas.create_rectangle( eye_bottom, bottom_right_corner[0], bottom_right_corner[1], eye_left, background, background ) bottom_corner_fill = canvas.create_polygon( left_diagonal_top[1], left_diagonal_top[0], left_diagonal_top[1], eye_left, left_diagonal_bottom[1], left_diagonal_bottom[0], color = background, outline = background ) # Outlines top_line = canvas.create_line( top_left_corner[1], top_left_corner[0], right_diagonal_top[1], right_diagonal_top[0], colour ) top_corner = canvas.create_line( right_diagonal_top[1], right_diagonal_top[0], right_diagonal_bottom[1], right_diagonal_bottom[0], colour ) left_line = canvas.create_line( top_left_corner[1], top_left_corner[0], left_diagonal_top[1], left_diagonal_top[0], colour ) bottom_corner = canvas.create_line( left_diagonal_top[1], left_diagonal_top[0], left_diagonal_bottom[1], left_diagonal_bottom[0], colour ) bottom_line = canvas.create_line( left_diagonal_bottom[1], left_diagonal_bottom[0], bottom_right_corner[1], bottom_right_corner[0], colour ) right_line = canvas.create_line( right_diagonal_bottom[1], right_diagonal_bottom[0], bottom_right_corner[1], bottom_right_corner[0], colour ) left_leg = canvas.create_rectangle( leftLeg_top, leftLeg_right, leftLeg_bottom, leftLeg_left, colour, colour ) left_foot = canvas.create_rectangle( leftFoot_top, leftFoot_right, leftFoot_bottom, leftFoot_left, colour, colour ) right_leg = canvas.create_rectangle( rightLeg_top, rightLeg_right, rightLeg_bottom, rightLeg_left, colour, colour ) right_foot = canvas.create_rectangle( rightFoot_top, rigthFoot_right, rightFoot_bottom, rightFoot_left, colour, colour ) eye = canvas.create_rectangle( eye_top, eye_right, eye_bottom, eye_left, "transparent", colour, ) mouth = canvas.create_line( mouth_left_corner[1], mouth_left_corner[0], mouth_right_corner[1], mouth_right_corner[0], colour ) # Return each object so it can later be altered/destroyed if transparent: shapes = { "top_line": top_line, "top_corner": top_corner, "left_line": left_line, "bottom_corner": bottom_corner, "bottom_line": bottom_line, "right_line": right_line, "left_leg": left_leg, "left_foot": left_foot, "right_foot": right_foot, "right_leg": right_leg, "right_foot": right_foot, "eye": eye, "mouth": mouth } else: # Not transparent shapes = { "top_fill": top_fill, "top_corner_fill": top_corner_fill, "left_fill": left_fill, "right_fill": right_fill, "bottom_fill": bottom_fill, "bottom_corner_fill": bottom_corner_fill, "top_line": top_line, "top_corner": top_corner, "left_line": left_line, "bottom_corner": bottom_corner, "bottom_line": bottom_line, "right_line": right_line, "left_leg": left_leg, "left_foot": left_foot, "right_foot": right_foot, "right_leg": right_leg, "right_foot": right_foot, "eye": eye, "mouth": mouth } arguments = [canvas, centre_x, centre_y, size, orientation, colour, background, transparent] logger.debug(f"Created Karel: {shapes, arguments}") return [shapes, arguments]