karel_asset.py 29 KB

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