karel_asset.py 23 KB

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