karel_asset.py 28 KB

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