karel_asset.py 30 KB

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