Hướng Dẫn Lập Trình Game Rắn Săn Mồi Với Python

Lập trình game rắn săn mồi

Trong bài này chúng ta sẽ cùng lập trình game rắn săn mồi (Snake) bằng ngôn ngữ Python và thư viện Pygame, đi từ tạo cửa sổ game, vẽ con rắn, điều khiển bằng phím mũi tên, cho rắn ăn mồi, dài ra và tính điểm.

Học xong biến, vòng lặp, hàm mà chưa làm ra được cái gì nhìn thấy được thì khá chán đúng không nào. Game rắn săn mồi là bài tập rất hợp để ráp tất cả lại với nhau, code chưa tới 150 dòng mà chơi được thật. Mỗi bước bên dưới là một chương trình hoàn chỉnh, các bạn copy về chạy thử từng bước một nhé.

Giới thiệu về game rắn săn mồi với Python

Game rắn săn mồi chắc ai cũng từng chơi trên mấy chiếc điện thoại Nokia ngày xưa. Trò chơi xuất hiện từ những năm 1970 và cực kỳ phổ biến tới tận những năm 1990.

Luật chơi rất đơn giản: người chơi điều khiển một con rắn đi ăn mồi, mỗi lần ăn thì rắn dài thêm một đốt. Rắn đâm vào tường hoặc tự cắn vào thân mình là thua.

Game rắn săn mồi
Hình 1. Game rắn săn mồi

Cách lập trình game rắn săn mồi với Python

Chúng ta sẽ dùng thư viện Pygame để vẽ đồ họa và bắt sự kiện bàn phím. Trong game có 2 đối tượng chính là con rắn và mồi, con rắn được điều khiển bằng 4 phím mũi tên.

Cách làm là đi từng bước nhỏ, mỗi bước thêm một tính năng, chạy được rồi mới làm tiếp.

Cài đặt module Pygame

Đầu tiên cài module Pygame bằng pip:

pip install pygame

Cài xong là có thể import pygame và bắt đầu viết code.

Module Pygame
Hình 2. Module Pygame

Khởi tạo màn hình game

Mọi chương trình Pygame đều bắt đầu bằng pygame.init() để khởi tạo thư viện và kết thúc bằng pygame.quit() để giải phóng tài nguyên. Cửa sổ game được tạo bằng hàm pygame.display.set_mode().

Sau mỗi lần vẽ, phải gọi pygame.display.update() để đẩy những gì vừa vẽ lên màn hình. Ngoài ra còn có hàm pygame.display.flip() cũng làm việc tương tự: flip() luôn cập nhật toàn bộ màn hình, còn update() có thể truyền vào một vùng để chỉ cập nhật vùng đó (không truyền gì thì cập nhật toàn bộ).

import pygame

pygame.init()

# Tạo một màn hình hiển thị trò chơi với kích thước chiều rộng là 400 và chiều cao là 300
dis = pygame.display.set_mode((400, 300))

# Cập nhật những thay đổi lên màn hình
pygame.display.update()

pygame.quit()
quit()

Chạy đoạn code này, các bạn sẽ thấy cửa sổ hiện lên rồi tắt ngay lập tức. Lý do là chương trình chạy thẳng một mạch tới pygame.quit() mà không có gì giữ nó lại.

Muốn cửa sổ đứng yên thì phải có một vòng lặp chạy liên tục, gọi là vòng lặp game (game loop). Trong vòng lặp, dùng pygame.event.get() để lấy các sự kiện như nhấn phím, bấm chuột, bấm nút đóng cửa sổ. Khi gặp sự kiện pygame.QUIT (người dùng bấm nút X) thì mới thoát vòng lặp. Chúng ta cũng đặt tên cửa sổ là “Snake game” bằng pygame.display.set_caption().

import pygame

pygame.init()
dis = pygame.display.set_mode((400, 300))
pygame.display.update()
pygame.display.set_caption('Snake game')

game_end = False
while not game_end:
    for event in pygame.event.get():
        # Phát hiện thao tác thoát khỏi trò chơi của người dùng
        if event.type == pygame.QUIT:
            game_end = True

pygame.quit()
quit()

Giờ cửa sổ đã đứng yên và bấm nút X thì tắt được.

Lưu ý: Nếu không xử lý sự kiện pygame.QUIT, hoặc không gọi pygame.event.get() trong vòng lặp, cửa sổ game sẽ bị treo (Not Responding) và bấm X không tắt được. Lúc đó phải tắt bằng Ctrl + C trong terminal hoặc Task Manager.

1. Tạo và hiển thị con rắn lên màn hình

Bước tiếp theo là vẽ con rắn. Màu sắc trong Pygame biểu diễn theo hệ RGB, mỗi màu là một bộ 3 số từ 0 đến 255. Ví dụ (0, 0, 0) là màu đen, (255, 255, 255) là màu trắng, (0, 0, 255) là xanh dương.

Con rắn lúc đầu thực ra chỉ là một ô vuông nhỏ, vẽ bằng hàm pygame.draw.rect() với tham số là màn hình, màu và vị trí kích thước [x, y, rộng, cao].

import pygame

pygame.init()
dis = pygame.display.set_mode((400, 300))
pygame.display.set_caption('Snake game')

# Tạo màu cho con rắn
blue = (0, 0, 255)
red = (255, 0, 0)

game_end = False
while not game_end:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game_end = True

    # Vẽ một ô vuông màu xanh nước biển (đại diện cho đầu con rắn) lên màn hình
    # tại vị trí (x, y) = (200, 150), chiều rộng và chiều cao là 10
    pygame.draw.rect(dis, blue, [200, 150, 10, 10])
    pygame.display.update()

pygame.quit()
quit()

Chạy lên sẽ thấy một ô vuông xanh ở giữa màn hình. Giờ chúng ta cho nó di chuyển.

Để bắt phím, dùng sự kiện pygame.KEYDOWN (có phím được nhấn). Bên trong kiểm tra event.key là phím nào: K_UP, K_DOWN, K_LEFT, K_RIGHT tương ứng với 4 phím mũi tên.

Thêm 2 biến x1_change và y1_change để lưu hướng di chuyển. Mỗi vòng lặp cộng chúng vào tọa độ x1, y1, con rắn sẽ tự đi liên tục theo hướng đã chọn. Mỗi vòng cũng phải tô lại nền bằng dis.fill(), nếu không vị trí cũ sẽ còn nguyên và con rắn để lại một vệt dài. Cuối cùng clock.tick(30) giới hạn game chạy 30 khung hình mỗi giây, đây chính là tốc độ của con rắn.

import pygame

pygame.init()

white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)

dis = pygame.display.set_mode((800, 600))
pygame.display.set_caption('Snake game')

game_end = False

x1 = 300
y1 = 300

x1_change = 0
y1_change = 0

clock = pygame.time.Clock()

while not game_end:
    for event in pygame.event.get():
        # Nếu kiểu sự kiện là QUIT, thực hiện kết thúc trò chơi
        if event.type == pygame.QUIT:
            game_end = True
        # Nếu kiểu sự kiện là KEYDOWN (có phím được nhấn xuống)
        if event.type == pygame.KEYDOWN:
            # Phím mũi tên trái: đầu con rắn đi sang trái
            if event.key == pygame.K_LEFT:
                x1_change = -10
                y1_change = 0
            # Phím mũi tên phải: đầu con rắn đi sang phải
            elif event.key == pygame.K_RIGHT:
                x1_change = 10
                y1_change = 0
            # Phím mũi tên lên: đầu con rắn đi lên
            elif event.key == pygame.K_UP:
                y1_change = -10
                x1_change = 0
            # Phím mũi tên xuống: đầu con rắn đi xuống
            elif event.key == pygame.K_DOWN:
                y1_change = 10
                x1_change = 0

    # Cập nhật tọa độ cho đầu con rắn
    x1 += x1_change
    y1 += y1_change

    # Tô lại toàn bộ màn hình màu trắng
    dis.fill(white)

    # Vẽ lại con rắn tại vị trí mới
    pygame.draw.rect(dis, black, [x1, y1, 10, 10])
    pygame.display.update()

    # Điều chỉnh tốc độ của trò chơi
    clock.tick(30)

pygame.quit()
quit()

Theo luật chơi, con rắn chạm vào mép màn hình là thua. Chúng ta dùng lệnh if so sánh tọa độ đầu rắn với kích thước màn hình: nếu x1 vượt quá chiều rộng hoặc nhỏ hơn 0, hay y1 vượt quá chiều cao hoặc nhỏ hơn 0 thì kết thúc game.

Kích thước màn hình, kích thước một đốt rắn và tốc độ cũng được đưa vào biến (dis_width, dis_height, snake_block, snake_speed), sau này muốn chỉnh chỉ cần sửa một chỗ.

import pygame
import time

pygame.init()

white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)

dis_width = 800
dis_height = 600
dis = pygame.display.set_mode((dis_width, dis_height))
pygame.display.set_caption('Snake game')

game_end = False

x1 = dis_width / 2
y1 = dis_height / 2

snake_block = 10

x1_change = 0
y1_change = 0

clock = pygame.time.Clock()
snake_speed = 30

font_style = pygame.font.SysFont(None, 50)

def message_func(msg, color):
    mesg = font_style.render(msg, True, color)
    dis.blit(mesg, [dis_width / 2, dis_height / 2])

while not game_end:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            game_end = True
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_LEFT:
                x1_change = -snake_block
                y1_change = 0
            elif event.key == pygame.K_RIGHT:
                x1_change = snake_block
                y1_change = 0
            elif event.key == pygame.K_UP:
                y1_change = -snake_block
                x1_change = 0
            elif event.key == pygame.K_DOWN:
                y1_change = snake_block
                x1_change = 0

    # Kiểm tra đầu con rắn đã chạm vào ranh giới màn hình hay chưa
    if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0:
        game_end = True

    x1 += x1_change
    y1 += y1_change
    dis.fill(white)
    pygame.draw.rect(dis, black, [x1, y1, snake_block, snake_block])
    pygame.display.update()

    clock.tick(snake_speed)

# Hiển thị thông báo người chơi đã thua
message_func("You lost", red)
pygame.display.update()
time.sleep(2)

pygame.quit()
quit()

Thua thì dòng chữ “You lost” hiện lên trong 2 giây rồi game tự đóng.

2. Cung cấp đồ ăn cho con rắn

Giờ thêm mồi cho rắn. Vị trí mồi được chọn ngẫu nhiên bằng random.randrange(), sau đó làm tròn về bội số của 10 để mồi nằm thẳng hàng với các ô mà con rắn đi qua. Khi đầu rắn trùng vị trí mồi thì in ra “Yummy!!” ở terminal.

Ngoài ra chúng ta đổi lại cách xử lý khi thua: thay vì tắt game luôn, hiện thông báo cho người chơi chọn Q để thoát hoặc C để chơi lại. Để làm được việc này, toàn bộ game được đưa vào hàm gameLooping_func(), và có thêm một vòng while bên trong chạy khi người chơi đang ở màn hình thua.

import pygame
import time
import random

pygame.init()

white = (255, 255, 255)
black = (0, 0, 0)
red = (255, 0, 0)
blue = (0, 0, 255)

dis_width = 800
dis_height = 600

dis = pygame.display.set_mode((dis_width, dis_height))
pygame.display.set_caption('Snake game')

clock = pygame.time.Clock()

snake_block = 10
snake_speed = 30

font_style = pygame.font.SysFont(None, 30)

def message_func(msg, color):
    mesg = font_style.render(msg, True, color)
    dis.blit(mesg, [dis_width / 3, dis_height / 3])

def gameLooping_func():
    game_end = False
    game_close = False

    x1 = dis_width / 2
    y1 = dis_height / 2

    x1_change = 0
    y1_change = 0

    # Vị trí tọa độ (x, y) của đồ ăn trong trò chơi
    foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0
    foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0

    while not game_end:

        while game_close == True:
            dis.fill(white)
            message_func("You Lost! Press Q-Quit or C-Play Again", red)
            pygame.display.update()

            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    # Nhấn phím 'q': thoát khỏi trò chơi
                    if event.key == pygame.K_q:
                        game_end = True
                        game_close = False
                    # Nhấn phím 'c': chơi lại
                    if event.key == pygame.K_c:
                        gameLooping_func()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_end = True
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    x1_change = -snake_block
                    y1_change = 0
                elif event.key == pygame.K_RIGHT:
                    x1_change = snake_block
                    y1_change = 0
                elif event.key == pygame.K_UP:
                    y1_change = -snake_block
                    x1_change = 0
                elif event.key == pygame.K_DOWN:
                    y1_change = snake_block
                    x1_change = 0

        if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0:
            game_close = True

        x1 += x1_change
        y1 += y1_change
        dis.fill(white)
        pygame.draw.rect(dis, blue, [foodx, foody, snake_block, snake_block])
        pygame.draw.rect(dis, black, [x1, y1, snake_block, snake_block])
        pygame.display.update()

        # Nếu đầu con rắn trùng vị trí đồ ăn, tức là con rắn đã ăn được đồ ăn
        if x1 == foodx and y1 == foody:
            print("Yummy!!")
        clock.tick(snake_speed)

    pygame.quit()
    quit()

gameLooping_func()

Lưu ý: Tọa độ mồi phải được làm tròn về bội số của snake_block (ở đây là 10). Con rắn mỗi bước đi đúng 10 pixel, nếu mồi nằm ở tọa độ lẻ như 123 thì đầu rắn không bao giờ trùng được vị trí đó, và rắn đi qua mồi mà không ăn được.

3. Tùy chỉnh kích thước con rắn

Bây giờ cho con rắn dài ra mỗi khi ăn mồi. Ý tưởng là lưu vị trí của tất cả các đốt rắn trong một list snake_List. Mỗi vòng lặp, thêm vị trí mới của đầu vào cuối list. Nếu list dài hơn độ dài hiện tại của rắn (Length_of_snake) thì xóa phần tử đầu list, tức là đốt đuôi cũ nhất. Kết quả là con rắn trông như đang bò về phía trước.

Ăn mồi thì tăng Length_of_snake thêm 1, vòng sau sẽ không xóa đuôi nữa nên rắn dài thêm một đốt. Ngoài ra nếu đầu rắn trùng với bất kỳ đốt nào của thân thì thua, và hiện thông báo “You Lost! Press C-Play Again or Q-Quit”.

Độ dài ban đầu của rắn là 1 đốt.

import pygame
import time
import random

pygame.init()

white = (255, 255, 255)
yellow = (255, 255, 102)
black = (0, 0, 0)
red = (213, 50, 80)
green = (0, 255, 0)
blue = (50, 153, 213)

dis_width = 600
dis_height = 400

dis = pygame.display.set_mode((dis_width, dis_height))
pygame.display.set_caption('Snake game')

clock = pygame.time.Clock()

snake_block = 10
snake_speed = 15

font_style = pygame.font.SysFont("bahnschrift", 25)
score_font = pygame.font.SysFont("comicsansms", 35)

def snake_func(snake_block, snake_list):
    for x in snake_list:
        pygame.draw.rect(dis, black, [x[0], x[1], snake_block, snake_block])

def message_func(msg, color):
    mesg = font_style.render(msg, True, color)
    dis.blit(mesg, [dis_width / 6, dis_height / 3])

def gameLooping_func():
    game_end = False
    game_close = False

    x1 = dis_width / 2
    y1 = dis_height / 2

    x1_change = 0
    y1_change = 0

    # Danh sách chứa vị trí các đốt của con rắn
    snake_List = []
    # Độ dài ban đầu của con rắn là 1
    Length_of_snake = 1

    foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0
    foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0

    while not game_end:

        while game_close == True:
            dis.fill(blue)
            message_func("You Lost! Press C-Play Again or Q-Quit", red)
            pygame.display.update()

            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_q:
                        game_end = True
                        game_close = False
                    if event.key == pygame.K_c:
                        gameLooping_func()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_end = True
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    x1_change = -snake_block
                    y1_change = 0
                elif event.key == pygame.K_RIGHT:
                    x1_change = snake_block
                    y1_change = 0
                elif event.key == pygame.K_UP:
                    y1_change = -snake_block
                    x1_change = 0
                elif event.key == pygame.K_DOWN:
                    y1_change = snake_block
                    x1_change = 0

        if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0:
            game_close = True
        x1 += x1_change
        y1 += y1_change
        dis.fill(blue)
        # Vẽ đồ ăn màu xanh lá, kích thước bằng một đốt của con rắn
        pygame.draw.rect(dis, green, [foodx, foody, snake_block, snake_block])

        # Thêm vị trí mới của đầu con rắn vào danh sách
        snake_Head = []
        snake_Head.append(x1)
        snake_Head.append(y1)
        snake_List.append(snake_Head)

        # Nếu số đốt nhiều hơn độ dài hiện tại thì xóa đốt cũ nhất (đuôi)
        if len(snake_List) > Length_of_snake:
            del snake_List[0]

        # Nếu đầu chạm vào bất kỳ đốt nào của thân thì thua
        for x in snake_List[:-1]:
            if x == snake_Head:
                game_close = True

        snake_func(snake_block, snake_List)

        pygame.display.update()

        if x1 == foodx and y1 == foody:
            foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0
            foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0
            Length_of_snake += 1

        clock.tick(snake_speed)

    pygame.quit()
    quit()

gameLooping_func()

4. Hiển thị kết quả qua điểm số

Bước cuối cùng là hiển thị điểm. Chúng ta thêm hàm score_func() để vẽ điểm số ở góc trên bên trái màn hình. Điểm bằng chiều dài con rắn trừ 1, vì đốt đầu tiên có sẵn từ lúc bắt đầu chứ không phải ăn mà có.

import pygame
import time
import random

pygame.init()

white = (255, 255, 255)
yellow = (255, 255, 102)
black = (0, 0, 0)
red = (213, 50, 80)
green = (0, 255, 0)
blue = (50, 153, 213)

dis_width = 600
dis_height = 400

dis = pygame.display.set_mode((dis_width, dis_height))
pygame.display.set_caption('Snake game')

clock = pygame.time.Clock()

snake_block = 10
snake_speed = 15

font_style = pygame.font.SysFont("bahnschrift", 25)
score_font = pygame.font.SysFont("comicsansms", 35)

def score_func(score):
    value = score_font.render("Scoring: " + str(score), True, yellow)
    dis.blit(value, [0, 0])

def snake_func(snake_block, snake_list):
    for x in snake_list:
        pygame.draw.rect(dis, black, [x[0], x[1], snake_block, snake_block])

def message_func(msg, color):
    mesg = font_style.render(msg, True, color)
    dis.blit(mesg, [dis_width / 6, dis_height / 3])

def gameLooping_func():
    game_end = False
    game_close = False

    x1 = dis_width / 2
    y1 = dis_height / 2

    x1_change = 0
    y1_change = 0

    snake_List = []
    Length_of_snake = 1

    foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0
    foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0

    while not game_end:

        while game_close == True:
            dis.fill(blue)
            message_func("Lose", red)
            # Điểm số bằng chiều dài con rắn trừ 1 (không tính phần đầu)
            score_func(Length_of_snake - 1)
            pygame.display.update()

            for event in pygame.event.get():
                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_q:
                        game_end = True
                        game_close = False
                    if event.key == pygame.K_c:
                        gameLooping_func()

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                game_end = True
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    x1_change = -snake_block
                    y1_change = 0
                elif event.key == pygame.K_RIGHT:
                    x1_change = snake_block
                    y1_change = 0
                elif event.key == pygame.K_UP:
                    y1_change = -snake_block
                    x1_change = 0
                elif event.key == pygame.K_DOWN:
                    y1_change = snake_block
                    x1_change = 0

        if x1 >= dis_width or x1 < 0 or y1 >= dis_height or y1 < 0:
            game_close = True
        x1 += x1_change
        y1 += y1_change
        dis.fill(blue)
        pygame.draw.rect(dis, green, [foodx, foody, snake_block, snake_block])
        snake_Head = []
        snake_Head.append(x1)
        snake_Head.append(y1)
        snake_List.append(snake_Head)
        if len(snake_List) > Length_of_snake:
            del snake_List[0]

        for x in snake_List[:-1]:
            if x == snake_Head:
                game_close = True

        snake_func(snake_block, snake_List)
        score_func(Length_of_snake - 1)

        pygame.display.update()

        if x1 == foodx and y1 == foody:
            foodx = round(random.randrange(0, dis_width - snake_block) / 10.0) * 10.0
            foody = round(random.randrange(0, dis_height - snake_block) / 10.0) * 10.0
            Length_of_snake += 1

        clock.tick(snake_speed)

    pygame.quit()
    quit()

gameLooping_func()

Chạy lên là đã có game rắn săn mồi hoàn chỉnh: điều khiển bằng phím mũi tên, ăn mồi dài ra, có điểm số, thua thì chọn chơi lại hoặc thoát.

Màn hình hiển thị game rắn săn mồi với Python
Hình 3. Màn hình hiển thị game rắn săn mồi với Python

Lời kết

Chỉ với vòng lặp, câu điều kiện, list và hàm, chúng ta đã làm ra được một game chơi được thật. Các bạn có thể tự nâng cấp thêm: tăng snake_speed mỗi khi ăn mồi cho khó dần, thêm chướng ngại vật, hay lưu điểm cao nhất ra file bằng kiến thức trong bài đọc ghi file.

Nếu thấy bài viết này hay, hãy chia sẻ tới bạn bè đang học Python nhé. Bài tiếp theo chúng ta sẽ làm game Flappy Bird.

Rate this post

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *