Menu
CoddyTech

הימלטות מהמבצר

חלק מהיחידה פרויקטים במסלול ה-Pygame של Coddy. שיעור 29 מתוך 31.

דרך הדלת של החדר האחרון האביר יוצא: המשחק מציג You escaped!. אחרי כל אחד משני הסיומים, לחיצה על R מתחילה מחדש בחדר הראשון.

כעת לבדיקת הדלת יש שתי תוצאות:

if room.door_open and knight.rect.colliderect(room.door):
    if level == len(ROOMS) - 1:
        state = "won"
    else:
        level += 1
        room = Room(ROOMS[level])
        knight.rect.center = room.start

ההתחלה מחדש קצרה, כי המחלקות מחזיקות את מצב המשחק. Room חדש בונה מחדש את החדר הראשון, עם המפתח שלו, הדלת וכל הסליימים במקומם. ל־Knight חדש יש 3 לבבות, הוא לא פצוע ולא מניף את נשקו.

elif event.key == pygame.K_r and state != "playing":
    level = 0
    room = Room(ROOMS[level])
    knight = Knight(room.start)
    state = "playing"

שחקו במשחק מההתחלה. אם חדר קשה מדי או קצר מדי, שנו את המפות: הזיזו סליים, הוסיפו שיקוי, ציירו חדר חדש והוסיפו אותו ל־ROOMS. אין צורך לשנות שום דבר אחר, כי כל כלל קורא את המפה. אי אפשר לעבור על פני סליים במסדרון שרוחבו אריח אחד, אלא רק להילחם בו; אפשר להתחמק מסליים באולם פתוח.

challenge icon

אתגר

בינוני
  1. שנה את בדיקת הדלת כפי שמתואר למעלה: בחדר האחרון, הגדר את המצב ל־"won".
  2. כשהמצב הוא "won", הצג את You escaped! במרכז הנקודה (336, 240).
  3. אחרי אחד משני סיומי המשחק, הצג את Press R to play again במרכז הנקודה (336, 290).
  4. אירוע KEYDOWN של R כשהמצב אינו "playing" מתחיל את המשחק מחדש כפי שמתואר למעלה.

נסו בעצמכם

import random
import pygame

TILE = 48
MAP = [
    "##############",
    "#............#",
    "#.P......K...#",
    "#............#",
    "#....##......D",
    "#....##......#",
    "##.#######.###",
    "#............#",
    "#...S....H...#",
    "##############",
]
MAP2 = [
    "##############",
    "#P...#...X...#",
    "#....#.......#",
    "#..S.#..###..#",
    "#....#..#K...#",
    "#.......#....#",
    "##.######X####",
    "#....S.......#",
    "#.H..........#",
    "###D##########",
]
MAP3 = [
    "##############",
    "#....#...X...#",
    "#.P..#.......#",
    "#....#..S....#",
    "#..S.....###.D",
    "#....#...#K.S#",
    "###.###X.#...#",
    "#........#...#",
    "#..H.....#...#",
    "##############",
]
ROOMS = [MAP, MAP2, MAP3]

pygame.init()
screen = pygame.display.set_mode((672, 480))
pygame.display.set_caption("Rune Keep")
clock = pygame.time.Clock()
font = pygame.font.Font("assets/fonts/pixel.ttf", 32)


def load(name):
    return pygame.image.load("assets/images/" + name).convert_alpha()


floor_img = load("floor.png")
wall_img = load("wall.png")
knight_img = load("knight.png")
door_img = load("dungeon_door.png")
key_img = load("key.png")
slime_img = load("slime.png")
spider_img = load("spider.png")
heart_img = load("heart.png")
empty_img = load("heart_empty.png")
sword_img = load("sword.png")
potion_img = load("potion_red.png")
ANGLES = {(0, -1): 0, (-1, 0): 90, (0, 1): 180, (1, 0): -90}


def draw(image, rect):
    screen.blit(image, image.get_rect(center=rect.center))


def cell_rect(col, row):
    rect = pygame.Rect(0, 0, 32, 32)
    rect.center = (col * TILE + 24, row * TILE + 24)
    return rect


class Enemy(pygame.sprite.Sprite):
    def __init__(self, image, rect, dx, dy):
        super().__init__()
        self.image = image
        self.rect = rect
        self.dx = dx
        self.dy = dy

    def update(self, blocks):
        self.rect.x += self.dx
        self.rect.y += self.dy
        if self.rect.collidelist(blocks) != -1:
            self.rect.x -= self.dx
            self.rect.y -= self.dy
            self.dx = -self.dx
            self.dy = -self.dy


class Room:
    def __init__(self, lines):
        self.walls = []
        self.enemies = pygame.sprite.Group()
        self.potions = []
        self.start = (72, 72)
        self.has_key = False
        self.door_open = False
        for row, line in enumerate(lines):
            for col, char in enumerate(line):
                if char == "#":
                    self.walls.append(pygame.Rect(col * TILE, row * TILE, TILE, TILE))
                elif char == "P":
                    self.start = cell_rect(col, row).center
                elif char == "K":
                    self.key = cell_rect(col, row)
                elif char == "D":
                    self.door = pygame.Rect(col * TILE, row * TILE, TILE, TILE)
                elif char == "S":
                    speed = random.choice([-2, 2])
                    self.enemies.add(Enemy(slime_img, cell_rect(col, row), speed, 0))
                elif char == "X":
                    speed = random.choice([-2, 2])
                    self.enemies.add(Enemy(spider_img, cell_rect(col, row), 0, speed))
                elif char == "H":
                    self.potions.append(cell_rect(col, row))

    def blocks(self):
        if self.door_open:
            return self.walls
        return self.walls + [self.door]


class Knight(pygame.sprite.Sprite):
    def __init__(self, center):
        super().__init__()
        self.image = knight_img
        self.rect = pygame.Rect(0, 0, 32, 32)
        self.rect.center = center
        self.hearts = 3
        self.hurt = 0
        self.facing = (1, 0)
        self.swing = 0

    def move(self, dx, dy, blocks):
        self.rect.x += dx
        for block in blocks:
            if self.rect.colliderect(block):
                if dx > 0:
                    self.rect.right = block.left
                elif dx < 0:
                    self.rect.left = block.right
        self.rect.y += dy
        for block in blocks:
            if self.rect.colliderect(block):
                if dy > 0:
                    self.rect.bottom = block.top
                elif dy < 0:
                    self.rect.top = block.bottom

    def sword(self):
        box = pygame.Rect(0, 0, 36, 36)
        box.centerx = self.rect.centerx + self.facing[0] * 34
        box.centery = self.rect.centery + self.facing[1] * 34
        return box


level = 0
room = Room(ROOMS[level])
knight = Knight(room.start)
state = "playing"

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE and state == "playing" and knight.swing == 0:
                knight.swing = 12

    if state == "playing":
        keys = pygame.key.get_pressed()
        dx = 0
        dy = 0
        if keys[pygame.K_LEFT]:
            dx = -3
            knight.facing = (-1, 0)
        if keys[pygame.K_RIGHT]:
            dx = 3
            knight.facing = (1, 0)
        if keys[pygame.K_UP]:
            dy = -3
            knight.facing = (0, -1)
        if keys[pygame.K_DOWN]:
            dy = 3
            knight.facing = (0, 1)
        knight.move(dx, dy, room.blocks())
        knight.rect.clamp_ip(screen.get_rect())

        if not room.has_key and knight.rect.colliderect(room.key):
            room.has_key = True
        if room.has_key and knight.rect.inflate(8, 8).colliderect(room.door):
            room.door_open = True
        if room.door_open and knight.rect.colliderect(room.door) and level < len(ROOMS) - 1:
            level += 1
            room = Room(ROOMS[level])
            knight.rect.center = room.start

        room.enemies.update(room.walls + [room.door])

        if knight.swing > 0:
            knight.swing -= 1
            for enemy in room.enemies:
                if enemy.rect.colliderect(knight.sword()):
                    enemy.kill()

        if knight.hurt > 0:
            knight.hurt -= 1
        elif pygame.sprite.spritecollide(knight, room.enemies, False):
            knight.hearts -= 1
            knight.hurt = 60
            if knight.hearts == 0:
                state = "lost"

        index = knight.rect.collidelist(room.potions)
        if index != -1 and knight.hearts < 3:
            knight.hearts += 1
            room.potions.pop(index)

    for row in range(10):
        for col in range(14):
            screen.blit(floor_img, (col * TILE, row * TILE))
    for wall in room.walls:
        screen.blit(wall_img, wall)
    if not room.door_open:
        screen.blit(door_img, room.door)
    if not room.has_key:
        draw(key_img, room.key)
    for potion in room.potions:
        draw(potion_img, potion)
    for enemy in room.enemies:
        draw(enemy.image, enemy.rect)
    if knight.hurt % 10 < 5:
        draw(knight.image, knight.rect)
    if knight.swing > 0:
        blade = pygame.transform.rotate(sword_img, ANGLES[knight.facing])
        draw(blade, knight.sword())
    for i in range(3):
        if i < knight.hearts:
            screen.blit(heart_img, (8 + i * 40, 6))
        else:
            screen.blit(empty_img, (8 + i * 40, 6))
    if room.has_key:
        screen.blit(key_img, (136, 6))
    label = font.render(f"Room {level + 1} of {len(ROOMS)}", True, (240, 240, 240))
    screen.blit(label, label.get_rect(topright=(660, 8)))
    if state == "lost":
        message = font.render("Game over", True, (240, 240, 240))
        screen.blit(message, message.get_rect(center=(336, 240)))
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

כל השיעורים ביחידה פרויקטים

תרגלו בעצמכם: Pygame אונליין