karel_asset.py 18 KB

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