karel_asset.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. import operator
  2. import logging
  3. import random
  4. # Logging to console
  5. logger = logging.getLogger(__name__)
  6. logging.basicConfig(level=logging.INFO) # Set loglevel to INFO or higher to prevent console spam.
  7. """
  8. Helper functions
  9. """
  10. # Return some hex colour
  11. """
  12. Waiting for Stanford to fix a bug in the environment
  13. CIP bug: https://codeinplace.stanford.edu/cip6/report?post=ac7c4ffd-1f90-475a-b4ee-2e5b73c5348f
  14. from matplotlib import colors
  15. import decimal
  16. def get_random_colour():
  17. return decimal.Decimal(random.randrange(0,10)/10), decimal.Decimal(random.randrange(0,10)/10), decimal.Decimal(random.randrange(0,10)/10)
  18. colour = colors.rgb2hex((get_random_colour()))
  19. background = colors.rgb2hex((get_random_colour()))
  20. """
  21. def generate_random_colour(sort:str="hex"):
  22. # Generate random RGB values
  23. rgb_values = [random.randrange(256), random.randrange(256), random.randrange(256)]
  24. if sort.lower() == "rgb":
  25. return rgb_values
  26. if sort.lower() == "print-ansi":
  27. return print(f"\033[38;2;{rgb_values[0]};{rgb_values[1]};{rgb_values[2]}m")
  28. if sort.lower() == "ansi":
  29. return f"\033[38;2;{rgb_values[0]};{rgb_values[1]};{rgb_values[2]}m"
  30. # Convert to HEX values
  31. hex_colours = [hex(value)[2:] for value in rgb_values] # Strip the '0x' prefix
  32. # Add leading "0" for single digit values
  33. for i in range(len(hex_colours)):
  34. if len(str(hex_colours[i])) == 1:
  35. hex_colours[i] = f"0{hex_colours[i]}"
  36. # Add leading "#" to hex values
  37. colour = '#' + ''.join(hex_colours)
  38. logger.debug(f"Generated random colour: {colour}")
  39. return colour
  40. # Base the maximum random size on the shortest pane
  41. def determine_random_size(canvas):
  42. if canvas.width < canvas.height:
  43. size = random.randint(1, canvas.width)
  44. else:
  45. size = random.randint(1, canvas.height)
  46. return size
  47. """
  48. Asset functions
  49. """
  50. # Draw a background with random colour on the canvas
  51. def generate_random_background(canvas):
  52. background = canvas.create_rectangle(
  53. 1,
  54. 1,
  55. canvas.width,
  56. canvas.height,
  57. generate_random_colour(),
  58. generate_random_colour()
  59. )
  60. return background
  61. # Erase a passed Karel from the canvas
  62. def erase_asset(asset):
  63. canvas = asset[1][0]
  64. # Erase each shape of the asset
  65. for shape in asset[0].values():
  66. canvas.delete(shape)
  67. logger.debug(f"Erased {asset[2]} asset: {asset}")
  68. # Move a passed Karel on the canvas
  69. """
  70. Waiting for Stanford to fix a bug in the environment
  71. CIP bug: https://codeinplace.stanford.edu/cip6/report?post=7043ece9-7653-4e39-8a63-5f4544b1d74b
  72. def move_karel(canvas, karel, x, y):
  73. for shape in karel[0].values():
  74. canvas.move(shape, x, y)
  75. logger.debug(f"Moved: {karel} by {x} horizontally, {y} vertically")
  76. """
  77. # Move a passed asset relative to previous position
  78. def relative_move_asset(asset, x:int=0, y:int=0):
  79. # Update coordinates in list
  80. asset[1][1] += x
  81. asset[1][2] += y
  82. # Replace asset
  83. match asset[2]:
  84. case "karel": new_asset = draw_karel(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  85. case "beeper": new_asset = draw_beeper(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  86. erase_asset(asset)
  87. logger.debug(f"Moved {asset[2]} by {x} horizontally, {y} vertically: {new_asset}")
  88. return new_asset
  89. # Move a passed asset in relation to her orientation
  90. def orientation_move_asset(asset, direction:str, amount:int):
  91. # Direction translation
  92. if asset[1][4].startswith("east"): # East
  93. if direction.lower() == "forward" or direction.lower() == "front":
  94. asset[1][1] += amount
  95. if direction.lower() == "left":
  96. asset[1][2] -= amount
  97. if direction.lower() == "right":
  98. asset[1][2] += amount
  99. if direction.lower() == "backward" or direction.lower() == "back":
  100. asset[1][1] -= amount
  101. if asset[1][4].startswith("north"): # North
  102. if direction.lower() == "forward" or direction.lower() == "front":
  103. asset[1][2] -= amount
  104. if direction.lower() == "left":
  105. asset[1][1] -= amount
  106. if direction.lower() == "right":
  107. asset[1][1] += amount
  108. if direction.lower() == "backward" or direction.lower() == "back":
  109. asset[1][2] += amount
  110. if asset[1][4].startswith("west"): # West
  111. if direction.lower() == "forward" or direction.lower() == "front":
  112. asset[1][1] -= amount
  113. if direction.lower() == "left":
  114. asset[1][2] += amount
  115. if direction.lower() == "right":
  116. asset[1][2] -= amount
  117. if direction.lower() == "backward" or direction.lower() == "back":
  118. asset[1][1] += amount
  119. if asset[1][4].startswith("south"): # South
  120. if direction.lower() == "forward" or direction.lower() == "front":
  121. asset[1][2] += amount
  122. if direction.lower() == "left":
  123. asset[1][1] += amount
  124. if direction.lower() == "right":
  125. asset[1][1] -= amount
  126. if direction.lower() == "backward" or direction.lower() == "back":
  127. asset[1][2] -= amount
  128. # Replace asset
  129. match asset[2]:
  130. case "karel": new_asset = draw_karel(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  131. case "beeper": new_asset = draw_beeper(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  132. erase_asset(asset)
  133. logger.debug(f"Moved: {asset[2]} {direction} by {amount}: {new_asset}")
  134. return new_asset
  135. # Move a passed asset to new coordinates
  136. def absolute_move_asset(asset, x:int, y:int):
  137. # Update coordinates in list
  138. asset[1][1] = x
  139. asset[1][2] = y
  140. # Replace asset
  141. match asset[2]:
  142. case "karel": new_asset = draw_karel(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  143. case "beeper": new_asset = draw_beeper(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  144. erase_asset(asset)
  145. logger.debug(f"Moved: {asset[2]} by {x} horizontally, {y} vertically: {new_asset}")
  146. return new_asset
  147. # Change the orientation of a passed asset
  148. def rotate_asset(asset, direction:str):
  149. # Relative turn
  150. if direction == "right" or direction == "left":
  151. if asset[1][4] == "east": # East
  152. if direction == "right":
  153. asset[1][4] = "south"
  154. if direction == "left":
  155. asset[1][4] = "north"
  156. elif asset[1][4] == "east-flipped":
  157. if direction == "right":
  158. asset[1][4] = "south-flipped"
  159. if direction == "left":
  160. asset[1][4] = "north-flipped"
  161. elif asset[1][4] == "north": # North
  162. if direction == "right":
  163. asset[1][4] = "east"
  164. if direction == "left":
  165. asset[1][4] = "west"
  166. elif asset[1][4] == "north-flipped":
  167. if direction == "right":
  168. asset[1][4] = "east-flipped"
  169. if direction == "left":
  170. asset[1][4] = "west-flipped"
  171. elif asset[1][4] == "west": # West
  172. if direction == "right":
  173. asset[1][4] = "north"
  174. if direction == "left":
  175. asset[1][4] = "south"
  176. elif asset[1][4] == "west-flipped":
  177. if direction == "right":
  178. asset[1][4] = "north-flipped"
  179. if direction == "left":
  180. asset[1][4] = "south-flipped"
  181. elif asset[1][4] == "south": # South
  182. if direction == "right":
  183. asset[1][4] = "west"
  184. if direction == "left":
  185. asset[1][4] = "east"
  186. elif asset[1][4] == "south-flipped":
  187. if direction == "right":
  188. asset[1][4] = "west-flipped"
  189. if direction == "left":
  190. asset[1][4] = "east-flipped"
  191. # Absolute rotation
  192. 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":
  193. asset[1][4] = direction
  194. # Syntax error
  195. else:
  196. logger.error(f"Invalid rotation direction: {direction}")
  197. # Repalce asset
  198. match asset[2]:
  199. case "karel": new_asset = draw_karel(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  200. case "beeper": new_asset = draw_beeper(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  201. erase_asset(asset)
  202. logger.debug(f"Rotated {asset[2]} by {direction} as {asset[1][4]}: {new_asset}")
  203. return new_asset
  204. # Recolour a passed asset on the canvas
  205. """
  206. Waiting for Stanford to fix a bug in the environment
  207. CIP bug: https://codeinplace.stanford.edu/cip6/report?post=c36ed931-3019-4830-8ba6-655c1a513471
  208. def recolour_karel(canvas, karel, colour:str="black", background:str="white"):
  209. for name, shape in karel[0].items(): # Loop over Karel dict
  210. if name.endswith("_fill"): # Background shape
  211. canvas.set_color(shape, background)
  212. canvas.set_outline_color(shape, background)
  213. else: # Foreground shape
  214. if name.endswith("_line") or name.endswith("_corner") or name == "mouth":
  215. canvas.set_color(shape, colour)
  216. elif name == "eye":
  217. #canvas.set_color(shape, "transparent")
  218. canvas.set_outline_color(shape, colour)
  219. else: # Legs and feet
  220. canvas.set_color(shape, colour)
  221. canvas.set_outline_color(shape, colour)
  222. """
  223. def recolour_asset(asset, colour:str="black", background:str="white"):
  224. # Random colours
  225. if colour == "random":
  226. colour = generate_random_colour()
  227. if background == "random":
  228. background = generate_random_colour()
  229. # Update colours in list
  230. asset[1][5] = colour
  231. asset[1][6] = background
  232. # Replace asset
  233. match asset[2]:
  234. case "karel": new_asset = draw_karel(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  235. case "beeper": new_asset = draw_beeper(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  236. erase_asset(asset)
  237. logger.debug(f"Re-coloured {asset[2]} with {colour} and {background}: {new_asset}")
  238. return new_asset
  239. # Draw a random asset on the canvas
  240. def generate_random_asset(canvas, karel_prevelance:int=4, centre_x="random", centre_y="random", size="random"):
  241. asset_type = random.randint(1, karel_prevelance)
  242. match asset_type:
  243. case 1: asset = generate_random_beeper(canvas, centre_x, centre_y, size)
  244. case _: asset = generate_random_karel(canvas, centre_x, centre_y, size)
  245. return asset
  246. # Draw a random asset outside the canvas
  247. def generate_outofbounds_random_asset(canvas, side:str, karel_prevelance:int=4):
  248. asset_type = random.randint(1, karel_prevelance)
  249. match asset_type:
  250. case 1: asset = generate_outofbounds_random_beeper(canvas, side)
  251. case _: asset = generate_outofbounds_random_karel(canvas, side)
  252. return asset
  253. # Resize an asset realtive to its previous size
  254. def relative_resize_asset(asset, size_difference:int):
  255. asset[1][3] += size_difference
  256. match asset[2]:
  257. case "karel": new_asset = draw_karel(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  258. case "beeper": new_asset = draw_beeper(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  259. erase_asset(asset)
  260. logger.debug(f"Re-sized {asset[2]} by {size_difference} to {asset[1][3]}: {new_asset}")
  261. return new_asset
  262. # Resize an asset to a new absolute size
  263. def absolute_resize_asset(asset, size:int):
  264. asset[1][3] = size
  265. match asset[2]:
  266. case "karel": new_asset = draw_karel(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  267. case "beeper": new_asset = draw_beeper(asset[1][0], asset[1][1], asset[1][2], asset[1][3], asset[1][4], asset[1][5], asset[1][6], asset[1][7])
  268. erase_asset(asset)
  269. logger.debug(f"Re-sized {asset[2]} to {size}: {new_asset}")
  270. return new_asset
  271. """
  272. Karel functions
  273. """
  274. # Draw a random Karel outside the canvas
  275. def generate_outofbounds_random_karel(canvas, side:str):
  276. size = determine_random_size(canvas)
  277. # Generate random coordinates (Outside the canvas geometry)
  278. match side:
  279. case "left":
  280. centre_x = int(0 - size / 2 - 1)
  281. centre_y = random.randint(int(size / 2), int(canvas.height - size / 2))
  282. case "top":
  283. centre_x = random.randint(int(size / 2), int(canvas.height - size / 2))
  284. centre_y = int(0 - size / 2 - 1)
  285. case "right":
  286. centre_x = int(canvas.width + size / 2 + 1)
  287. centre_y = random.randint(int(size / 2), int(canvas.height - size / 2))
  288. case "bottom":
  289. centre_x = random.randint(int(size / 2), int(canvas.height - size / 2))
  290. centre_y = int(canvas.height + size / 2 + 1)
  291. # Generate random values
  292. orientation = random.choice(["east", "east-flipped", "north", "north-flipped", "west", "west-flipped", "south", "south-flipped"])
  293. transparent = random.choice((True, False, False))
  294. return draw_karel(canvas, centre_x, centre_y, size, orientation, "random", "random", transparent)
  295. # Draw a random Karel on the canvas
  296. def generate_random_karel(canvas, centre_x="random", centre_y="random", size="random", orientation="random", transparent="random"):
  297. if size == "random":
  298. size = determine_random_size(canvas)
  299. # Generate random values (Inside the canvas geometry)
  300. if centre_x == "random":
  301. centre_x = random.randint(int(size / 2), int(canvas.width - size / 2))
  302. if centre_y == "random":
  303. centre_y = random.randint(int(size / 2), int(canvas.height - size / 2))
  304. if orientation == "random":
  305. orientation = random.choice(["east", "east-flipped", "north", "north-flipped", "west", "west-flipped", "south", "south-flipped"])
  306. if transparent == "random":
  307. transparent = random.choice((True, False, False, False))
  308. return draw_karel(canvas, centre_x, centre_y, size, orientation, "random", "random", transparent)
  309. # Draw a Karel
  310. def draw_karel(
  311. canvas,
  312. centre_x:int=25,
  313. centre_y:int=25,
  314. size:int=50,
  315. orientation:str="east",
  316. colour:str="black",
  317. background:str="white",
  318. transparent:bool=False
  319. ):
  320. # Body constants
  321. MARGIN = size / 8
  322. APPENDAGE_MULTIPLIER = MARGIN * 0.6
  323. # Random colours
  324. if colour == "random":
  325. colour = generate_random_colour()
  326. if background == "random":
  327. background = generate_random_colour()
  328. """ Flipper case
  329. In order to be able to flip Karel, the operands in the forumlas must be able to switch around.
  330. Orientations are in relation to the centre of Karel.
  331. """
  332. match orientation.lower():
  333. case "east" | "south-flipped":
  334. left_operand = operator.sub # Left / Top
  335. top_operand = operator.sub # Top / Left
  336. right_operand = operator.add # Right / Bottom
  337. bottom_operand = operator.add # Bottom / Right
  338. case "east-flipped" | "south":
  339. left_operand = operator.sub # Left / Top
  340. top_operand = operator.add # Bottom / Right
  341. right_operand = operator.add # Right / Bottom
  342. bottom_operand = operator.sub # Top / Left
  343. case "west" | "north-flipped":
  344. left_operand = operator.add # Right
  345. top_operand = operator.add # Bottom
  346. right_operand = operator.sub # Left
  347. bottom_operand = operator.sub # Top
  348. case "west-flipped" | "north":
  349. left_operand = operator.add # Right / Bottom
  350. top_operand = operator.sub # Top / Left
  351. right_operand = operator.sub # Left / Top
  352. bottom_operand = operator.add # Bottom / Right
  353. case _:
  354. logger.error(f"Invalid orientation: {orientation}")
  355. """ Coords case
  356. X and Y need to be swapped in case of a North or South facing Karel, yet the original vlaues have to be returned. (To prevent diagonal relative translation)
  357. """
  358. if orientation.lower() == "north" or orientation.lower() == "north-flipped" or orientation.lower() == "south" or orientation.lower() == "south-flipped":
  359. x = centre_y
  360. y = centre_x
  361. else:
  362. x = centre_x
  363. y = centre_y
  364. # Borders
  365. left = left_operand(x, size / 2)
  366. top = top_operand(y, size / 2)
  367. right = right_operand(x, size / 2)
  368. bottom = bottom_operand(y, size / 2)
  369. margin = size / 8
  370. appendage_multiplier = margin * 0.6
  371. # Body borders
  372. body_left = left_operand(x, size / 3)
  373. body_top = top
  374. body_right = right_operand(x, size / 3)
  375. body_bottom = bottom_operand(y, size / 2.6)
  376. eye_left = right_operand(body_left, margin)
  377. eye_top = bottom_operand(body_top, margin)
  378. eye_right = left_operand(body_right, margin)
  379. eye_bottom = top_operand(body_bottom, margin * 2)
  380. # Body coordinates
  381. top_left_corner = body_left, body_top
  382. left_diagonal_top = body_left, top_operand(body_bottom, margin)
  383. left_diagonal_bottom = eye_left, body_bottom
  384. right_diagonal_top = eye_right, body_top
  385. right_diagonal_bottom = body_right, bottom_operand(body_top, margin)
  386. bottom_right_corner = body_right, body_bottom
  387. mouth_left_corner = x, bottom_operand(eye_bottom, margin)
  388. mouth_right_corner = eye_right, mouth_left_corner[1]
  389. # Left appendage borders
  390. leftLeg_left = left_operand(body_left, margin)
  391. leftLeg_top = eye_bottom
  392. leftLeg_right = body_left
  393. leftLeg_bottom = bottom_operand(leftLeg_top, appendage_multiplier)
  394. leftFoot_left = leftLeg_left
  395. leftFoot_top = leftLeg_bottom
  396. leftFoot_right = right_operand(leftLeg_left, appendage_multiplier)
  397. leftFoot_bottom = bottom_operand(leftFoot_top, appendage_multiplier)
  398. # Right appendage borders
  399. rightLeg_left = x
  400. rightLeg_top = body_bottom
  401. rightLeg_right = right_operand(rightLeg_left, appendage_multiplier)
  402. rightLeg_bottom = bottom
  403. rightFoot_left = rightLeg_right
  404. rightFoot_top = top_operand(bottom, appendage_multiplier)
  405. rigthFoot_right = right_operand(rightLeg_right, appendage_multiplier)
  406. rightFoot_bottom = bottom
  407. # Draw Karel
  408. match orientation.lower():
  409. case "east" | "east-flipped" | "west" | "west-flipped":
  410. if not transparent:
  411. # Draw meat
  412. filler = canvas.create_polygon(
  413. top_left_corner[0], top_left_corner[1],
  414. right_diagonal_top[0], right_diagonal_top[1],
  415. right_diagonal_bottom[0], right_diagonal_bottom[1],
  416. bottom_right_corner[0], bottom_right_corner[1],
  417. left_diagonal_bottom[0], left_diagonal_bottom[1],
  418. left_diagonal_top[0], left_diagonal_top[1],
  419. top_left_corner[0], eye_top,
  420. eye_left, eye_top,
  421. eye_left, eye_bottom,
  422. eye_left, eye_bottom,
  423. eye_right, eye_bottom,
  424. eye_right, eye_top,
  425. top_left_corner[0], eye_top,
  426. color = background,
  427. outline = background
  428. )
  429. # Draw outlines
  430. top_line = canvas.create_line(
  431. top_left_corner[0], top_left_corner[1],
  432. right_diagonal_top[0], right_diagonal_top[1],
  433. colour
  434. )
  435. top_corner = canvas.create_line(
  436. right_diagonal_top[0], right_diagonal_top[1],
  437. right_diagonal_bottom[0], right_diagonal_bottom[1],
  438. colour
  439. )
  440. left_line = canvas.create_line(
  441. top_left_corner[0], top_left_corner[1],
  442. left_diagonal_top[0], left_diagonal_top[1],
  443. colour
  444. )
  445. bottom_corner = canvas.create_line(
  446. left_diagonal_top[0], left_diagonal_top[1],
  447. left_diagonal_bottom[0], left_diagonal_bottom[1],
  448. colour
  449. )
  450. bottom_line = canvas.create_line(
  451. left_diagonal_bottom[0], left_diagonal_bottom[1],
  452. bottom_right_corner[0], bottom_right_corner[1],
  453. colour
  454. )
  455. right_line = canvas.create_line(
  456. right_diagonal_bottom[0], right_diagonal_bottom[1],
  457. bottom_right_corner[0], bottom_right_corner[1],
  458. colour
  459. )
  460. left_appendage = canvas.create_polygon(
  461. leftLeg_left, leftLeg_top,
  462. leftLeg_right, leftLeg_top,
  463. leftLeg_right, leftLeg_bottom,
  464. leftFoot_right, leftFoot_top,
  465. leftFoot_right, leftFoot_bottom,
  466. leftFoot_left, leftFoot_bottom,
  467. color = colour,
  468. outline = colour
  469. )
  470. right_appendage = canvas.create_polygon(
  471. rightLeg_left, rightLeg_top,
  472. rightLeg_right, rightLeg_top,
  473. rightFoot_left, rightFoot_top,
  474. rigthFoot_right, rightFoot_top,
  475. rigthFoot_right, rightFoot_bottom,
  476. rightLeg_left, rightFoot_bottom,
  477. color = colour,
  478. outline = colour
  479. )
  480. eye = canvas.create_rectangle(
  481. eye_left,
  482. eye_top,
  483. eye_right,
  484. eye_bottom,
  485. "transparent",
  486. colour,
  487. )
  488. mouth = canvas.create_line(
  489. mouth_left_corner[0], mouth_left_corner[1],
  490. mouth_right_corner[0], mouth_right_corner[1],
  491. colour
  492. )
  493. # X and Y coordinates swapped for these orientations
  494. case "north" | "north-flipped" | "south" | "south-flipped":
  495. if not transparent:
  496. # Draw meat
  497. filler = canvas.create_polygon(
  498. top_left_corner[1], top_left_corner[0],
  499. right_diagonal_top[1], right_diagonal_top[0],
  500. right_diagonal_bottom[1], right_diagonal_bottom[0],
  501. #eye_bottom, right_diagonal_bottom[0],
  502. bottom_right_corner[1], bottom_right_corner[0],
  503. left_diagonal_bottom[1], left_diagonal_bottom[0],
  504. left_diagonal_top[1], left_diagonal_top[0],
  505. eye_top, top_left_corner[0],
  506. eye_top, eye_left,
  507. eye_bottom, eye_left,
  508. eye_bottom, eye_right,
  509. eye_top, eye_right,
  510. eye_top, top_left_corner[0],
  511. color = background,
  512. outline = background
  513. )
  514. # Draw outlines
  515. top_line = canvas.create_line(
  516. top_left_corner[1], top_left_corner[0],
  517. right_diagonal_top[1], right_diagonal_top[0],
  518. colour
  519. )
  520. top_corner = canvas.create_line(
  521. right_diagonal_top[1], right_diagonal_top[0],
  522. right_diagonal_bottom[1], right_diagonal_bottom[0],
  523. colour
  524. )
  525. left_line = canvas.create_line(
  526. top_left_corner[1], top_left_corner[0],
  527. left_diagonal_top[1], left_diagonal_top[0],
  528. colour
  529. )
  530. bottom_corner = canvas.create_line(
  531. left_diagonal_top[1], left_diagonal_top[0],
  532. left_diagonal_bottom[1], left_diagonal_bottom[0],
  533. colour
  534. )
  535. bottom_line = canvas.create_line(
  536. left_diagonal_bottom[1], left_diagonal_bottom[0],
  537. bottom_right_corner[1], bottom_right_corner[0],
  538. colour
  539. )
  540. right_line = canvas.create_line(
  541. right_diagonal_bottom[1], right_diagonal_bottom[0],
  542. bottom_right_corner[1], bottom_right_corner[0],
  543. colour
  544. )
  545. left_appendage = canvas.create_polygon(
  546. leftLeg_top, leftLeg_left,
  547. leftLeg_top, leftLeg_right,
  548. leftLeg_bottom, leftLeg_right,
  549. leftFoot_top, leftFoot_right,
  550. leftFoot_bottom, leftFoot_right,
  551. leftFoot_bottom, leftFoot_left,
  552. color = colour,
  553. outline = colour
  554. )
  555. right_appendage = canvas.create_polygon(
  556. rightLeg_top, rightLeg_left,
  557. rightLeg_top, rightLeg_right,
  558. rightFoot_top, rightFoot_left,
  559. rightFoot_top, rigthFoot_right,
  560. rightFoot_bottom, rigthFoot_right,
  561. rightFoot_bottom, rightLeg_left,
  562. color = colour,
  563. outline = colour
  564. )
  565. eye = canvas.create_rectangle(
  566. eye_top,
  567. eye_right,
  568. eye_bottom,
  569. eye_left,
  570. "transparent",
  571. colour,
  572. )
  573. mouth = canvas.create_line(
  574. mouth_left_corner[1], mouth_left_corner[0],
  575. mouth_right_corner[1], mouth_right_corner[0],
  576. colour
  577. )
  578. # Return each object so it can later be altered/destroyed
  579. if transparent:
  580. shapes = {
  581. "top_line": top_line,
  582. "top_corner": top_corner,
  583. "left_line": left_line,
  584. "bottom_corner": bottom_corner,
  585. "bottom_line": bottom_line,
  586. "right_line": right_line,
  587. "left_appendage": left_appendage,
  588. "right_appendage": right_appendage,
  589. "eye": eye,
  590. "mouth": mouth
  591. }
  592. else: # Not transparent
  593. shapes = {
  594. "filler": filler,
  595. "top_line": top_line,
  596. "top_corner": top_corner,
  597. "left_line": left_line,
  598. "bottom_corner": bottom_corner,
  599. "bottom_line": bottom_line,
  600. "right_line": right_line,
  601. "left_appendage": left_appendage,
  602. "right_appendage": right_appendage,
  603. "eye": eye,
  604. "mouth": mouth
  605. }
  606. arguments = [canvas, centre_x, centre_y, size, orientation.lower(), colour, background, transparent]
  607. logger.debug(f"Created Karel: {shapes, arguments}")
  608. return [shapes, arguments, "karel"]
  609. """ Beeper functions
  610. Even though the beeper is symmetrical in two directions and rotating wont matter,
  611. it may be useful in case of moving in relation to orientation,
  612. perhaps in coordinated group movements with Karels.
  613. """
  614. # Draw a random Karel outside the canvas
  615. def generate_outofbounds_random_beeper(canvas, side:str):
  616. size = determine_random_size(canvas)
  617. # Generate random coordinates (Outside the canvas geometry)
  618. match side:
  619. case "left":
  620. centre_x = int(0 - size / 2 - 1)
  621. centre_y = random.randint(int(size / 2), int(canvas.height - size / 2))
  622. case "top":
  623. centre_x = random.randint(int(size / 2), int(canvas.height - size / 2))
  624. centre_y = int(0 - size / 2 - 1)
  625. case "right":
  626. centre_x = int(canvas.width + size / 2 + 1)
  627. centre_y = random.randint(int(size / 2), int(canvas.height - size / 2))
  628. case "bottom":
  629. centre_x = random.randint(int(size / 2), int(canvas.height - size / 2))
  630. centre_y = int(canvas.height + size / 2 + 1)
  631. # Generate random values
  632. orientation = random.choice(["east", "east-flipped", "north", "north-flipped", "west", "west-flipped", "south", "south-flipped"])
  633. transparent = random.choice((True, False, False))
  634. return draw_beeper(canvas, centre_x, centre_y, size, orientation, "random", "random", transparent)
  635. def generate_random_beeper(canvas, centre_x="random", centre_y="random", size="random"):
  636. if size == "random":
  637. size = determine_random_size(canvas)
  638. # Generate random values (Inside the canvas geometry)
  639. if centre_x == "random":
  640. centre_x = random.randint(int(size / 2), int(canvas.width - size / 2))
  641. if centre_y == "random":
  642. centre_y = random.randint(int(size / 2), int(canvas.height - size / 2))
  643. orientation = random.choice(["east", "east-flipped", "north", "north-flipped", "west", "west-flipped", "south", "south-flipped"])
  644. transparent = random.choice((True, False, False, False))
  645. return draw_beeper(canvas, centre_x, centre_y, size, orientation, "random", "random", transparent)
  646. def draw_beeper(
  647. canvas,
  648. centre_x:int=25,
  649. centre_y:int=25,
  650. size:int=50,
  651. orientation:str="east",
  652. colour:str="black",
  653. background:str="cyan",
  654. transparent:bool=False
  655. ):
  656. # Random colours
  657. if colour == "random":
  658. colour = generate_random_colour()
  659. if background == "random":
  660. background = generate_random_colour()
  661. # Borders
  662. left = centre_x - size / 2
  663. top = centre_y - size / 2
  664. right = centre_x + size / 2
  665. bottom = centre_y + size / 2
  666. # Body coordinates
  667. top_corner = centre_x, top
  668. left_corner = left, centre_y
  669. bottom_corner = centre_x, bottom
  670. right_corner = right, centre_y
  671. # Draw insides
  672. filler = canvas.create_polygon(
  673. top_corner[0], top_corner[1],
  674. right_corner[0], right_corner[1],
  675. bottom_corner[0], bottom_corner[1],
  676. left_corner[0], left_corner[1],
  677. color = background,
  678. outline = colour
  679. )
  680. # Return each object so it can later be altered/destroyed
  681. shapes = {
  682. "filler": filler
  683. }
  684. arguments = [canvas, centre_x, centre_y, size, orientation.lower(), colour, background, transparent]
  685. logger.debug(f"Created beeper: {shapes, arguments}")
  686. return [shapes, arguments, "beeper"]