| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- 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()
|