Các bạn có thể xem lại phần 1 ở đây
- Kiến thức lập trình
- Lập trình game với thư viện pygame
- Câu điều kiện
- Mảng 2 chiều trong python
- Vòng lặp trong python
- Hàm trong python
- Một số mã RGB của các màu cơ bản
- Giới thiệu trò chơi
Các bạn học sinh hẳn đã rất quen thuộc với trò chơi caro truyền thống. Cách chơi trò này rất đơn giản, 2 người chơi đại diện cho 2 quân X và O. Mỗi lượt đi, người chơi lần lượt đánh quân của họ lên bàn cờ. Trò chơi chỉ xác định người thắng bại khi mà có 5 quân cờ của họ cùng nằm liên tiếp trên một hàng ngang hoặc một hàng dọc hoặc một đường chéo.
Trước tiên các bạn hãy chơi thử game nhé! https://replit.com/@STEAM4VNOfficial/Caro
- Bắt tay vào lập trình thôi nào!
a. Thuật toán
Chúng ta sẽ chọn một bảng kích cỡ khá lớn để bắt đầu. Ở đây các bạn học sinh hãy chọn bảng 33×64 là dễ nhìn và tiện thao tác nhất.
Bảng ở đây sẽ biểu diễn dưới dạng mảng 2 chiều. Mỗi khi chúng ta click chuột vào một ô để đánh dấu thì sẽ xem lại cả bảng để kiểm tra các điều kiện về đường dọc, đường ngang, đường chéo. Khi đó các bạn sẽ biết được mình có chiến thắng không. Không chỉ vậy, các bạn sẽ dùng vòng lặp để đếm các ô trong mảng 2 chiều, chỉ khi nào mà 5 ô cùng màu được tô liên tiếp theo hàng dọc hoặc hàng ngang hoặc hàng chéo chúng ta sẽ in ra kết quả người chơi chiến thắng.
Nếu có người thắng cuối, trò chơi sẽ in ra kết quả người thắng cuộc và kết thúc trò chơi, còn không, trò chơi vẫn tiếp tục. Nếu trong trường hợp 2 người chơi đã tô hết cả bảng mà vẫn không xác định trò chơi, máy sẽ thông báo kết quả hòa và kết thúc trò chơi.
b. Các bước triển khai dự án
Bước 1: Khai báo thư viện sử dụng để code trò chơi.
Ở đây ngoài các hàm tiêu chuẩn có sẵn của ngôn ngữ lập trình python ra, ta sẽ sử dụng thêm 2 thư viện đó là pygame và sys. Thư viện pygame là một thư viện python giúp chúng ta có thể code các trò chơi dễ dàng hơn. Còn thư viện sys liên quan đến điều khiển các chương trình trên máy tính. Để khai báo chương trình, ta dùng 2 câu lệnh sau.
import pygame
import sys
Bước 2: Khai báo một số yếu tố cơ bản trong trò chơi
Đầu tiên, chúng ta sẽ định nghĩa một số màu hiển thị trong trò chơi theo bảng mã RGB.
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0,0,255)
Trong bảng mã RGB, mỗi một màu sẽ được biểu diễn bằng 3 phần tử tương ứng cường độ của 3 màu cơ bản: đỏ (Red), xanh lá (Green), xanh dương (Blue). Hoà trộn 3 màu này lại, chúng ta sẽ được một màu mới.
Ta sẽ giả định con x là con đi trước và khởi tạo FPS – Frames Per Second (độ mượt của game) là 120
XO = 'x'
FPS = 120
Tiếp đó, chúng ta sẽ khởi tạo các kích cỡ bảng và ô caro. Biến WIDTH và HEIGHT chỉ chiều dài và chiều rộng mỗi ô caro là 28×28. Biến MARGIN chỉ độ dày cạnh mỗi ô caro là 2. Biến rownum và colnum lần lượt là số hàng và số cột của bảng caro. Chúng ta có 33 hàng và 64 cột.
# This sets the WIDTH and HEIGHT of each board location
WIDTH = 28
HEIGHT = 28
# This sets the distance between each cell
MARGIN = 2
rownum = 33
colnum = 64
Các bạn học sinh hãy biểu bảng caro dưới dạng mảng 2 chiều với rownum hàng và colnum cột. Đầu tiên khởi tạo grid là một mảng đơn. Sau đó, chúng ta dùng vòng lặp với số lần lặp tương ứng với số hàng (rownum) để khởi tạo mỗi phần tử của grid là một mảng, ứng với một hàng trong bảng caro. Chúng ta tiếp tục sử dụng vòng lặp thứ hai với số lần lặp tương ứng với số cột (colnum). Chúng ta sử dụng 2 vòng lặp để tạo một mảng hai chiều, giống như bảng caro hoặc mê cung trong bài 6 của khoá CS 101. Giá trị ở mỗi vị trí trong bảng là 0. Vậy là các bạn học sinh đã khởi tạo được bảng caro với kích cỡ rownum * colnum rồi đấy!
# Create a 2 dimensional array. A two dimensional
# array is simply a list of lists.
grid = []
for row in range(rownum):
# Add an empty array that will hold each cell
# in this row
grid.append([])
for column in range(colnum):
grid[row].append(0) # Append a cell
Giờ thì các bạn phải khởi tạo trò chơi. Muốn chạy một trò chơi thì trước tiên các bạn hãy dùng hàm pygame.init(). Ngoài ra để game hiển thị chúng ta phải khởi tạo về cửa sổ game: Biến WINDOW_SIZE chứa 2 kích cỡ chiều dài và chiều rộng của màn hình game ở đây sẽ để kích cỡ là 1920 * 990. Khi đó, biến screen chính là màn hình game trong python. Chúng ta sẽ khởi tạo nó bằng cách khai báo screen = pygame.display.set_mode(WINDOW_SIZE). Vậy chúng ta đã khởi tạo xong cửa sổ game với kích cỡ chúng ta yêu cầu.
# Set the HEIGHT and WIDTH of the screen
WINDOW_SIZE = [1920,990]
screen = pygame.display.set_mode(WINDOW_SIZE)
Về yếu tố hình ảnh, chúng ta có hình ảnh quân X và quân O trên bàn cờ. Ở đây, chúng ta chuẩn bị hình ảnh quân X là file ảnh X_modified-100×100.png và quân O là o_modified-100×100.png. (Các bạn học sinh có thể lấy hình ở link). Sau đó, ta sẽ đưa hai hình ảnh đó vào trò chơi bằng các câu lệnh sau:
x_img = pygame.transform.smoothscale(pygame.image.load("X_modified-100x100.png").convert(),(28,28))
o_img = pygame.transform.smoothscale(pygame.image.load("o_modified-100x100.png").convert(),(28,28))
Ở đây, ta có thể thấy hàm pygame.transform.smoothscale chính là hàm giúp chúng ta chuyển đổi các hình ảnh thành các phần tử ta muốn trong game. Còn hàm pygame.image.load() sẽ đưa hình ảnh vào. Hàm convert() sẽ giúp ta chuyển hình ảnh thành các ô pixel. Và cuối cùng (28,28) chính là kích cỡ một ô vuông trong bảng caro ta đã nói ở trên.
Bước 3: Viết hàm kiểm tra điều kiện thắng của trò chơi
Làm thế nào để các bạn học sinh có thể biết trò chơi kết thúc và kết quả người chiến thắng?
Như các bạn đã biết, trò chơi chỉ có người chiến thắng khi tồn tại 5 quân cùng loại cùng nằm trên một hàng ngang hoặc một hàng dọc hoặc một đường chéo. Vậy trên một bảng, điều kiện đó sẽ được biểu diễn như thế nào?
Chúng ta gọi ô (i, j) là ô có địa chỉ tại hàng thứ i và cột thứ j. Khi đó, ta giả sử ô (i, j) là ô bắt đầu hàng ngang thì các ô tiếp theo của hàng ngang đó sẽ là (i, j + 1), (i, j + 2), (i, j + 3), (i, j + 4). Nếu ô đó là ô bắt đầu một hàng dọc thì các ô tiếp theo sẽ là (i + 1,j), (i + 2, j), (i + 3, j), (i + 4, j). Chúng ta có 2 loại đường chéo. Với loại đường chéo thứ nhất thì các ô tiếp theo sẽ là (i + 1, j – 1), (i + 2, j – 2), (i + 3, j – 3), (i + 4, j – 4). Còn lại với loại đường chéo thứ 2 thì là (i + 1, j + 1), (i + 2, j + 2), (i + 3, j + 3), (i + 4, j + 4). Chúng ta đã được học cách để lấy địa chỉ cho một ô trong mảng hai chiều ở bài 6 trong khoá CS 101.
Như vậy để kiểm tra điều kiện thắng thì ta có thể kiểm tra các ô cùng hàng, cùng cột, cùng đường chéo một cách dễ dàng hơn. Ta có hàm kiểm tra như sau:
def checkwin(board):
indices = [i for i,x in enumerate(board) if 'x' in x]
for index in indices:
xrowindices = [i for i, x in enumerate(board[index]) if x == "x"]
for xs in xrowindices:
if xs<=len(board[0])-5:
if board[index][xs] == board[index][xs+1] == board[index][xs+2] == board[index][xs+3] == board[index][xs+4]:
return 1
if index<=len(board)-5:
if board[index][xs] == board[index+1][xs] == board[index+2][xs] == board[index+3][xs] == board[index+4][xs]:
return 1
if xs<=len(board[0])-5:
if board[index][xs] == board[index+1][xs+1] == board[index+2][xs+2] == board[index+3][xs+3] == board[index+4][xs+4]:
return 1
if board[index][xs] == board[index+1][xs-1] == board[index+2][xs-2] == board[index+3][xs-3] == board[index+4][xs-4]:
return 1
indices1 = [i for i,x in enumerate(board) if 'o' in x]
for index1 in indices1:
orowindices = [i for i, x in enumerate(board[index1]) if x == "o"]
for os in orowindices:
if os<=len(board[0])-5:
if board[index1][os] == board[index1][os+1] == board[index1][os+2] == board[index1][os+3] == board[index1][os+4]:
return 2
if index1<=len(board)-5:
if board[index1][os] == board[index1+1][os] == board[index1+2][os] == board[index1+3][os] == board[index1+4][os]:
return 2
if os<=len(board[0])-5:
if board[index1][os] == board[index1+1][os+1] == board[index1+2][os+2] == board[index1+3][os+3] == board[index1+4][os+4]:
return 2
if board[index1][os] == board[index1+1][os-1] == board[index1+2][os-2] == board[index1+3][os-3] == board[index1+4][os-4]:
return 2
count = 0
for rows in board:
for cells in rows:
if cells == 'x' or cells == 'o':
count+=1
if count == rownum*colnum:
return 3
return 0
Về cách đoạn code hoạt động, ta sẽ tách rõ ra làm 3 điều kiện kiểm tra. Phần thứ nhất là kiểm tra điều kiện thắng của con X, phần thứ 2 kiểm tra điều kiện thắng của con O và phần còn lại sẽ giúp chúng ta kiểm tra hai người chơi hòa hay không. Chúng ta sẽ đặt nếu người chơi X thắng thì trả kết quả chương trình về 1. Nếu người chơi O thắng sẽ trả kết quả về 2. Nếu giải đấu caro hòa thì về giá trị 3. Còn nếu trò chơi tiếp tục chúng ta trả về giá trị 0.
Các bạn có thể thấy ở đoạn code trên có một cách tạo mảng theo điểu kiện rất hay như sau:
indices = [i for i,x in enumerate(board) if 'x' in x]
Cách tạo mảng trên được gọi là list comprehension và cấu trúc của nó như sau:
newlist = [expression for value in array if true]
Cách tạo mảng này sẽ giúp chúng ta tạo một mảng mới từ mảng cũ bằng cách dựa vào các giá trị mảng cũ theo điều kiện nào đó. Các bạn có thể để ý hàm enumerate() sẽ giúp chúng ta tạo ra một danh sách gồm các cặp chỉ số – phần tử trong mảng như kiểu 0 – board [0] rất thuận tiện. Trong đoạn code này, ta sẽ thấy mảng indices chứa các chỉ số hàng của mảng board tồn các con X, tương tự với mảng indices1 sẽ chứa các chỉ số hàng của mảng board tồn tại các con O.
Các bạn học sinh hãy để ý hai đoạn code kiểm tra điều kiện thắng của X và O. Ở vòng lặp thứ hai, chúng ta lại bắt gặp cách tạo mảng tương tự như vậy. Mảng thứ hai sẽ giúp chúng ta lấy chỉ số cột của các ô trong hàng index.
Ngoài việc kiểm tra các hàng, các cột, các đường chéo, trước hết chúng ta cần đảm bảo các ô chúng ta kiểm tra không bị vượt ra ngoài mảng 2 chiều. Nếu không, chương trình sẽ bị lỗi. Như khi kiểm tra một hàng, chúng ta phải kiểm tra đúng xs <= len(board[0]) – 5 để xem các ô chúng ta kiểm tra sau đó có bị tràn ra khỏi bảng caro không. Các bạn nhớ chú ý khi kiểm tra một đường chéo thỏa mãn thì phải xem chỉ số hàng, cột và ô bắt đầu để có thể kiểm tra được cả 5 ô.
Về phần kiểm tra người chơi hòa, các bạn hãy xem các ô tô X hoặc O hết chưa. Nếu có thì trò chơi hòa. Còn nếu không trò chơi tiếp tục
Bước 4: hoàn thiện các phần còn lại để trò chơi chạy
Đầu tiên, chúng ta sẽ khởi tạo hai biến là done và status để thể hiện trò chơi “đã kết thúc” và “đã có người chiến thắng”. Ban đầu, chúng ta để hai biến này là False và None tại vì trò chơi chưa kết thúc và chưa xác định kết quả trò chơi.
# Loop until the user clicks the close button.
done = False
status = None
Sau đó, các bạn sẽ dùng một vòng lặp while để chạy trò chơi. Nếu mà biến done chưa đúng thì nghĩa là trò chơi vẫn tiếp tục và vòng lặp vẫn chạy. Trong pygame, các hoạt động của người chơi sẽ là các event. Chúng ta sẽ dùng một vòng lặp để lấy các event mà chúng ta tác động lên trò chơi.
Trong đó, nếu mà chúng ta kết thúc trò chơi tức là loại event chúng ta thực hiện sẽ là pygame. QUIT thì pygame sẽ kết thúc và chúng ta sẽ để biến done là True để kết thúc vòng lặp. Còn nếu chúng ta click chuột vào một ô trên màn hình thì thao tác này là pygame .MOUSEBUTTONDOWN tức là người chơi chọn đánh dấu một quân lên bảng và chúng ta sẽ dùng hàm pygame.mouse.get_pos() để lấy vị trí ô chúng ta click vào. Nếu ô đó đã được đánh dấu là X hoặc O thì chúng ta bỏ qua. Còn không, chúng ta sẽ đánh dấu nó là biến xo và đổi biến xo ngược lại để đến lượt người chơi thứ hai. Sau đó, chúng ta sẽ gắn biến status với giá trị của hàm checkwin(grid).
while not done:
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done = True # Flag that we are done so we exit this loop
# Set the screen background
if event.type == pygame.MOUSEBUTTONDOWN:
pos = pygame.mouse.get_pos()
col = pos[0] // (WIDTH + MARGIN)
row= pos[1] // (HEIGHT + MARGIN)
if grid[row][col] == 0:
if XO == 'x':
grid[row][col] = XO
XO = 'o'
else:
grid[row][col] = XO
XO = 'x'
status = checkwin(grid)
Ở đoạn code trên, các bạn có thể thấy chỉ số hàng và cột mà chúng ta lấy đã bị đảo lại. Nguyên nhân là vì hàm python.mouse.get_pos() sẽ trả về cột trước hàng sau..
Trong vòng lặp while trên, sau khi đánh dấu ô và kiểm tra điều kiện chiến thắng, giờ chúng ta sẽ vẽ hình bảng caro. Đầu tiên, chúng ta sẽ để cả cái bảng màu đen. Sau đó, vẽ các ô caro là các hình vuông màu trắng có kích cỡ WIDTH * HEIGHT có khoảng cách là MARGIN. Như vậy, chúng ta đã xong bảng ô vuông caro. Giờ các bạn hãy dùng vòng lặp để xem ô đó có điền X hay O thì chúng ta chèn hình ảnh X hoặc O vào ô nhé! Ta có đoạn code sau:
for row in range(rownum):
for column in range(colnum):
color = WHITE
pygame.draw.rect(screen,
color,
[(MARGIN + WIDTH) * column + MARGIN,
(MARGIN + HEIGHT) * row + MARGIN,
WIDTH,
HEIGHT])
if grid[row][column] == 'x':
screen.blit(x_img,((WIDTH + MARGIN)*column+2,(HEIGHT + MARGIN)*row+2))
if grid[row][column] == 'o':
screen.blit(o_img,((WIDTH + MARGIN)*column+2,(HEIGHT + MARGIN)*row+2))
Ở đây, chúng ta thấy tọa độ một ô vuông màu trắng ứng với ô row, column sẽ là (MARGIN + WIDTH) * column + MARGIN, (MARGIN + HEIGHT) * row. Các bạn hãy vẽ hình vuông kích cỡ WIDTH * HEIGHT. Để ý kỹ, các bạn sẽ thấy chúng ta vẽ theo thứ tự cột trước hàng sau tại vì pygame sẽ vẽ hình chữ nhật theo kích cỡ là chiều ngang trước chiều dọc sau. Hàm screen.blit giúp chúng ta có thể chèn hình ảnh ta muốn vào ô vuông với tọa độ như trên.
Giờ chúng ta đã vẽ xong bảng và nếu có người chơi thắng hoặc hòa, chúng ta phải in kết quả ra và kết thúc trò chơi.
if status == 3:
font = pygame.font.Font('freesansbold.ttf', 100)
text = font.render('Draw', True, GREEN, BLUE)
textRect = text.get_rect()
textRect.center = (WINDOW_SIZE[0]/2,WINDOW_SIZE[1]/2)
screen.blit(text,textRect)
done = True
if status == 1:
font = pygame.font.Font('freesansbold.ttf', 100)
text = font.render('X wins', True, GREEN, BLUE)
textRect = text.get_rect()
textRect.center = (WINDOW_SIZE[0]/2,WINDOW_SIZE[1]/2)
screen.blit(text,textRect)
done = True
if status == 2:
font = pygame.font.Font('freesansbold.ttf', 100)
text = font.render('O wins', True, GREEN, BLUE)
textRect = text.get_rect()
textRect.center = (WINDOW_SIZE[0]/2,WINDOW_SIZE[1]/2)
screen.blit(text,textRect)
done = True
Ở đây, các bạn sẽ thấy hàm pygame.font.Font có thể chọn lần lượt font chữ và kích cỡ chữ hiển thị. Ở đây ta sẽ chọn là font freesansbold và file của font đó là freesansbold.ttf, kích cỡ 100. Nếu status là 3 thì chúng ta có kết quả hòa. Còn nếu status là 2 chúng ta cho người chơi O thắng. Còn là 1 thì người chơi X thắng.
Hàm font.render sẽ giúp chúng ta biểu thị dòng chữ mà mình cần. Còn biến True trong hàm font.render giúp chữ hiển thị nét hơn trên máy tính. GREEN chính là màu của chữ cái ta chọn. BLUE chính là màu phông nền đằng sau. Chúng ta đã tạo các màu này bằng giá trị RGB ở phần đầu chương trình. Hàm get_rect sẽ giúp lấy hình chữ nhật chứa các từ mà các bạn muốn. Sau đó, các bạn hãy nhớ chỉnh tọa độ của hình chữ nhật ở trung tâm là (WINDOW_SIZE[0]/2,WINDOW_SIZE[1]/2).
Ngoài ra, hàm screen.blit sẽ giúp trò chơi hiển thị chữ cái ta cần ở vị trí hình chữ nhật đó. Và nếu trò chơi kết thúc chúng ta phải đặt biến done là True.
Sau đó, các bạn sẽ làm trò chơi cập nhật và hiện lên bằng câu lệnh sau:
clock.tick(FPS)
# Go ahead and update the screen with what we've drawn.
pygame.display.update()
Hàm clock.tick(FPS) sẽ giúp trò chơi chúng ta mượt hơn và hàm python.display.update() sẽ giúp ta hiện những gì vừa thực hiện lên. FPS là số khung hình trong một giây chúng ta đã tạo ở đầu chương trình.
Kết thúc trò chơi, chúng ta sẽ dừng trò chơi trong 10s để nó giữ kết quả người chơi chiến thắng và thoát khỏi trò chơi.
pygame.time.delay(10000)
quit()
pygame.quit()
sys.exit()
Hàm pygame.time.delay(10000) sẽ giữ trò chơi tầm 10 giây để chúng ta có thể nhìn thấy kết quả trò chơi và sau đó thoát game ra bằng hàm pygame.quit().
4. Tadaa !
Sau trò chơi thú vị này, các bạn học sinh lại được cập nhật thêm những kiến thức thú vị về lập trình python như toán, hàm, câu điều kiện, các vòng lặp…
Đây là một chương trình cần kiến thức khá là khó và dài, nhưng cũng không kém phần thú vị. Các bạn học sinh hãy thử sáng tạo, cải tiến code bằng cách thay hình ảnh của X và O bằng bất cứ hình ảnh nào các bạn thích. Hoặc chúng ta có thể tạo một bảng caro to hơn để chơi đã hơn.
Sau khi hoàn thành dự án cá nhân, các bạn đừng quên chia sẻ chương trình của mình lên STEAMese Profile để thầy cô và các bạn cùng trải nghiệm nhé!
— — —
STEAM for Vietnam Foundation là tổ chức phi lợi nhuận 501(c)(3) được thành lập tại Hoa Kỳ với sứ mệnh thúc đẩy các hoạt động liên quan tới giáo dục STEAM (Science — Khoa học, Technology — Công nghệ, Engineering — Kỹ thuật, Arts — Nghệ thuật, Mathematics — Toán học) tại Việt nam. STEAM for Vietnam được thành lập và vận hành bởi đội ngũ tình nguyện viên là du học sinh và chuyên gia người Việt trên khắp thế giới.
— — —
📧Email: hello@steamforvietnam.org
🌐Website: www.steamforvietnam.org
🌐Fanpage: STEAM for Vietnam
📺YouTube: http://bit.ly/S4V_YT
🌐Zalo: Zalo Official