karel_evolución 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. from graphics import Canvas
  2. import random
  3. import time
  4. # ======================================================
  5. # MADE BY : YESSENIA CHOQUEHUANCA MASIAS - COUNTRY: PERÚ
  6. # PROJECT : Code Catcher - Catch the Codes
  7. # UNIVERSITY: STANFORD - Peru
  8. # COLORS : Peru Gold/Red
  9. # ======================================================
  10. CANVAS_WIDTH = 600
  11. CANVAS_HEIGHT = 400
  12. BALL_SIZE = 25
  13. GRAVITY = 0.9
  14. JUMP_STRENGTH = -22
  15. BOUNCE_FACTOR = 0.6
  16. MIN_BOUNCE_VY = 3.0
  17. CODE_W = 110
  18. CODE_H = 26
  19. CODE_COUNT = 4
  20. CODES_TO_WIN = 10
  21. COLOR_STANFORD_RED = "#800b0bff"
  22. COLOR_PERU_RED = "#BF0000"
  23. COLOR_GOLD = "#c49620ff"
  24. COLOR_BRIGHT_GOLD = "#938224ff"
  25. COLOR_DARK_NAVY = "#1a1a2e"
  26. COLOR_WHITE = "WHITE"
  27. COLOR_GRAY = "#AAAAAA"
  28. COLOR_BLACK = "black"
  29. CODE_SNIPPETS = [
  30. "move()",
  31. "pick_beeper()",
  32. "turn_left()",
  33. "turn_right()",
  34. "put_beeper()",
  35. ]
  36. class KarelBall:
  37. """
  38. Pelota que se convierte en Karel parte a parte.
  39. Nivel 0 → pelota blanca
  40. Nivel 1-2 → cuerpo
  41. Nivel 3-4 → piernas
  42. Nivel 5-6 → brazos
  43. Nivel 7-8 → cabeza/pantalla
  44. Nivel 9-10 → Karel completo con antena
  45. """
  46. def __init__(self, canvas, x, y):
  47. self.canvas = canvas
  48. self.x = x
  49. self.y = y
  50. self.vx = 0
  51. self.vy = 0
  52. self.is_grounded = False
  53. self.on_platform = None
  54. self.level = 0 # how many codes has he caught
  55. # IDs of each drawn part
  56. self.parts = {}
  57. self._draw_level()
  58. # --------------------------------------------------
  59. # DRAWN
  60. # --------------------------------------------------
  61. def _clear(self):
  62. for pid in self.parts.values():
  63. self.canvas.delete(pid)
  64. self.parts = {}
  65. def _draw_level(self):
  66. self._clear()
  67. x, y = self.x, self.y # top-left corner of the sprite (25x25)
  68. # --- LEVEL 0: single ball ---
  69. self.parts['body_oval'] = self.canvas.create_oval(
  70. x, y, x + 25, y + 25,
  71. COLOR_WHITE, COLOR_STANFORD_RED
  72. )
  73. if self.level >= 2:
  74. # Gray square (replaces the oval with a rectangle on top)
  75. self.parts['body'] = self.canvas.create_rectangle(
  76. x + 4, y + 8, x + 21, y + 22,
  77. COLOR_GRAY, COLOR_BLACK
  78. )
  79. if self.level >= 4:
  80. # Left leg
  81. self.parts['leg_l'] = self.canvas.create_rectangle(
  82. x + 5, y + 22, x + 10, y + 30,
  83. COLOR_GRAY, COLOR_BLACK
  84. )
  85. # Right leg
  86. self.parts['leg_r'] = self.canvas.create_rectangle(
  87. x + 15, y + 22, x + 20, y + 30,
  88. COLOR_GRAY, COLOR_BLACK
  89. )
  90. # Left foot
  91. self.parts['foot_l'] = self.canvas.create_rectangle(
  92. x + 3, y + 28, x + 11, y + 32,
  93. COLOR_BLACK, COLOR_BLACK
  94. )
  95. # Ceiling height
  96. self.parts['foot_r'] = self.canvas.create_rectangle(
  97. x + 14, y + 28, x + 22, y + 32,
  98. COLOR_BLACK, COLOR_BLACK
  99. )
  100. if self.level >= 6:
  101. # Left arm
  102. self.parts['arm_l'] = self.canvas.create_rectangle(
  103. x - 5, y + 10, x + 4, y + 15,
  104. COLOR_GRAY, COLOR_BLACK
  105. )
  106. # Right-hand man
  107. self.parts['arm_r'] = self.canvas.create_rectangle(
  108. x + 21, y + 10, x + 30, y + 15,
  109. COLOR_GRAY, COLOR_BLACK
  110. )
  111. # Left hand
  112. self.parts['hand_l'] = self.canvas.create_oval(
  113. x - 8, y + 8, x - 2, y + 17,
  114. COLOR_BLACK, COLOR_BLACK
  115. )
  116. # Right hand
  117. self.parts['hand_r'] = self.canvas.create_oval(
  118. x + 27, y + 8, x + 33, y + 17,
  119. COLOR_BLACK, COLOR_BLACK
  120. )
  121. if self.level >= 8:
  122. # Head
  123. self.parts['head'] = self.canvas.create_rectangle(
  124. x + 5, y - 10, x + 20, y + 8,
  125. COLOR_GRAY, COLOR_BLACK
  126. )
  127. # Screen (eye)
  128. self.parts['screen'] = self.canvas.create_rectangle(
  129. x + 8, y - 8, x + 17, y + 2,
  130. COLOR_DARK_NAVY, COLOR_BLACK
  131. )
  132. # Screen resolution
  133. self.parts['pixel'] = self.canvas.create_rectangle(
  134. x + 10, y - 5, x + 15, y - 1,
  135. COLOR_BRIGHT_GOLD, COLOR_BRIGHT_GOLD
  136. )
  137. if self.level >= 10:
  138. # AntenNa
  139. self.parts['antenna'] = self.canvas.create_rectangle(
  140. x + 11, y - 18, x + 14, y - 10,
  141. COLOR_BLACK, COLOR_BLACK
  142. )
  143. # Antenna tip
  144. self.parts['antenna_tip'] = self.canvas.create_oval(
  145. x + 9, y - 22, x + 16, y - 16,
  146. COLOR_BRIGHT_GOLD, COLOR_BLACK
  147. )
  148. def _move_parts(self, dx, dy):
  149. for pid in self.parts.values():
  150. self.canvas.move(pid, dx, dy)
  151. # --------------------------------------------------
  152. # DEVELOPMENT
  153. # --------------------------------------------------
  154. def evolve(self, codes_caught):
  155. self.level = codes_caught
  156. self._draw_level()
  157. # --------------------------------------------------
  158. # PHYSICS
  159. # --------------------------------------------------
  160. def jump(self):
  161. if self.is_grounded:
  162. self.vy = JUMP_STRENGTH
  163. self.is_grounded = False
  164. self.on_platform = None
  165. def update_physics(self, platforms):
  166. if self.is_grounded and self.on_platform is not None:
  167. dx = self.on_platform.drift
  168. self.x += dx
  169. plat_top = CANVAS_HEIGHT - self.on_platform.height
  170. self.y = plat_top - BALL_SIZE
  171. self._move_parts(dx, 0)
  172. self.vy += GRAVITY
  173. old_x, old_y = self.x, self.y
  174. self.y += self.vy
  175. self.x += self.vx
  176. self.is_grounded = False
  177. self.on_platform = None
  178. ball_bottom = self.y + BALL_SIZE
  179. ball_center_x = self.x + BALL_SIZE / 2
  180. for platform in platforms:
  181. plat_top = CANVAS_HEIGHT - platform.height
  182. if (self.vy >= 0 and plat_top - 8 <= ball_bottom <= plat_top + self.vy + 2):
  183. if platform.x <= ball_center_x <= platform.x + platform.width:
  184. self.y = plat_top - BALL_SIZE
  185. if abs(self.vy) > MIN_BOUNCE_VY:
  186. self.vy = -self.vy * BOUNCE_FACTOR
  187. else:
  188. self.vy = 0
  189. self.is_grounded = True
  190. self.on_platform = platform
  191. break
  192. if self.x < 0:
  193. self.x = 0
  194. self.vx = abs(self.vx) * BOUNCE_FACTOR
  195. elif self.x + BALL_SIZE > CANVAS_WIDTH:
  196. self.x = CANVAS_WIDTH - BALL_SIZE
  197. self.vx = -abs(self.vx) * BOUNCE_FACTOR
  198. if self.y < 0:
  199. self.y = 0
  200. self.vy = abs(self.vy) * BOUNCE_FACTOR
  201. if self.y > CANVAS_HEIGHT:
  202. p = platforms[0]
  203. self.x = p.x + p.width / 2 - BALL_SIZE / 2
  204. self.y = CANVAS_HEIGHT - p.height - BALL_SIZE
  205. self.vy = 0
  206. self.vx = 0
  207. self.is_grounded = True
  208. self.on_platform = p
  209. dx = self.x - old_x
  210. dy = self.y - old_y
  211. self._move_parts(dx, dy)
  212. def get_center(self):
  213. return self.x + BALL_SIZE / 2, self.y + BALL_SIZE / 2
  214. class GamePlatform:
  215. COLORS = [
  216. COLOR_STANFORD_RED,
  217. COLOR_PERU_RED,
  218. COLOR_GOLD,
  219. COLOR_DARK_NAVY,
  220. "#C0392B"
  221. ]
  222. def __init__(self, canvas, x, width, height):
  223. self.canvas = canvas
  224. self.x = x
  225. self.width = width
  226. self.height = height
  227. self.color = random.choice(self.COLORS)
  228. self.drift = random.choice([-1, 1])
  229. self.rect_id = None
  230. self.draw()
  231. def draw(self):
  232. top_y = CANVAS_HEIGHT - self.height
  233. self.rect_id = self.canvas.create_rectangle(
  234. self.x, top_y,
  235. self.x + self.width, CANVAS_HEIGHT,
  236. self.color, COLOR_BRIGHT_GOLD
  237. )
  238. def update(self):
  239. self.x += self.drift
  240. self.canvas.move(self.rect_id, self.drift, 0)
  241. if self.x <= 0:
  242. self.drift = abs(self.drift)
  243. elif self.x + self.width >= CANVAS_WIDTH:
  244. self.drift = -abs(self.drift)
  245. def move(self, dx):
  246. self.x += dx
  247. self.canvas.move(self.rect_id, dx, 0)
  248. class CodeToken:
  249. SLOTS = [
  250. (100, 80), (280, 80), (440, 80),
  251. (100, 160), (280, 160), (440, 160),
  252. (180, 220), (340, 220),
  253. ]
  254. used_slots = []
  255. @classmethod
  256. def reset_slots(cls):
  257. cls.used_slots = []
  258. @classmethod
  259. def get_free_slot(cls):
  260. free = [s for s in cls.SLOTS if s not in cls.used_slots]
  261. if not free:
  262. cls.used_slots = []
  263. free = cls.SLOTS[:]
  264. slot = random.choice(free)
  265. cls.used_slots.append(slot)
  266. return slot
  267. def __init__(self, canvas):
  268. self.canvas = canvas
  269. self.active = True
  270. self.snippet = random.choice(CODE_SNIPPETS)
  271. self.rect_id = None
  272. self.text_id = None
  273. self.slot = None
  274. self._place()
  275. def _place(self):
  276. self.slot = CodeToken.get_free_slot()
  277. self.x, self.y = self.slot
  278. self.rect_id = self.canvas.create_rectangle(
  279. self.x, self.y,
  280. self.x + CODE_W, self.y + CODE_H,
  281. COLOR_GOLD, COLOR_BRIGHT_GOLD
  282. )
  283. self.text_id = self.canvas.create_text(
  284. self.x + CODE_W / 2,
  285. self.y + CODE_H / 2,
  286. text=self.snippet,
  287. font="Courier", font_size=24, color=COLOR_STANFORD_RED
  288. )
  289. def check_catch(self, ball_cx, ball_cy):
  290. if not self.active:
  291. return False
  292. cx = self.x + CODE_W / 2
  293. cy = self.y + CODE_H / 2
  294. if (abs(ball_cx - cx) < CODE_W / 2 + BALL_SIZE / 2 and
  295. abs(ball_cy - cy) < CODE_H / 2 + BALL_SIZE / 2):
  296. self.canvas.delete(self.rect_id)
  297. self.canvas.delete(self.text_id)
  298. if self.slot in CodeToken.used_slots:
  299. CodeToken.used_slots.remove(self.slot)
  300. self.active = False
  301. return True
  302. return False
  303. def respawn(self):
  304. self.snippet = random.choice(CODE_SNIPPETS)
  305. self._place()
  306. self.active = True
  307. def show_win_screen(canvas):
  308. canvas.create_rectangle(
  309. 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
  310. COLOR_DARK_NAVY, COLOR_DARK_NAVY
  311. )
  312. canvas.create_rectangle(
  313. 50, 100, 550, 310,
  314. COLOR_STANFORD_RED, COLOR_BRIGHT_GOLD
  315. )
  316. canvas.create_text(150, 135,
  317. text="YOU WIN!",
  318. font="Arial", font_size=38, color=COLOR_BRIGHT_GOLD)
  319. canvas.create_text(150, 175,
  320. text="Karel is fully built!",
  321. font="Arial", font_size=16, color=COLOR_WHITE)
  322. canvas.create_text(150, 205,
  323. text="You collected all 10 Karel codes!",
  324. font="Arial", font_size=13, color=COLOR_WHITE)
  325. canvas.create_text(150, 240,
  326. text="Final Score: 100 points",
  327. font="Arial", font_size=15, color=COLOR_BRIGHT_GOLD)
  328. canvas.create_text(150, 268,
  329. text="Keep coding! PERU @ STANFORD - CODE IN PLACE",
  330. font="Arial", font_size=11, color=COLOR_BRIGHT_GOLD)
  331. canvas.create_text(150, 290,
  332. text="YESSENIA CH. M.",
  333. font="Arial", font_size=10, color=COLOR_WHITE)
  334. def main():
  335. canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT)
  336. CodeToken.reset_slots()
  337. # Franja superior
  338. canvas.create_rectangle(
  339. 0, 0, CANVAS_WIDTH, 50,
  340. COLOR_STANFORD_RED, COLOR_STANFORD_RED
  341. )
  342. canvas.create_text(10, 15,
  343. text="CONTROLS: [ N ] Forward [ M ] Back [ B ] JUMP",
  344. font="Arial", font_size=11, color=COLOR_BRIGHT_GOLD)
  345. canvas.create_text(10, 35,
  346. text="CODE CATCHER — Collect 10 Karel codes to build Karel!",
  347. font="Arial", font_size=11, color=COLOR_WHITE)
  348. score_label = canvas.create_text(480, 25,
  349. text="Codes: 0 / 10",
  350. font="Arial", font_size=13, color=COLOR_BRIGHT_GOLD)
  351. canvas.create_text(300, CANVAS_HEIGHT - 8,
  352. text="YESSENIA CH.M. | PERU @ STANFORD UNIVERSITY",
  353. font="Arial", font_size=11, color=COLOR_STANFORD_RED)
  354. platforms = [
  355. GamePlatform(canvas, 30, 140, 80),
  356. GamePlatform(canvas, 230, 130, 130),
  357. GamePlatform(canvas, 420, 120, 100),
  358. ]
  359. codes = [CodeToken(canvas) for _ in range(CODE_COUNT)]
  360. p0 = platforms[0]
  361. ball = KarelBall(canvas, p0.x + 50, CANVAS_HEIGHT - p0.height - BALL_SIZE)
  362. ball.is_grounded = True
  363. ball.on_platform = p0
  364. speed = 12
  365. codes_caught = 0
  366. game_over = False
  367. while True:
  368. if game_over:
  369. time.sleep(0.1)
  370. continue
  371. key = canvas.get_last_key_press()
  372. if key:
  373. key = key.lower()
  374. move_speed = speed * 2 if not ball.is_grounded else speed
  375. if key == 'n':
  376. for platform in platforms:
  377. platform.move(-move_speed)
  378. elif key == 'm':
  379. for platform in platforms:
  380. platform.move(move_speed)
  381. elif key == 'b':
  382. ball.jump()
  383. for platform in platforms:
  384. platform.update()
  385. ball.update_physics(platforms)
  386. bx, by = ball.get_center()
  387. for code in codes:
  388. if code.check_catch(bx, by):
  389. codes_caught += 1
  390. # Karel evolves with every code he captures
  391. ball.evolve(codes_caught)
  392. canvas.delete(score_label)
  393. score_label = canvas.create_text(480, 25,
  394. text=f"Codes: {codes_caught} / {CODES_TO_WIN}",
  395. font="Arial", font_size=13, color=COLOR_BRIGHT_GOLD)
  396. if codes_caught >= CODES_TO_WIN:
  397. show_win_screen(canvas)
  398. game_over = True
  399. break
  400. code.respawn()
  401. time.sleep(0.04)
  402. if __name__ == '__main__':
  403. main()