from graphics import Canvas import random import time # ====================================================== # MADE BY : YESSENIA CHOQUEHUANCA MASIAS - COUNTRY: PERÚ # PROJECT : Code Catcher - Catch the Codes # UNIVERSITY: STANFORD - Peru # COLORS : Peru Gold/Red # ====================================================== CANVAS_WIDTH = 600 CANVAS_HEIGHT = 400 BALL_SIZE = 25 GRAVITY = 0.9 JUMP_STRENGTH = -22 BOUNCE_FACTOR = 0.6 MIN_BOUNCE_VY = 3.0 CODE_W = 110 CODE_H = 26 CODE_COUNT = 4 CODES_TO_WIN = 10 COLOR_STANFORD_RED = "#800b0bff" COLOR_PERU_RED = "#BF0000" COLOR_GOLD = "#c49620ff" COLOR_BRIGHT_GOLD = "#938224ff" COLOR_DARK_NAVY = "#1a1a2e" COLOR_WHITE = "WHITE" COLOR_GRAY = "#AAAAAA" COLOR_BLACK = "black" CODE_SNIPPETS = [ "move()", "pick_beeper()", "turn_left()", "turn_right()", "put_beeper()", ] class KarelBall: """ Pelota que se convierte en Karel parte a parte. Nivel 0 → pelota blanca Nivel 1-2 → cuerpo Nivel 3-4 → piernas Nivel 5-6 → brazos Nivel 7-8 → cabeza/pantalla Nivel 9-10 → Karel completo con antena """ def __init__(self, canvas, x, y): self.canvas = canvas self.x = x self.y = y self.vx = 0 self.vy = 0 self.is_grounded = False self.on_platform = None self.level = 0 # how many codes has he caught # IDs of each drawn part self.parts = {} self._draw_level() # -------------------------------------------------- # DRAWN # -------------------------------------------------- def _clear(self): for pid in self.parts.values(): self.canvas.delete(pid) self.parts = {} def _draw_level(self): self._clear() x, y = self.x, self.y # top-left corner of the sprite (25x25) # --- LEVEL 0: single ball --- self.parts['body_oval'] = self.canvas.create_oval( x, y, x + 25, y + 25, COLOR_WHITE, COLOR_STANFORD_RED ) if self.level >= 2: # Gray square (replaces the oval with a rectangle on top) self.parts['body'] = self.canvas.create_rectangle( x + 4, y + 8, x + 21, y + 22, COLOR_GRAY, COLOR_BLACK ) if self.level >= 4: # Left leg self.parts['leg_l'] = self.canvas.create_rectangle( x + 5, y + 22, x + 10, y + 30, COLOR_GRAY, COLOR_BLACK ) # Right leg self.parts['leg_r'] = self.canvas.create_rectangle( x + 15, y + 22, x + 20, y + 30, COLOR_GRAY, COLOR_BLACK ) # Left foot self.parts['foot_l'] = self.canvas.create_rectangle( x + 3, y + 28, x + 11, y + 32, COLOR_BLACK, COLOR_BLACK ) # Ceiling height self.parts['foot_r'] = self.canvas.create_rectangle( x + 14, y + 28, x + 22, y + 32, COLOR_BLACK, COLOR_BLACK ) if self.level >= 6: # Left arm self.parts['arm_l'] = self.canvas.create_rectangle( x - 5, y + 10, x + 4, y + 15, COLOR_GRAY, COLOR_BLACK ) # Right-hand man self.parts['arm_r'] = self.canvas.create_rectangle( x + 21, y + 10, x + 30, y + 15, COLOR_GRAY, COLOR_BLACK ) # Left hand self.parts['hand_l'] = self.canvas.create_oval( x - 8, y + 8, x - 2, y + 17, COLOR_BLACK, COLOR_BLACK ) # Right hand self.parts['hand_r'] = self.canvas.create_oval( x + 27, y + 8, x + 33, y + 17, COLOR_BLACK, COLOR_BLACK ) if self.level >= 8: # Head self.parts['head'] = self.canvas.create_rectangle( x + 5, y - 10, x + 20, y + 8, COLOR_GRAY, COLOR_BLACK ) # Screen (eye) self.parts['screen'] = self.canvas.create_rectangle( x + 8, y - 8, x + 17, y + 2, COLOR_DARK_NAVY, COLOR_BLACK ) # Screen resolution self.parts['pixel'] = self.canvas.create_rectangle( x + 10, y - 5, x + 15, y - 1, COLOR_BRIGHT_GOLD, COLOR_BRIGHT_GOLD ) if self.level >= 10: # AntenNa self.parts['antenna'] = self.canvas.create_rectangle( x + 11, y - 18, x + 14, y - 10, COLOR_BLACK, COLOR_BLACK ) # Antenna tip self.parts['antenna_tip'] = self.canvas.create_oval( x + 9, y - 22, x + 16, y - 16, COLOR_BRIGHT_GOLD, COLOR_BLACK ) def _move_parts(self, dx, dy): for pid in self.parts.values(): self.canvas.move(pid, dx, dy) # -------------------------------------------------- # DEVELOPMENT # -------------------------------------------------- def evolve(self, codes_caught): self.level = codes_caught self._draw_level() # -------------------------------------------------- # PHYSICS # -------------------------------------------------- def jump(self): if self.is_grounded: self.vy = JUMP_STRENGTH self.is_grounded = False self.on_platform = None def update_physics(self, platforms): if self.is_grounded and self.on_platform is not None: dx = self.on_platform.drift self.x += dx plat_top = CANVAS_HEIGHT - self.on_platform.height self.y = plat_top - BALL_SIZE self._move_parts(dx, 0) self.vy += GRAVITY old_x, old_y = self.x, self.y self.y += self.vy self.x += self.vx self.is_grounded = False self.on_platform = None ball_bottom = self.y + BALL_SIZE ball_center_x = self.x + BALL_SIZE / 2 for platform in platforms: plat_top = CANVAS_HEIGHT - platform.height if (self.vy >= 0 and plat_top - 8 <= ball_bottom <= plat_top + self.vy + 2): if platform.x <= ball_center_x <= platform.x + platform.width: self.y = plat_top - BALL_SIZE if abs(self.vy) > MIN_BOUNCE_VY: self.vy = -self.vy * BOUNCE_FACTOR else: self.vy = 0 self.is_grounded = True self.on_platform = platform break if self.x < 0: self.x = 0 self.vx = abs(self.vx) * BOUNCE_FACTOR elif self.x + BALL_SIZE > CANVAS_WIDTH: self.x = CANVAS_WIDTH - BALL_SIZE self.vx = -abs(self.vx) * BOUNCE_FACTOR if self.y < 0: self.y = 0 self.vy = abs(self.vy) * BOUNCE_FACTOR if self.y > CANVAS_HEIGHT: p = platforms[0] self.x = p.x + p.width / 2 - BALL_SIZE / 2 self.y = CANVAS_HEIGHT - p.height - BALL_SIZE self.vy = 0 self.vx = 0 self.is_grounded = True self.on_platform = p dx = self.x - old_x dy = self.y - old_y self._move_parts(dx, dy) def get_center(self): return self.x + BALL_SIZE / 2, self.y + BALL_SIZE / 2 class GamePlatform: COLORS = [ COLOR_STANFORD_RED, COLOR_PERU_RED, COLOR_GOLD, COLOR_DARK_NAVY, "#C0392B" ] def __init__(self, canvas, x, width, height): self.canvas = canvas self.x = x self.width = width self.height = height self.color = random.choice(self.COLORS) self.drift = random.choice([-1, 1]) self.rect_id = None self.draw() def draw(self): top_y = CANVAS_HEIGHT - self.height self.rect_id = self.canvas.create_rectangle( self.x, top_y, self.x + self.width, CANVAS_HEIGHT, self.color, COLOR_BRIGHT_GOLD ) def update(self): self.x += self.drift self.canvas.move(self.rect_id, self.drift, 0) if self.x <= 0: self.drift = abs(self.drift) elif self.x + self.width >= CANVAS_WIDTH: self.drift = -abs(self.drift) def move(self, dx): self.x += dx self.canvas.move(self.rect_id, dx, 0) class CodeToken: SLOTS = [ (100, 80), (280, 80), (440, 80), (100, 160), (280, 160), (440, 160), (180, 220), (340, 220), ] used_slots = [] @classmethod def reset_slots(cls): cls.used_slots = [] @classmethod def get_free_slot(cls): free = [s for s in cls.SLOTS if s not in cls.used_slots] if not free: cls.used_slots = [] free = cls.SLOTS[:] slot = random.choice(free) cls.used_slots.append(slot) return slot def __init__(self, canvas): self.canvas = canvas self.active = True self.snippet = random.choice(CODE_SNIPPETS) self.rect_id = None self.text_id = None self.slot = None self._place() def _place(self): self.slot = CodeToken.get_free_slot() self.x, self.y = self.slot self.rect_id = self.canvas.create_rectangle( self.x, self.y, self.x + CODE_W, self.y + CODE_H, COLOR_GOLD, COLOR_BRIGHT_GOLD ) self.text_id = self.canvas.create_text( self.x + CODE_W / 2, self.y + CODE_H / 2, text=self.snippet, font="Courier", font_size=24, color=COLOR_STANFORD_RED ) def check_catch(self, ball_cx, ball_cy): if not self.active: return False cx = self.x + CODE_W / 2 cy = self.y + CODE_H / 2 if (abs(ball_cx - cx) < CODE_W / 2 + BALL_SIZE / 2 and abs(ball_cy - cy) < CODE_H / 2 + BALL_SIZE / 2): self.canvas.delete(self.rect_id) self.canvas.delete(self.text_id) if self.slot in CodeToken.used_slots: CodeToken.used_slots.remove(self.slot) self.active = False return True return False def respawn(self): self.snippet = random.choice(CODE_SNIPPETS) self._place() self.active = True def show_win_screen(canvas): canvas.create_rectangle( 0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, COLOR_DARK_NAVY, COLOR_DARK_NAVY ) canvas.create_rectangle( 50, 100, 550, 310, COLOR_STANFORD_RED, COLOR_BRIGHT_GOLD ) canvas.create_text(150, 135, text="YOU WIN!", font="Arial", font_size=38, color=COLOR_BRIGHT_GOLD) canvas.create_text(150, 175, text="Karel is fully built!", font="Arial", font_size=16, color=COLOR_WHITE) canvas.create_text(150, 205, text="You collected all 10 Karel codes!", font="Arial", font_size=13, color=COLOR_WHITE) canvas.create_text(150, 240, text="Final Score: 100 points", font="Arial", font_size=15, color=COLOR_BRIGHT_GOLD) canvas.create_text(150, 268, text="Keep coding! PERU @ STANFORD - CODE IN PLACE", font="Arial", font_size=11, color=COLOR_BRIGHT_GOLD) canvas.create_text(150, 290, text="YESSENIA CH. M.", font="Arial", font_size=10, color=COLOR_WHITE) def main(): canvas = Canvas(CANVAS_WIDTH, CANVAS_HEIGHT) CodeToken.reset_slots() # Franja superior canvas.create_rectangle( 0, 0, CANVAS_WIDTH, 50, COLOR_STANFORD_RED, COLOR_STANFORD_RED ) canvas.create_text(10, 15, text="CONTROLS: [ N ] Forward [ M ] Back [ B ] JUMP", font="Arial", font_size=11, color=COLOR_BRIGHT_GOLD) canvas.create_text(10, 35, text="CODE CATCHER — Collect 10 Karel codes to build Karel!", font="Arial", font_size=11, color=COLOR_WHITE) score_label = canvas.create_text(480, 25, text="Codes: 0 / 10", font="Arial", font_size=13, color=COLOR_BRIGHT_GOLD) canvas.create_text(300, CANVAS_HEIGHT - 8, text="YESSENIA CH.M. | PERU @ STANFORD UNIVERSITY", font="Arial", font_size=11, color=COLOR_STANFORD_RED) platforms = [ GamePlatform(canvas, 30, 140, 80), GamePlatform(canvas, 230, 130, 130), GamePlatform(canvas, 420, 120, 100), ] codes = [CodeToken(canvas) for _ in range(CODE_COUNT)] p0 = platforms[0] ball = KarelBall(canvas, p0.x + 50, CANVAS_HEIGHT - p0.height - BALL_SIZE) ball.is_grounded = True ball.on_platform = p0 speed = 12 codes_caught = 0 game_over = False while True: if game_over: time.sleep(0.1) continue key = canvas.get_last_key_press() if key: key = key.lower() move_speed = speed * 2 if not ball.is_grounded else speed if key == 'n': for platform in platforms: platform.move(-move_speed) elif key == 'm': for platform in platforms: platform.move(move_speed) elif key == 'b': ball.jump() for platform in platforms: platform.update() ball.update_physics(platforms) bx, by = ball.get_center() for code in codes: if code.check_catch(bx, by): codes_caught += 1 # Karel evolves with every code he captures ball.evolve(codes_caught) canvas.delete(score_label) score_label = canvas.create_text(480, 25, text=f"Codes: {codes_caught} / {CODES_TO_WIN}", font="Arial", font_size=13, color=COLOR_BRIGHT_GOLD) if codes_caught >= CODES_TO_WIN: show_win_screen(canvas) game_over = True break code.respawn() time.sleep(0.04) if __name__ == '__main__': main()