karel_asset.py 28 KB

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