karel_asset.py 20 KB

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