karel_asset.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  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(0 + 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(0 + 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"):
  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. orientation = random.choice(["east", "east-flipped", "north", "north-flipped", "west", "west-flipped", "south", "south-flipped"])
  305. transparent = random.choice((True, False, False, False))
  306. return draw_karel(canvas, centre_x, centre_y, size, orientation, "random", "random", transparent)
  307. # Draw a Karel
  308. def draw_karel(
  309. canvas,
  310. centre_x:int=25,
  311. centre_y:int=25,
  312. size:int=50,
  313. orientation:str="east",
  314. colour:str="black",
  315. background:str="white",
  316. transparent:bool=False
  317. ):
  318. # Body constants
  319. MARGIN = size / 8
  320. APPENDAGE_MULTIPLIER = MARGIN * 0.6
  321. # Random colours
  322. if colour == "random":
  323. colour = generate_random_colour()
  324. if background == "random":
  325. background = generate_random_colour()
  326. """ Flipper case
  327. In order to be able to flip Karel, the operands in the forumlas must be able to switch around.
  328. Orientations are in relation to the centre of Karel.
  329. """
  330. match orientation.lower():
  331. case "east" | "south-flipped":
  332. left_operand = operator.sub # Left / Top
  333. top_operand = operator.sub # Top / Left
  334. right_operand = operator.add # Right / Bottom
  335. bottom_operand = operator.add # Bottom / Right
  336. case "east-flipped" | "south":
  337. left_operand = operator.sub # Left / Top
  338. top_operand = operator.add # Bottom / Right
  339. right_operand = operator.add # Right / Bottom
  340. bottom_operand = operator.sub # Top / Left
  341. case "west" | "north-flipped":
  342. left_operand = operator.add # Right
  343. top_operand = operator.add # Bottom
  344. right_operand = operator.sub # Left
  345. bottom_operand = operator.sub # Top
  346. case "west-flipped" | "north":
  347. left_operand = operator.add # Right / Bottom
  348. top_operand = operator.sub # Top / Left
  349. right_operand = operator.sub # Left / Top
  350. bottom_operand = operator.add # Bottom / Right
  351. """ Coords case
  352. 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)
  353. """
  354. if orientation.lower() == "north" or orientation.lower() == "north-flipped" or orientation.lower() == "south" or orientation.lower() == "south-flipped":
  355. x = centre_y
  356. y = centre_x
  357. else:
  358. x = centre_x
  359. y = centre_y
  360. # Borders
  361. left = left_operand(x, size / 2)
  362. top = top_operand(y, size / 2)
  363. right = right_operand(x, size / 2)
  364. bottom = bottom_operand(y, size / 2)
  365. margin = size / 8
  366. appendage_multiplier = margin * 0.6
  367. # Body borders
  368. body_left = left_operand(x, size / 3)
  369. body_top = top
  370. body_right = right_operand(x, size / 3)
  371. body_bottom = bottom_operand(y, size / 2.6)
  372. eye_left = right_operand(body_left, margin)
  373. eye_top = bottom_operand(body_top, margin)
  374. eye_right = left_operand(body_right, margin)
  375. eye_bottom = top_operand(body_bottom, margin * 2)
  376. # Body coordinates
  377. top_left_corner = body_left, body_top
  378. left_diagonal_top = body_left, top_operand(body_bottom, margin)
  379. left_diagonal_bottom = eye_left, body_bottom
  380. right_diagonal_top = eye_right, body_top
  381. right_diagonal_bottom = body_right, bottom_operand(body_top, margin)
  382. bottom_right_corner = body_right, body_bottom
  383. mouth_left_corner = x, bottom_operand(eye_bottom, margin)
  384. mouth_right_corner = eye_right, mouth_left_corner[1]
  385. # Left appendage borders
  386. leftLeg_left = left_operand(body_left, margin)
  387. leftLeg_top = eye_bottom
  388. leftLeg_right = body_left
  389. leftLeg_bottom = bottom_operand(leftLeg_top, appendage_multiplier)
  390. leftFoot_left = leftLeg_left
  391. leftFoot_top = leftLeg_bottom
  392. leftFoot_right = right_operand(leftLeg_left, appendage_multiplier)
  393. leftFoot_bottom = bottom_operand(leftFoot_top, appendage_multiplier)
  394. # Right appendage borders
  395. rightLeg_left = x
  396. rightLeg_top = body_bottom
  397. rightLeg_right = right_operand(rightLeg_left, appendage_multiplier)
  398. rightLeg_bottom = bottom
  399. rightFoot_left = rightLeg_right
  400. rightFoot_top = top_operand(bottom, appendage_multiplier)
  401. rigthFoot_right = right_operand(rightLeg_right, appendage_multiplier)
  402. rightFoot_bottom = bottom
  403. # Draw Karel
  404. match orientation.lower():
  405. case "east" | "east-flipped" | "west" | "west-flipped":
  406. if not transparent:
  407. # Draw meat
  408. filler = canvas.create_polygon(
  409. top_left_corner[0], top_left_corner[1],
  410. right_diagonal_top[0], right_diagonal_top[1],
  411. right_diagonal_bottom[0], right_diagonal_bottom[1],
  412. bottom_right_corner[0], bottom_right_corner[1],
  413. left_diagonal_bottom[0], left_diagonal_bottom[1],
  414. left_diagonal_top[0], left_diagonal_top[1],
  415. top_left_corner[0], eye_top,
  416. eye_left, eye_top,
  417. eye_left, eye_bottom,
  418. eye_left, eye_bottom,
  419. eye_right, eye_bottom,
  420. eye_right, eye_top,
  421. top_left_corner[0], eye_top,
  422. color = background,
  423. outline = background
  424. )
  425. # Draw outlines
  426. top_line = canvas.create_line(
  427. top_left_corner[0], top_left_corner[1],
  428. right_diagonal_top[0], right_diagonal_top[1],
  429. colour
  430. )
  431. top_corner = canvas.create_line(
  432. right_diagonal_top[0], right_diagonal_top[1],
  433. right_diagonal_bottom[0], right_diagonal_bottom[1],
  434. colour
  435. )
  436. left_line = canvas.create_line(
  437. top_left_corner[0], top_left_corner[1],
  438. left_diagonal_top[0], left_diagonal_top[1],
  439. colour
  440. )
  441. bottom_corner = canvas.create_line(
  442. left_diagonal_top[0], left_diagonal_top[1],
  443. left_diagonal_bottom[0], left_diagonal_bottom[1],
  444. colour
  445. )
  446. bottom_line = canvas.create_line(
  447. left_diagonal_bottom[0], left_diagonal_bottom[1],
  448. bottom_right_corner[0], bottom_right_corner[1],
  449. colour
  450. )
  451. right_line = canvas.create_line(
  452. right_diagonal_bottom[0], right_diagonal_bottom[1],
  453. bottom_right_corner[0], bottom_right_corner[1],
  454. colour
  455. )
  456. left_appendage = canvas.create_polygon(
  457. leftLeg_left, leftLeg_top,
  458. leftLeg_right, leftLeg_top,
  459. leftLeg_right, leftLeg_bottom,
  460. leftFoot_right, leftFoot_top,
  461. leftFoot_right, leftFoot_bottom,
  462. leftFoot_left, leftFoot_bottom,
  463. color = colour,
  464. outline = colour
  465. )
  466. right_appendage = canvas.create_polygon(
  467. rightLeg_left, rightLeg_top,
  468. rightLeg_right, rightLeg_top,
  469. rightFoot_left, rightFoot_top,
  470. rigthFoot_right, rightFoot_top,
  471. rigthFoot_right, rightFoot_bottom,
  472. rightLeg_left, rightFoot_bottom,
  473. color = colour,
  474. outline = colour
  475. )
  476. eye = canvas.create_rectangle(
  477. eye_left,
  478. eye_top,
  479. eye_right,
  480. eye_bottom,
  481. "transparent",
  482. colour,
  483. )
  484. mouth = canvas.create_line(
  485. mouth_left_corner[0], mouth_left_corner[1],
  486. mouth_right_corner[0], mouth_right_corner[1],
  487. colour
  488. )
  489. # X and Y coordinates swapped for these orientations
  490. case "north" | "north-flipped" | "south" | "south-flipped":
  491. if not transparent:
  492. # Draw meat
  493. filler = canvas.create_polygon(
  494. top_left_corner[1], top_left_corner[0],
  495. right_diagonal_top[1], right_diagonal_top[0],
  496. right_diagonal_bottom[1], right_diagonal_bottom[0],
  497. #eye_bottom, right_diagonal_bottom[0],
  498. bottom_right_corner[1], bottom_right_corner[0],
  499. left_diagonal_bottom[1], left_diagonal_bottom[0],
  500. left_diagonal_top[1], left_diagonal_top[0],
  501. eye_top, top_left_corner[0],
  502. eye_top, eye_left,
  503. eye_bottom, eye_left,
  504. eye_bottom, eye_right,
  505. eye_top, eye_right,
  506. eye_top, top_left_corner[0],
  507. color = background,
  508. outline = background
  509. )
  510. # Draw outlines
  511. top_line = canvas.create_line(
  512. top_left_corner[1], top_left_corner[0],
  513. right_diagonal_top[1], right_diagonal_top[0],
  514. colour
  515. )
  516. top_corner = canvas.create_line(
  517. right_diagonal_top[1], right_diagonal_top[0],
  518. right_diagonal_bottom[1], right_diagonal_bottom[0],
  519. colour
  520. )
  521. left_line = canvas.create_line(
  522. top_left_corner[1], top_left_corner[0],
  523. left_diagonal_top[1], left_diagonal_top[0],
  524. colour
  525. )
  526. bottom_corner = canvas.create_line(
  527. left_diagonal_top[1], left_diagonal_top[0],
  528. left_diagonal_bottom[1], left_diagonal_bottom[0],
  529. colour
  530. )
  531. bottom_line = canvas.create_line(
  532. left_diagonal_bottom[1], left_diagonal_bottom[0],
  533. bottom_right_corner[1], bottom_right_corner[0],
  534. colour
  535. )
  536. right_line = canvas.create_line(
  537. right_diagonal_bottom[1], right_diagonal_bottom[0],
  538. bottom_right_corner[1], bottom_right_corner[0],
  539. colour
  540. )
  541. left_appendage = canvas.create_polygon(
  542. leftLeg_top, leftLeg_left,
  543. leftLeg_top, leftLeg_right,
  544. leftLeg_bottom, leftLeg_right,
  545. leftFoot_top, leftFoot_right,
  546. leftFoot_bottom, leftFoot_right,
  547. leftFoot_bottom, leftFoot_left,
  548. color = colour,
  549. outline = colour
  550. )
  551. right_appendage = canvas.create_polygon(
  552. rightLeg_top, rightLeg_left,
  553. rightLeg_top, rightLeg_right,
  554. rightFoot_top, rightFoot_left,
  555. rightFoot_top, rigthFoot_right,
  556. rightFoot_bottom, rigthFoot_right,
  557. rightFoot_bottom, rightLeg_left,
  558. color = colour,
  559. outline = colour
  560. )
  561. eye = canvas.create_rectangle(
  562. eye_top,
  563. eye_right,
  564. eye_bottom,
  565. eye_left,
  566. "transparent",
  567. colour,
  568. )
  569. mouth = canvas.create_line(
  570. mouth_left_corner[1], mouth_left_corner[0],
  571. mouth_right_corner[1], mouth_right_corner[0],
  572. colour
  573. )
  574. # Return each object so it can later be altered/destroyed
  575. if transparent:
  576. shapes = {
  577. "top_line": top_line,
  578. "top_corner": top_corner,
  579. "left_line": left_line,
  580. "bottom_corner": bottom_corner,
  581. "bottom_line": bottom_line,
  582. "right_line": right_line,
  583. "left_appendage": left_appendage,
  584. "right_appendage": right_appendage,
  585. "eye": eye,
  586. "mouth": mouth
  587. }
  588. else: # Not transparent
  589. shapes = {
  590. "filler": filler,
  591. "top_line": top_line,
  592. "top_corner": top_corner,
  593. "left_line": left_line,
  594. "bottom_corner": bottom_corner,
  595. "bottom_line": bottom_line,
  596. "right_line": right_line,
  597. "left_appendage": left_appendage,
  598. "right_appendage": right_appendage,
  599. "eye": eye,
  600. "mouth": mouth
  601. }
  602. arguments = [canvas, centre_x, centre_y, size, orientation.lower(), colour, background, transparent]
  603. logger.debug(f"Created Karel: {shapes, arguments}")
  604. return [shapes, arguments, "karel"]
  605. """ Beeper functions
  606. Even though the beeper is symmetrical in two directions and rotating wont matter,
  607. it may be useful in case of moving in relation to orientation,
  608. perhaps in coordinated group movements with Karels.
  609. """
  610. # Draw a random Karel outside the canvas
  611. def generate_outofbounds_random_beeper(canvas, side:str):
  612. size = determine_random_size(canvas)
  613. # Generate random coordinates (Outside the canvas geometry)
  614. match side:
  615. case "left":
  616. centre_x = int(0 - size / 2 - 1)
  617. centre_y = random.randint(int(size / 2), int(canvas.height - size / 2))
  618. case "top":
  619. centre_x = random.randint(int(size / 2), int(canvas.height - size / 2))
  620. centre_y = int(0 - size / 2 - 1)
  621. case "right":
  622. centre_x = int(0 + size / 2 + 1)
  623. centre_y = random.randint(int(size / 2), int(canvas.height - size / 2))
  624. case "bottom":
  625. centre_x = random.randint(int(size / 2), int(canvas.height - size / 2))
  626. centre_y = int(0 + size / 2 + 1)
  627. # Generate random values
  628. orientation = random.choice(["east", "east-flipped", "north", "north-flipped", "west", "west-flipped", "south", "south-flipped"])
  629. transparent = random.choice((True, False, False))
  630. return draw_beeper(canvas, centre_x, centre_y, size, orientation, "random", "random", transparent)
  631. def generate_random_beeper(canvas, centre_x="random", centre_y="random", size="random"):
  632. if size == "random":
  633. size = determine_random_size(canvas)
  634. # Generate random values (Inside the canvas geometry)
  635. if centre_x == "random":
  636. centre_x = random.randint(int(size / 2), int(canvas.width - size / 2))
  637. if centre_y == "random":
  638. centre_y = random.randint(int(size / 2), int(canvas.height - size / 2))
  639. orientation = random.choice(["east", "east-flipped", "north", "north-flipped", "west", "west-flipped", "south", "south-flipped"])
  640. transparent = random.choice((True, False, False, False))
  641. return draw_beeper(canvas, centre_x, centre_y, size, orientation, "random", "random", transparent)
  642. def draw_beeper(
  643. canvas,
  644. centre_x:int=25,
  645. centre_y:int=25,
  646. size:int=50,
  647. orientation:str="east",
  648. colour:str="black",
  649. background:str="cyan",
  650. transparent:bool=False
  651. ):
  652. # Random colours
  653. if colour == "random":
  654. colour = generate_random_colour()
  655. if background == "random":
  656. background = generate_random_colour()
  657. # Borders
  658. left = centre_x - size / 2
  659. top = centre_y - size / 2
  660. right = centre_x + size / 2
  661. bottom = centre_y + size / 2
  662. # Body coordinates
  663. top_corner = centre_x, top
  664. left_corner = left, centre_y
  665. bottom_corner = centre_x, bottom
  666. right_corner = right, centre_y
  667. # Draw insides
  668. filler = canvas.create_polygon(
  669. top_corner[0], top_corner[1],
  670. right_corner[0], right_corner[1],
  671. bottom_corner[0], bottom_corner[1],
  672. left_corner[0], left_corner[1],
  673. color = background,
  674. outline = colour
  675. )
  676. # Return each object so it can later be altered/destroyed
  677. shapes = {
  678. "filler": filler
  679. }
  680. arguments = [canvas, centre_x, centre_y, size, orientation.lower(), colour, background, transparent]
  681. logger.debug(f"Created beeper: {shapes, arguments}")
  682. return [shapes, arguments, "beeper"]