Menu
CoddyTech

Lựa chọn có trọng số

Thuộc phần Luật chơi trong hành trình Pygame của Coddy. Bài học 18 trên 30.

Trò chơi sẽ thú vị hơn khi không phải mọi thứ rơi xuống đều giống nhau. Ở đây, một nửa số vật rơi xuống là đá nhỏ, một số là đá lớn và một vài viên là đá quý đáng giá 5 điểm. random.choice chọn mỗi vật với tần suất như nhau. random.choices nhận weights, chỉ định tần suất mỗi vật được chọn:

def pick_kind():
    return random.choices(["small", "big", "gem"], weights=[5, 3, 2])[0]

Các trọng số 5, 3 và 2 cộng lại thành 10, vì vậy cứ 10 vật rơi xuống thì có khoảng 5 vật nhỏ, 3 vật lớn và 2 viên đá quý. Trọng số biểu thị tỷ lệ, không phải phần trăm: [50, 30, 20] cũng có nghĩa tương tự.

random.choices có thể chọn nhiều vật cùng lúc, nên luôn trả về một danh sách, kể cả khi chỉ chọn một vật. [0] lấy vật đó ra khỏi danh sách.

Sau đó, mỗi loại sẽ trở thành một Rect riêng. Đá quý nằm trong danh sách riêng, gems, vì chạm vào đá quý là điều đáng mừng. Phần code khởi đầu đã di chuyển, vẽ và bắt đá quý, nhưng chưa có viên nào rơi xuống.

challenge icon

Thử thách

Trung bình

Trộn đá lớn và đá quý cùng với đá nhỏ.

  1. Viết pick_kind(): "small", "big" hoặc "gem" với trọng số 5, 3 và 2.
  2. Khi có SPAWN, thả vật phẩm đã chọn: đá nhỏ kích thước 24 x 24, đá lớn kích thước 40 x 40 (x từ 0 đến 440, y -40) hoặc đá quý kích thước 20 x 20 trong gems (x từ 0 đến 460, y -20).

Nhấn Chơi: đá lớn và đá quý cũng rơi xuống. Sau đó nhấn Kiểm tra.

Tự làm thử

import random
import pygame

pygame.init()
screen = pygame.display.set_mode((480, 360))
pygame.display.set_caption("Meteor Dodge")
clock = pygame.time.Clock()
heart = pygame.image.load("assets/images/heart.png").convert_alpha()
heart_empty = pygame.image.load("assets/images/heart_empty.png").convert_alpha()
font = pygame.font.Font("assets/fonts/pixel.ttf", 24)
big_font = pygame.font.Font("assets/fonts/pixel.ttf", 48)
WHITE = (240, 240, 240)
PLATE = (60, 60, 90)
POINT = pygame.USEREVENT + 1
SPAWN = pygame.USEREVENT + 2


def reset():
    global player, rocks, gems, score, lives
    player = pygame.Rect(220, 300, 40, 40)
    rocks = []
    gems = []
    score = 0
    lives = 3
    pygame.time.set_timer(POINT, 1000)
    pygame.time.set_timer(SPAWN, 500)


def draw_menu():
    screen.fill((30, 30, 46))
    title = big_font.render("Meteor Dodge", True, WHITE)
    title_rect = title.get_rect(center=(240, 140))
    pygame.draw.rect(screen, PLATE, title_rect.inflate(24, 12))
    screen.blit(title, title_rect)
    hint = font.render("Press Enter to play", True, WHITE)
    screen.blit(hint, hint.get_rect(center=(240, 200)))


def draw_game():
    screen.fill((30, 30, 46))
    pygame.draw.rect(screen, (255, 196, 0), player)
    for rock in rocks:
        pygame.draw.rect(screen, (235, 80, 80), rock)
    for gem in gems:
        pygame.draw.rect(screen, (90, 220, 120), gem)
    score_text = font.render(f"{score:05}", True, WHITE)
    score_rect = score_text.get_rect(topright=(470, 10))
    pygame.draw.rect(screen, PLATE, score_rect.inflate(12, 8))
    screen.blit(score_text, score_rect)
    for i in range(3):
        if i < lives:
            screen.blit(heart, (10 + i * 40, 10))
        else:
            screen.blit(heart_empty, (10 + i * 40, 10))


def play_frame():
    global rocks, gems, score, lives, state
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT]:
        player.x -= 6
    if keys[pygame.K_RIGHT]:
        player.x += 6
    player.clamp_ip(screen.get_rect())

    kept = []
    for rock in rocks:
        rock.y += 3
        if rock.colliderect(player):
            lives -= 1
        elif rock.top <= 360:
            kept.append(rock)
    rocks = kept
    kept = []
    for gem in gems:
        gem.y += 3
        if gem.colliderect(player):
            score += 5
        elif gem.top <= 360:
            kept.append(gem)
    gems = kept
    if lives <= 0:
        state = "over"
    draw_game()


def draw_over():
    draw_game()
    message = big_font.render("Game over", True, WHITE)
    screen.blit(message, message.get_rect(center=(240, 160)))
    hint = font.render("Press R to play again", True, WHITE)
    screen.blit(hint, hint.get_rect(center=(240, 200)))


reset()
state = "menu"

running = True
while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == POINT and state == "play":
            score += 1
        if event.type == SPAWN and state == "play":
            rocks.append(pygame.Rect(random.randint(0, 456), -24, 24, 24))
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_RETURN and state == "menu":
                reset()
                state = "play"
            elif event.key == pygame.K_r and state == "over":
                reset()
                state = "play"

    if state == "menu":
        draw_menu()
    elif state == "play":
        play_frame()
    else:
        draw_over()
    pygame.display.flip()
    clock.tick(60)

pygame.quit()
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Luật chơi

Tự luyện tập: Playground Pygame