Pygame - Các bước code Puzzle Memory

04/01/2022   PyGame

Pygame - Các bước code Puzzle Memory

Nội dung bài viết

Bước 1. Bắt đầu xây dựng Game 
Bước 2. Thiết lập các tham số mặc định cho Game
Bước 3. Viết hàm main cho Game, khởi tạo Game (biến vẽ, giờ..)
Bước 4. Tạo dữ liệu ngẫu nhiên bảng các icon cho các hộp, đặt tiết lộ tất cả các hộp bằng False
Bước 5. Tạo hàm trò chơi bắt đầu và Lấy ngẫu nhiên tọa độ các hộp để tiết lộ
Bước 6. Trò chơi bắt đầu, tạo hiệu ứng tiết lộ các hộp lấy ở bước trước, giới thiệu các biến môi trường
# vẽ khung trò chơi với board và coveredBoxes
# vẽ icon với shape,corlor tại vị trí boxx, boxy
# vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxesToReveal
# vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover
# lấy từ  boxes vẽ các khung trắng bao phủ với mức độ coverage
Bước 7. Vòng lặp trò chơi, bắt sự kiện, khởi tạo chưa click chuột và vẽ lại màn hình 
Bước 8. Vẽ bảng trò chơi trong vòng lặp trò chơi từ mainBoard,revealedBoxes
Bước 9. Bắt các sự kiện trò chơi để thay đổi các biến môi trường của trò chơi
Bước 10. Xử lý khi di chuyển chuột qua hộp
Bước 11. Xử lý khi Click chuột vào một hộp chưa tiết lộ
Bước 12. Xử lý trường hợp 2 hộp chọn khác nhau
Bước 13: Chiến thắng trò chơi, và cho chơi lại

 

Bước 1. Bắt đầu xây dựng Game 

#sử dụng thư viện
import random, pygame, sys
from pygame.locals import *

def main():
    pass

#chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()

Bước 2. Thiết lập các tham số mặc định cho Game

#sử dụng thư viện
import random, pygame, sys
from pygame.locals import *
#tốc độ game chương trình
FPS = 30 # frames per second, the general speed of the program
#độ rộng màn hình game
WINDOWWIDTH = 640 # size of window's width in pixels
#độ cao màn hình game
WINDOWHEIGHT = 480 # size of windows' height in pixels
#tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1 # speed boxes' sliding reveals and covers
#kích thước hộp icon
BOXSIZE = 40 # size of box height & width in pixels
#khoảng cách giữa các hộp
GAPSIZE = 10 # size of gap between boxes in pixels
#số hộp một hàng
BOARDWIDTH = 3 # number of columns of icons
#số hộp một cột
BOARDHEIGHT = 2 # number of rows of icons
#kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
#khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
#khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
#sử dụng tên để đặt cho các màu
#            R    G    B
GRAY     = (100, 100, 100)
NAVYBLUE = ( 60,  60, 100)
WHITE    = (255, 255, 255)
RED      = (255,   0,   0)
GREEN    = (  0, 255,   0)
BLUE     = (  0,   0, 255)
YELLOW   = (255, 255,   0)
ORANGE   = (255, 128,   0)
PURPLE   = (255,   0, 255)
CYAN     = (  0, 255, 255)
#đặt màu nền
BGCOLOR = NAVYBLUE
#đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
#màu của hộp icon
BOXCOLOR = WHITE
#màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
#các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
#lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
#kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."

#chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()

Bước 3. Viết hàm main cho Game, khởi tạo Game (biến vẽ, giờ..)

#sử dụng thư viện
import random, pygame, sys
from pygame.locals import *
#tốc độ game chương trình
FPS = 30 # frames per second, the general speed of the program
#độ rộng màn hình game
WINDOWWIDTH = 640 # size of window's width in pixels
#độ cao màn hình game
WINDOWHEIGHT = 480 # size of windows' height in pixels
#tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1 # speed boxes' sliding reveals and covers
#kích thước hộp icon
BOXSIZE = 40 # size of box height & width in pixels
#khoảng cách giữa các hộp
GAPSIZE = 10 # size of gap between boxes in pixels
#số hộp một hàng
BOARDWIDTH = 3 # number of columns of icons
#số hộp một cột
BOARDHEIGHT = 2 # number of rows of icons
#kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
#khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
#khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
#sử dụng tên để đặt cho các màu
#            R    G    B
GRAY     = (100, 100, 100)
NAVYBLUE = ( 60,  60, 100)
WHITE    = (255, 255, 255)
RED      = (255,   0,   0)
GREEN    = (  0, 255,   0)
BLUE     = (  0,   0, 255)
YELLOW   = (255, 255,   0)
ORANGE   = (255, 128,   0)
PURPLE   = (255,   0, 255)
CYAN     = (  0, 255, 255)
#đặt màu nền
BGCOLOR = NAVYBLUE
#đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
#màu của hộp icon
BOXCOLOR = WHITE
#màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
#các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
#lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
#kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."

#hàm chính của chương trình
def main():
    #FPSCLOCK dùng đề chỉnh tốc độ game
    #DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    #khởi tạo chế độ vẽ
    pygame.init()
    #khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    #thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    #lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0 # used to store x coordinate of mouse event
    mousey = 0 # used to store y coordinate of mouse event
    #Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')

#chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()

Bước 4. Tạo dữ liệu ngẫu nhiên bảng các icon cho các hộp, đặt tiết lộ tất cả các hộp bằng False

#sử dụng thư viện
import random, pygame, sys
from pygame.locals import *
#tốc độ game chương trình
FPS = 30 # frames per second, the general speed of the program
#độ rộng màn hình game
WINDOWWIDTH = 640 # size of window's width in pixels
#độ cao màn hình game
WINDOWHEIGHT = 480 # size of windows' height in pixels
#tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1 # speed boxes' sliding reveals and covers
#kích thước hộp icon
BOXSIZE = 40 # size of box height & width in pixels
#khoảng cách giữa các hộp
GAPSIZE = 10 # size of gap between boxes in pixels
#số hộp một hàng
BOARDWIDTH = 3 # number of columns of icons
#số hộp một cột
BOARDHEIGHT = 2 # number of rows of icons
#kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
#khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
#khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
#sử dụng tên để đặt cho các màu
#            R    G    B
GRAY     = (100, 100, 100)
NAVYBLUE = ( 60,  60, 100)
WHITE    = (255, 255, 255)
RED      = (255,   0,   0)
GREEN    = (  0, 255,   0)
BLUE     = (  0,   0, 255)
YELLOW   = (255, 255,   0)
ORANGE   = (255, 128,   0)
PURPLE   = (255,   0, 255)
CYAN     = (  0, 255, 255)
#đặt màu nền
BGCOLOR = NAVYBLUE
#đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
#màu của hộp icon
BOXCOLOR = WHITE
#màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
#các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
#lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
#kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."

#hàm chính của chương trình
def main():
    #FPSCLOCK dùng đề chỉnh tốc độ game
    #DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    #khởi tạo chế độ vẽ
    pygame.init()
    #khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    #thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    #lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0 # used to store x coordinate of mouse event
    mousey = 0 # used to store y coordinate of mouse event
    #Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')
    #mainBoard là danh sách 2 chiều lưu các icon,màu sắc trong game được lấy ngẫu nhiên qua hàm getRandomizedBoard()
    mainBoard = getRandomizedBoard()

    #revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    #hàm generateRevealedBoxesData(False) gán gí trị cho revealedBoxes tất cả là False
    revealedBoxes = generateRevealedBoxesData(False)

    #tọa độ chuột lúc clicked
    firstSelection = None # stores the (x, y) of the first box clicked.

    #tô màu cho nền
    DISPLAYSURF.fill(BGCOLOR)

#hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val
def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes

#hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều
def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))
            
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.      
    random.shuffle(icons) # randomize the order of the icons list

    # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình)
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2) # calculate how many icons are needed

    #lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon
    icons = icons[:numIconsUsed] * 2 # make two of each
    
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)

    # tạo danh sách 2 chiều chứa các icon, màu sắc
    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            # thêm icon[0] vào tọa độ (x,y)
            column.append(icons[0])

            # xóa icons[0] đi
            del icons[0] # remove the icons as we assign them
        #thêm cột cho board
        board.append(column)

    #trả lại danh sách 2 chiều chứa icon, colors
    return board

 

#chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()

Bước 5. Tạo hàm trò chơi bắt đầu và Lấy ngẫu nhiên tọa độ các hộp để tiết lộ

#sử dụng thư viện
import random, pygame, sys
from pygame.locals import *
#tốc độ game chương trình
FPS = 30 # frames per second, the general speed of the program
#độ rộng màn hình game
WINDOWWIDTH = 640 # size of window's width in pixels
#độ cao màn hình game
WINDOWHEIGHT = 480 # size of windows' height in pixels
#tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1 # speed boxes' sliding reveals and covers
#kích thước hộp icon
BOXSIZE = 40 # size of box height & width in pixels
#khoảng cách giữa các hộp
GAPSIZE = 10 # size of gap between boxes in pixels
#số hộp một hàng
BOARDWIDTH = 3 # number of columns of icons
#số hộp một cột
BOARDHEIGHT = 2 # number of rows of icons
#kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
#khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
#khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
#sử dụng tên để đặt cho các màu
#            R    G    B
GRAY     = (100, 100, 100)
NAVYBLUE = ( 60,  60, 100)
WHITE    = (255, 255, 255)
RED      = (255,   0,   0)
GREEN    = (  0, 255,   0)
BLUE     = (  0,   0, 255)
YELLOW   = (255, 255,   0)
ORANGE   = (255, 128,   0)
PURPLE   = (255,   0, 255)
CYAN     = (  0, 255, 255)
#đặt màu nền
BGCOLOR = NAVYBLUE
#đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
#màu của hộp icon
BOXCOLOR = WHITE
#màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
#các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
#lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
#kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."

#hàm chính của chương trình
def main():
    #FPSCLOCK dùng đề chỉnh tốc độ game
    #DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    #khởi tạo chế độ vẽ
    pygame.init()
    #khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    #thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    #lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0 # used to store x coordinate of mouse event
    mousey = 0 # used to store y coordinate of mouse event
    #Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')
    #mainBoard là danh sách 2 chiều lưu các icon,màu sắc trong game được lấy ngẫu nhiên qua hàm getRandomizedBoard()
    mainBoard = getRandomizedBoard()

    #revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    #hàm generateRevealedBoxesData(False) gán gí trị cho revealedBoxes tất cả là False
    revealedBoxes = generateRevealedBoxesData(False)

    #tọa độ chuột lúc clicked
    firstSelection = None # stores the (x, y) of the first box clicked.

    #tô màu cho nền
    DISPLAYSURF.fill(BGCOLOR)

    #gọi hàm game start
    startGameAnimation(mainBoard)

#hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val
def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes

#hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều
def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))
            
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.      
    random.shuffle(icons) # randomize the order of the icons list

    # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình)
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2) # calculate how many icons are needed

    #lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon
    icons = icons[:numIconsUsed] * 2 # make two of each
    
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)

    # tạo danh sách 2 chiều chứa các icon, màu sắc
    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            # thêm icon[0] vào tọa độ (x,y)
            column.append(icons[0])

            # xóa icons[0] đi
            del icons[0] # remove the icons as we assign them
        #thêm cột cho board
        board.append(column)

    #trả lại danh sách 2 chiều chứa icon, colors
    return board

#hàm bắt đầu chơi game 
def startGameAnimation(board):
    # Randomly reveal the boxes 8 at a time.
    #gán tất cả các hộp là chưa tiết lộ
    coveredBoxes = generateRevealedBoxesData(False)
    #khởi tạo rỗ cho các hộp
    boxes = []
    #lấy tọa độ hộp
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            boxes.append( (x, y) )

    #xáo trộn thứ tự các hộp
    random.shuffle(boxes)

    #tách mảng một chiều boxes thành mảng 2 chiều, có khoảng cách nhau 8 bước
    boxGroups = splitIntoGroupsOf(8, boxes)

#tách mảng một chiều theLisst thành mảng 2 chiều kích thước groupSize
def splitIntoGroupsOf(groupSize, theList):
    # splits a list into a list of lists, where the inner lists have at
    # most groupSize number of items.
    result = []
    #for i in range( start, stop, step)
    for i in range(0, len(theList), groupSize):
        #lấy từ vị trí i đến vị trí i+groupSize
        result.append(theList[i:i + groupSize])
    return result

#chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()

Bước 6. Trò chơi bắt đầu, tạo hiệu ứng tiết lộ các hộp lấy ở bước trước, giới thiệu các biến môi trường

#sử dụng thư viện
import random, pygame, sys
from pygame.locals import *
#tốc độ game chương trình
FPS = 30 # frames per second, the general speed of the program
#độ rộng màn hình game
WINDOWWIDTH = 640 # size of window's width in pixels
#độ cao màn hình game
WINDOWHEIGHT = 480 # size of windows' height in pixels
#tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1 # speed boxes' sliding reveals and covers
#kích thước hộp icon
BOXSIZE = 40 # size of box height & width in pixels
#khoảng cách giữa các hộp
GAPSIZE = 10 # size of gap between boxes in pixels
#số hộp một hàng
BOARDWIDTH = 3 # number of columns of icons
#số hộp một cột
BOARDHEIGHT = 2 # number of rows of icons
#kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
#khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
#khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
#sử dụng tên để đặt cho các màu
#            R    G    B
GRAY     = (100, 100, 100)
NAVYBLUE = ( 60,  60, 100)
WHITE    = (255, 255, 255)
RED      = (255,   0,   0)
GREEN    = (  0, 255,   0)
BLUE     = (  0,   0, 255)
YELLOW   = (255, 255,   0)
ORANGE   = (255, 128,   0)
PURPLE   = (255,   0, 255)
CYAN     = (  0, 255, 255)
#đặt màu nền
BGCOLOR = NAVYBLUE
#đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
#màu của hộp icon
BOXCOLOR = WHITE
#màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
#các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
#lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
#kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."

#hàm chính của chương trình
def main():
    #FPSCLOCK dùng đề chỉnh tốc độ game
    #DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    #khởi tạo chế độ vẽ
    pygame.init()
    #khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    #thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    #lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0 # used to store x coordinate of mouse event
    mousey = 0 # used to store y coordinate of mouse event
    #Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')
    #mainBoard là danh sách 2 chiều lưu các icon,màu sắc trong game được lấy ngẫu nhiên qua hàm getRandomizedBoard()
    mainBoard = getRandomizedBoard()

    #revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    #hàm generateRevealedBoxesData(False) gán gí trị cho revealedBoxes tất cả là False
    revealedBoxes = generateRevealedBoxesData(False)

    #tọa độ chuột lúc clicked
    firstSelection = None # stores the (x, y) of the first box clicked.

    #tô màu cho nền
    DISPLAYSURF.fill(BGCOLOR)

    #gọi hàm game start
    startGameAnimation(mainBoard)

    """
    Trạng thái trò chơi cho chương trình Câu đố bộ nhớ được lưu trữ trong các biến sau:
    · mainBoard: là danh sách 2 chiều lưu các icon
    · revealedBoxes: revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    · firstSelection: tọa độ chuột lúc clicked
    · mouseClicked: kiểm tra đã clicked chuột chưa
    · mousex: tọa độ x của chuột
    · mousey: tọa độ y của chuột
    """

#hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val
def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes

#hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều
def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))
            
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.      
    random.shuffle(icons) # randomize the order of the icons list

    # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình)
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2) # calculate how many icons are needed

    #lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon
    icons = icons[:numIconsUsed] * 2 # make two of each
    
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)

    # tạo danh sách 2 chiều chứa các icon, màu sắc
    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            # thêm icon[0] vào tọa độ (x,y)
            column.append(icons[0])

            # xóa icons[0] đi
            del icons[0] # remove the icons as we assign them
        #thêm cột cho board
        board.append(column)

    #trả lại danh sách 2 chiều chứa icon, colors
    return board

#hàm bắt đầu chơi game 
def startGameAnimation(board):
    # Randomly reveal the boxes 8 at a time.
    #gán tất cả các hộp là chưa tiết lộ
    coveredBoxes = generateRevealedBoxesData(False)
    #khởi tạo rỗ cho các hộp
    boxes = []
    #lấy tọa độ hộp
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            boxes.append( (x, y) )

    #xáo trộn thứ tự các hộp
    random.shuffle(boxes)

    #tách mảng một chiều boxes thành mảng 2 chiều, có khoảng cách nhau 8 bước
    boxGroups = splitIntoGroupsOf(8, boxes)

    #vẽ khung trò chơi với board và coveredBoxes
    drawBoard(board, coveredBoxes)

    #từ boxGroups tạo hiệu ứng tiết lộ các box
    for boxGroup in boxGroups:
        #vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxGroup
        revealBoxesAnimation(board, boxGroup)
        #vẽ hiệu ứng bao phủ hộp với 8 ô trong boxGroup
        coverBoxesAnimation(board, boxGroup)

#tách mảng một chiều theLisst thành mảng 2 chiều kích thước groupSize
def splitIntoGroupsOf(groupSize, theList):
    # splits a list into a list of lists, where the inner lists have at
    # most groupSize number of items.
    result = []
    #for i in range( start, stop, step)
    for i in range(0, len(theList), groupSize):
        #lấy từ vị trí i đến vị trí i+groupSize
        result.append(theList[i:i + groupSize])
    return result

#hàm vẽ khung trò chơi bao gồm các icon, và trạng thái đã tiết lộ hay chưa
def drawBoard(board, revealed):
    # Draws all of the boxes in their covered or revealed state.
    #vẽ từng tọa độ (boxx,boxy)
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            #chuyển tọa độ hộp thày tọa độ pixcel
            left, top = leftTopCoordsOfBox(boxx, boxy)
            #nếu chưa tiết lộ thì vẽ hình vuông trắng che đi
            if not revealed[boxx][boxy]:
                # Draw a covered box.
                pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE))
            #nếu đã tiết lộ thì vẽ icon
            else:
                # Draw the (revealed) icon.
                #lấy shape, color từ board
                shape, color = getShapeAndColor(board, boxx, boxy)
                #vẽ icon với shape, color tại tọa độ, boxx,boxy
                drawIcon(shape, color, boxx, boxy)

#chuyển hộp icon thành tọa độ pixcel
def leftTopCoordsOfBox(boxx, boxy):
    # Convert board coordinates to pixel coordinates
    left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN
    top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN
    return (left, top)

#vẽ icon với shape,corlor tại vị trí boxx, boxy
def drawIcon(shape, color, boxx, boxy):
    #lấy thông số để vẽ hình
    quarter = int(BOXSIZE * 0.25) # syntactic sugar
    half =    int(BOXSIZE * 0.5)  # syntactic sugar
    left, top = leftTopCoordsOfBox(boxx, boxy) # get pixel coords from board coords
    # Draw the shapes
    #vẽ hình bánh donut vẽ 2 vòng tròn 
    if shape == DONUT:
        pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5)
        pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5)
    #vẽ hình vuông
    elif shape == SQUARE:
        pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half))
    #vẽ hình kim cương (hình thoi)        
    elif shape == DIAMOND:
        pygame.draw.polygon(DISPLAYSURF, color, ((left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half)))
    #vẽ hình đường gạch chéo
    elif shape == LINES:
        for i in range(0, BOXSIZE, 4):
            pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top))
            pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i))
    #vẽ hình elipse (oval)        
    elif shape == OVAL:
        pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half))

def getShapeAndColor(board, boxx, boxy):
    # shape value for x, y spot is stored in board[x][y][0]
    # color value for x, y spot is stored in board[x][y][1]
    return board[boxx][boxy][0], board[boxx][boxy][1]


#lấy từ  boxes vẽ các khung trắng bao phủ với mức độ coverage
def drawBoxCovers(board, boxes, coverage):
    # Draws boxes being covered/revealed. "boxes" is a list
    # of two-item lists, which have the x & y spot of the box.
    #lấy từng box ra để vẻ khung trắng che box icon
    for box in boxes: #box là tọa độ 1 hộp (x,y) =>box[0],box[1]
        #lấy tọa độ pixcel phía trên, bên trái của hộp từ tọa độ hộp (x,y)
        left, top = leftTopCoordsOfBox(box[0], box[1])
        
        #vẽ hình chữ nhật màu nền (hình vuông), có tọa độ (trái, trên, BOXSIZE, BOXSIZE)
        #vẽ nền cho box cùng nền board
        pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE))

        #lấy icon, màu sắc của hộp tại box(x,y)
        shape, color = getShapeAndColor(board, box[0], box[1])
        #vẽ hình icon với shape, color, box[0],box[1] vừa lấy được
        drawIcon(shape, color, box[0], box[1])

        #vẽ đa gác hình chữ nhật màu trắng để che icon với mức độ coverage
        if coverage > 0: # only draw the cover if there is an coverage
            pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE))

    #cập nhật lại màn hình
    pygame.display.update()
    
    #điều khiển tốc độ, làm chậm lại theo FPS, FPS khung hình trên s, FPS càng lơn thì chạy càng nhanh
    FPSCLOCK.tick(FPS)


#vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxesToReveal
def revealBoxesAnimation(board, boxesToReveal):
    # Do the "box reveal" animation.
    
    #dùng for vẽ hộp màu trắng để che box chứa icon lại
    #coverage mức độ bao phủ
    #for coverage từ BOXSIZE->-REVEALSPEED - 1, step=-REVEALSPEED
    #tức là tiết lộ từ từ giảm từ từ khung trắng
    for coverage in range(BOXSIZE, -REVEALSPEED - 1, -REVEALSPEED):
        #lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToReveal, coverage)

#vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover
def coverBoxesAnimation(board, boxesToCover):
    # Do the "box cover" animation.
    # for coverage từ 0->BOXSIZE + REVEALSPEED,REVEALSPEED
    # tức là bao phủ từ từ, tăng từ từ khung trắng
    for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED):
        #lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToCover, coverage)

#chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()

# vẽ khung trò chơi với board và coveredBoxes
drawBoard(board, coveredBoxes)

def drawBoard(board, revealed):
    # Draws all of the boxes in their covered or revealed state.
    #vẽ từng tọa độ (boxx,boxy)
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            #chuyển tọa độ hộp thày tọa độ pixcel
            left, top = leftTopCoordsOfBox(boxx, boxy)
            #nếu chưa tiết lộ thì vẽ hình vuông trắng che đi
            if not revealed[boxx][boxy]:
                # Draw a covered box.
                pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE))
            #nếu đã tiết lộ thì vẽ icon
            else:
                # Draw the (revealed) icon.
                #lấy shape, color từ board
                shape, color = getShapeAndColor(board, boxx, boxy)
                #vẽ icon với shape, color tại tọa độ, boxx,boxy
                drawIcon(shape, color, boxx, boxy)

# vẽ icon với shape,corlor tại vị trí boxx, boxy
def drawIcon(shape, color, boxx, boxy):

def drawIcon(shape, color, boxx, boxy):
    # lấy thông số để vẽ hình
    quarter = int(BOXSIZE * 0.25)  # syntactic sugar
    half = int(BOXSIZE * 0.5)  # syntactic sugar
    left, top = leftTopCoordsOfBox(boxx, boxy)  # get pixel coords from board coords
    # Draw the shapes
    # vẽ hình bánh donut vẽ 2 vòng tròn
    if shape == DONUT:
        pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5)
        pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5)
    # vẽ hình vuông
    elif shape == SQUARE:
        pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half))
    # vẽ hình kim cương (hình thoi)
    elif shape == DIAMOND:
        pygame.draw.polygon(DISPLAYSURF, color, (
        (left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half)))
    # vẽ hình đường gạch chéo
    elif shape == LINES:
        for i in range(0, BOXSIZE, 4):
            pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top))
            pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i))
    # vẽ hình elipse (oval)
    elif shape == OVAL:
        pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half))

# vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxesToReveal
def revealBoxesAnimation(board, boxesToReveal):

def revealBoxesAnimation(board, boxesToReveal):
    # Do the "box reveal" animation.
    
    #dùng for vẽ hộp màu trắng để che box chứa icon lại
    #coverage mức độ bao phủ
    #for coverage từ BOXSIZE->-REVEALSPEED - 1, step=-REVEALSPEED
    #tức là tiết lộ từ từ giảm từ từ khung trắng
    for coverage in range(BOXSIZE, -REVEALSPEED - 1, -REVEALSPEED):
        #lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToReveal, coverage)

#vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover
def coverBoxesAnimation(board, boxesToCover):

def coverBoxesAnimation(board, boxesToCover):
    # Do the "box cover" animation.
    # for coverage từ 0->BOXSIZE + REVEALSPEED,REVEALSPEED
    # tức là bao phủ từ từ, tăng từ từ khung trắng
    for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED):
        #lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToCover, coverage)

# lấy từ  boxes vẽ các khung trắng bao phủ với mức độ coverage
def drawBoxCovers(board, boxes, coverage):

def drawBoxCovers(board, boxes, coverage):
    # Draws boxes being covered/revealed. "boxes" is a list
    # of two-item lists, which have the x & y spot of the box.
    # lấy từng box ra để vẻ khung trắng che box icon
    for box in boxes:  # box là tọa độ 1 hộp (x,y) =>box[0],box[1]
        # lấy tọa độ pixcel phía trên, bên trái của hộp từ tọa độ hộp (x,y)
        left, top = leftTopCoordsOfBox(box[0], box[1])

        # vẽ hình chữ nhật màu nền (hình vuông), có tọa độ (trái, trên, BOXSIZE, BOXSIZE)
        # vẽ nền cho box cùng nền board
        pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE))

        # lấy icon, màu sắc của hộp tại box(x,y)
        shape, color = getShapeAndColor(board, box[0], box[1])
        # vẽ hình icon với shape, color, box[0],box[1] vừa lấy được
        drawIcon(shape, color, box[0], box[1])

        # vẽ đa gác hình chữ nhật màu trắng để che icon với mức độ coverage
        if coverage > 0:  # only draw the cover if there is an coverage
            pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE))

    # cập nhật lại màn hình
    pygame.display.update()

    # điều khiển tốc độ, làm chậm lại theo FPS, FPS khung hình trên s, FPS càng lơn thì chạy càng nhanh
    FPSCLOCK.tick(FPS)

Bước 7. Vòng lặp trò chơi, bắt sự kiện, khởi tạo chưa click chuột và vẽ lại màn hình 

# sử dụng thư viện
import random, pygame, sys
from pygame.locals import *

# tốc độ game chương trình
FPS = 30  # frames per second, the general speed of the program
# độ rộng màn hình game
WINDOWWIDTH = 640  # size of window's width in pixels
# độ cao màn hình game
WINDOWHEIGHT = 480  # size of windows' height in pixels
# tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1  # speed boxes' sliding reveals and covers
# kích thước hộp icon
BOXSIZE = 40  # size of box height & width in pixels
# khoảng cách giữa các hộp
GAPSIZE = 10  # size of gap between boxes in pixels
# số hộp một hàng
BOARDWIDTH = 3  # number of columns of icons
# số hộp một cột
BOARDHEIGHT = 2  # number of rows of icons
# kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
# khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
# khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
# sử dụng tên để đặt cho các màu
#            R    G    B
GRAY = (100, 100, 100)
NAVYBLUE = (60, 60, 100)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 128, 0)
PURPLE = (255, 0, 255)
CYAN = (0, 255, 255)
# đặt màu nền
BGCOLOR = NAVYBLUE
# đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
# màu của hộp icon
BOXCOLOR = WHITE
# màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
# các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
# lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
# kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(
    ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."


# hàm chính của chương trình
def main():
    # FPSCLOCK dùng đề chỉnh tốc độ game
    # DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    # khởi tạo chế độ vẽ
    pygame.init()
    # khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    # thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    # lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0  # used to store x coordinate of mouse event
    mousey = 0  # used to store y coordinate of mouse event
    # Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')
    # mainBoard là danh sách 2 chiều lưu các icon,màu sắc trong game được lấy ngẫu nhiên qua hàm getRandomizedBoard()
    mainBoard = getRandomizedBoard()

    # revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    # hàm generateRevealedBoxesData(False) gán gí trị cho revealedBoxes tất cả là False
    revealedBoxes = generateRevealedBoxesData(False)

    # tọa độ chuột lúc clicked
    firstSelection = None  # stores the (x, y) of the first box clicked.

    # tô màu cho nền
    DISPLAYSURF.fill(BGCOLOR)

    # gọi hàm game start
    startGameAnimation(mainBoard)

    """
    Trạng thái trò chơi cho chương trình Câu đố bộ nhớ được lưu trữ trong các biến sau:
    · mainBoard: là danh sách 2 chiều lưu các icon
    · revealedBoxes: revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    · firstSelection: tọa độ chuột lúc clicked
    · mouseClicked: kiểm tra đã clicked chuột chưa
    · mousex: tọa độ x của chuột
    · mousey: tọa độ y của chuột
    """
    #vòng lặp trò chơi
    while True: # main game loop
        #đặt là chưa click chuột
        mouseClicked = False
        #vẽ, tô màu màn hình game
        DISPLAYSURF.fill(BGCOLOR) # drawing the window
        #code hoạt động của game

        # Redraw the screen and wait a clock tick.
        pygame.display.update()
        FPSCLOCK.tick(FPS)


# hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val
def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes


# hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều
def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))

    # Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)  # randomize the order of the icons list

    # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình)
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2)  # calculate how many icons are needed

    # lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon
    icons = icons[:numIconsUsed] * 2  # make two of each

    # Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)

    # tạo danh sách 2 chiều chứa các icon, màu sắc
    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            # thêm icon[0] vào tọa độ (x,y)
            column.append(icons[0])

            # xóa icons[0] đi
            del icons[0]  # remove the icons as we assign them
        # thêm cột cho board
        board.append(column)

    # trả lại danh sách 2 chiều chứa icon, colors
    return board


# hàm bắt đầu chơi game
def startGameAnimation(board):
    # Randomly reveal the boxes 8 at a time.
    # gán tất cả các hộp là chưa tiết lộ
    coveredBoxes = generateRevealedBoxesData(False)
    # khởi tạo rỗ cho các hộp
    boxes = []
    # lấy tọa độ hộp
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            boxes.append((x, y))

    # xáo trộn thứ tự các hộp
    random.shuffle(boxes)

    # tách mảng một chiều boxes thành mảng 2 chiều, có khoảng cách nhau 8 bước
    boxGroups = splitIntoGroupsOf(8, boxes)

    # vẽ khung trò chơi với board và coveredBoxes
    drawBoard(board, coveredBoxes)

    # từ boxGroups tạo hiệu ứng tiết lộ các box
    for boxGroup in boxGroups:
        # vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxGroup
        revealBoxesAnimation(board, boxGroup)
        # vẽ hiệu ứng bao phủ hộp với 8 ô trong boxGroup
        coverBoxesAnimation(board, boxGroup)


# tách mảng một chiều theLisst thành mảng 2 chiều kích thước groupSize
def splitIntoGroupsOf(groupSize, theList):
    # splits a list into a list of lists, where the inner lists have at
    # most groupSize number of items.
    result = []
    # for i in range( start, stop, step)
    for i in range(0, len(theList), groupSize):
        # lấy từ vị trí i đến vị trí i+groupSize
        result.append(theList[i:i + groupSize])
    return result


# hàm vẽ khung trò chơi bao gồm các icon, và trạng thái đã tiết lộ hay chưa
def drawBoard(board, revealed):
    # Draws all of the boxes in their covered or revealed state.
    # vẽ từng tọa độ (boxx,boxy)
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            # chuyển tọa độ hộp thày tọa độ pixcel
            left, top = leftTopCoordsOfBox(boxx, boxy)
            # nếu chưa tiết lộ thì vẽ hình vuông trắng che đi
            if not revealed[boxx][boxy]:
                # Draw a covered box.
                pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE))
            # nếu đã tiết lộ thì vẽ icon
            else:
                # Draw the (revealed) icon.
                # lấy shape, color từ board
                shape, color = getShapeAndColor(board, boxx, boxy)
                # vẽ icon với shape, color tại tọa độ, boxx,boxy
                drawIcon(shape, color, boxx, boxy)


# chuyển hộp icon thành tọa độ pixcel
def leftTopCoordsOfBox(boxx, boxy):
    # Convert board coordinates to pixel coordinates
    left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN
    top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN
    return (left, top)


# vẽ icon với shape,corlor tại vị trí boxx, boxy
def drawIcon(shape, color, boxx, boxy):
    # lấy thông số để vẽ hình
    quarter = int(BOXSIZE * 0.25)  # syntactic sugar
    half = int(BOXSIZE * 0.5)  # syntactic sugar
    left, top = leftTopCoordsOfBox(boxx, boxy)  # get pixel coords from board coords
    # Draw the shapes
    # vẽ hình bánh donut vẽ 2 vòng tròn
    if shape == DONUT:
        pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5)
        pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5)
    # vẽ hình vuông
    elif shape == SQUARE:
        pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half))
    # vẽ hình kim cương (hình thoi)
    elif shape == DIAMOND:
        pygame.draw.polygon(DISPLAYSURF, color, (
        (left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half)))
    # vẽ hình đường gạch chéo
    elif shape == LINES:
        for i in range(0, BOXSIZE, 4):
            pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top))
            pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i))
    # vẽ hình elipse (oval)
    elif shape == OVAL:
        pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half))


def getShapeAndColor(board, boxx, boxy):
    # shape value for x, y spot is stored in board[x][y][0]
    # color value for x, y spot is stored in board[x][y][1]
    return board[boxx][boxy][0], board[boxx][boxy][1]


# lấy từ  boxes vẽ các khung trắng bao phủ với mức độ coverage
def drawBoxCovers(board, boxes, coverage):
    # Draws boxes being covered/revealed. "boxes" is a list
    # of two-item lists, which have the x & y spot of the box.
    # lấy từng box ra để vẻ khung trắng che box icon
    for box in boxes:  # box là tọa độ 1 hộp (x,y) =>box[0],box[1]
        # lấy tọa độ pixcel phía trên, bên trái của hộp từ tọa độ hộp (x,y)
        left, top = leftTopCoordsOfBox(box[0], box[1])

        # vẽ hình chữ nhật màu nền (hình vuông), có tọa độ (trái, trên, BOXSIZE, BOXSIZE)
        # vẽ nền cho box cùng nền board
        pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE))

        # lấy icon, màu sắc của hộp tại box(x,y)
        shape, color = getShapeAndColor(board, box[0], box[1])
        # vẽ hình icon với shape, color, box[0],box[1] vừa lấy được
        drawIcon(shape, color, box[0], box[1])

        # vẽ đa gác hình chữ nhật màu trắng để che icon với mức độ coverage
        if coverage > 0:  # only draw the cover if there is an coverage
            pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE))

    # cập nhật lại màn hình
    pygame.display.update()

    # điều khiển tốc độ, làm chậm lại theo FPS, FPS khung hình trên s, FPS càng lơn thì chạy càng nhanh
    FPSCLOCK.tick(FPS)


# vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxesToReveal
def revealBoxesAnimation(board, boxesToReveal):
    # Do the "box reveal" animation.

    # dùng for vẽ hộp màu trắng để che box chứa icon lại
    # coverage mức độ bao phủ
    # for coverage từ BOXSIZE->-REVEALSPEED - 1, step=-REVEALSPEED
    # tức là tiết lộ từ từ giảm từ từ khung trắng
    for coverage in range(BOXSIZE, -REVEALSPEED - 1, -REVEALSPEED):
        # lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToReveal, coverage)


# vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover
def coverBoxesAnimation(board, boxesToCover):
    # Do the "box cover" animation.
    # for coverage từ 0->BOXSIZE + REVEALSPEED,REVEALSPEED
    # tức là bao phủ từ từ, tăng từ từ khung trắng
    for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED):
        # lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToCover, coverage)


# chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()

Bước 8. Vẽ bảng trò chơi trong vòng lặp trò chơi từ mainBoard,revealedBoxes

# sử dụng thư viện
import random, pygame, sys
from pygame.locals import *

# tốc độ game chương trình
FPS = 30  # frames per second, the general speed of the program
# độ rộng màn hình game
WINDOWWIDTH = 640  # size of window's width in pixels
# độ cao màn hình game
WINDOWHEIGHT = 480  # size of windows' height in pixels
# tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1  # speed boxes' sliding reveals and covers
# kích thước hộp icon
BOXSIZE = 40  # size of box height & width in pixels
# khoảng cách giữa các hộp
GAPSIZE = 10  # size of gap between boxes in pixels
# số hộp một hàng
BOARDWIDTH = 3  # number of columns of icons
# số hộp một cột
BOARDHEIGHT = 2  # number of rows of icons
# kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
# khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
# khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
# sử dụng tên để đặt cho các màu
#            R    G    B
GRAY = (100, 100, 100)
NAVYBLUE = (60, 60, 100)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 128, 0)
PURPLE = (255, 0, 255)
CYAN = (0, 255, 255)
# đặt màu nền
BGCOLOR = NAVYBLUE
# đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
# màu của hộp icon
BOXCOLOR = WHITE
# màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
# các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
# lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
# kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(
    ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."


# hàm chính của chương trình
def main():
    # FPSCLOCK dùng đề chỉnh tốc độ game
    # DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    # khởi tạo chế độ vẽ
    pygame.init()
    # khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    # thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    # lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0  # used to store x coordinate of mouse event
    mousey = 0  # used to store y coordinate of mouse event
    # Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')
    # mainBoard là danh sách 2 chiều lưu các icon,màu sắc trong game được lấy ngẫu nhiên qua hàm getRandomizedBoard()
    mainBoard = getRandomizedBoard()

    # revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    # hàm generateRevealedBoxesData(False) gán gí trị cho revealedBoxes tất cả là False
    revealedBoxes = generateRevealedBoxesData(False)

    # tọa độ chuột lúc clicked
    firstSelection = None  # stores the (x, y) of the first box clicked.

    # tô màu cho nền
    DISPLAYSURF.fill(BGCOLOR)

    # gọi hàm game start
    startGameAnimation(mainBoard)

    """
    Trạng thái trò chơi cho chương trình Câu đố bộ nhớ được lưu trữ trong các biến sau:
    · mainBoard: là danh sách 2 chiều lưu các icon
    · revealedBoxes: revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    · firstSelection: tọa độ chuột lúc clicked
    · mouseClicked: kiểm tra đã clicked chuột chưa
    · mousex: tọa độ x của chuột
    · mousey: tọa độ y của chuột
    """
    #vòng lặp trò chơi
    while True: # main game loop
        #đặt là chưa click chuột
        mouseClicked = False
        #vẽ, tô màu màn hình game
        DISPLAYSURF.fill(BGCOLOR) # drawing the window
        #vẽ bảng trò chơi từ mainBoard,revealedBoxes
        drawBoard(mainBoard, revealedBoxes)

        # Redraw the screen and wait a clock tick.
        pygame.display.update()
        FPSCLOCK.tick(FPS)


# hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val
def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes


# hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều
def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))

    # Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)  # randomize the order of the icons list

    # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình)
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2)  # calculate how many icons are needed

    # lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon
    icons = icons[:numIconsUsed] * 2  # make two of each

    # Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)

    # tạo danh sách 2 chiều chứa các icon, màu sắc
    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            # thêm icon[0] vào tọa độ (x,y)
            column.append(icons[0])

            # xóa icons[0] đi
            del icons[0]  # remove the icons as we assign them
        # thêm cột cho board
        board.append(column)

    # trả lại danh sách 2 chiều chứa icon, colors
    return board


# hàm bắt đầu chơi game
def startGameAnimation(board):
    # Randomly reveal the boxes 8 at a time.
    # gán tất cả các hộp là chưa tiết lộ
    coveredBoxes = generateRevealedBoxesData(False)
    # khởi tạo rỗ cho các hộp
    boxes = []
    # lấy tọa độ hộp
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            boxes.append((x, y))

    # xáo trộn thứ tự các hộp
    random.shuffle(boxes)

    # tách mảng một chiều boxes thành mảng 2 chiều, có khoảng cách nhau 8 bước
    boxGroups = splitIntoGroupsOf(8, boxes)

    # vẽ khung trò chơi với board và coveredBoxes
    drawBoard(board, coveredBoxes)

    # từ boxGroups tạo hiệu ứng tiết lộ các box
    for boxGroup in boxGroups:
        # vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxGroup
        revealBoxesAnimation(board, boxGroup)
        # vẽ hiệu ứng bao phủ hộp với 8 ô trong boxGroup
        coverBoxesAnimation(board, boxGroup)


# tách mảng một chiều theLisst thành mảng 2 chiều kích thước groupSize
def splitIntoGroupsOf(groupSize, theList):
    # splits a list into a list of lists, where the inner lists have at
    # most groupSize number of items.
    result = []
    # for i in range( start, stop, step)
    for i in range(0, len(theList), groupSize):
        # lấy từ vị trí i đến vị trí i+groupSize
        result.append(theList[i:i + groupSize])
    return result


# hàm vẽ khung trò chơi bao gồm các icon, và trạng thái đã tiết lộ hay chưa
def drawBoard(board, revealed):
    # Draws all of the boxes in their covered or revealed state.
    # vẽ từng tọa độ (boxx,boxy)
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            # chuyển tọa độ hộp thày tọa độ pixcel
            left, top = leftTopCoordsOfBox(boxx, boxy)
            # nếu chưa tiết lộ thì vẽ hình vuông trắng che đi
            if not revealed[boxx][boxy]:
                # Draw a covered box.
                pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE))
            # nếu đã tiết lộ thì vẽ icon
            else:
                # Draw the (revealed) icon.
                # lấy shape, color từ board
                shape, color = getShapeAndColor(board, boxx, boxy)
                # vẽ icon với shape, color tại tọa độ, boxx,boxy
                drawIcon(shape, color, boxx, boxy)


# chuyển hộp icon thành tọa độ pixcel
def leftTopCoordsOfBox(boxx, boxy):
    # Convert board coordinates to pixel coordinates
    left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN
    top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN
    return (left, top)


# vẽ icon với shape,corlor tại vị trí boxx, boxy
def drawIcon(shape, color, boxx, boxy):
    # lấy thông số để vẽ hình
    quarter = int(BOXSIZE * 0.25)  # syntactic sugar
    half = int(BOXSIZE * 0.5)  # syntactic sugar
    left, top = leftTopCoordsOfBox(boxx, boxy)  # get pixel coords from board coords
    # Draw the shapes
    # vẽ hình bánh donut vẽ 2 vòng tròn
    if shape == DONUT:
        pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5)
        pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5)
    # vẽ hình vuông
    elif shape == SQUARE:
        pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half))
    # vẽ hình kim cương (hình thoi)
    elif shape == DIAMOND:
        pygame.draw.polygon(DISPLAYSURF, color, (
        (left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half)))
    # vẽ hình đường gạch chéo
    elif shape == LINES:
        for i in range(0, BOXSIZE, 4):
            pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top))
            pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i))
    # vẽ hình elipse (oval)
    elif shape == OVAL:
        pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half))


def getShapeAndColor(board, boxx, boxy):
    # shape value for x, y spot is stored in board[x][y][0]
    # color value for x, y spot is stored in board[x][y][1]
    return board[boxx][boxy][0], board[boxx][boxy][1]


# lấy từ  boxes vẽ các khung trắng bao phủ với mức độ coverage
def drawBoxCovers(board, boxes, coverage):
    # Draws boxes being covered/revealed. "boxes" is a list
    # of two-item lists, which have the x & y spot of the box.
    # lấy từng box ra để vẻ khung trắng che box icon
    for box in boxes:  # box là tọa độ 1 hộp (x,y) =>box[0],box[1]
        # lấy tọa độ pixcel phía trên, bên trái của hộp từ tọa độ hộp (x,y)
        left, top = leftTopCoordsOfBox(box[0], box[1])

        # vẽ hình chữ nhật màu nền (hình vuông), có tọa độ (trái, trên, BOXSIZE, BOXSIZE)
        # vẽ nền cho box cùng nền board
        pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE))

        # lấy icon, màu sắc của hộp tại box(x,y)
        shape, color = getShapeAndColor(board, box[0], box[1])
        # vẽ hình icon với shape, color, box[0],box[1] vừa lấy được
        drawIcon(shape, color, box[0], box[1])

        # vẽ đa gác hình chữ nhật màu trắng để che icon với mức độ coverage
        if coverage > 0:  # only draw the cover if there is an coverage
            pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE))

    # cập nhật lại màn hình
    pygame.display.update()

    # điều khiển tốc độ, làm chậm lại theo FPS, FPS khung hình trên s, FPS càng lơn thì chạy càng nhanh
    FPSCLOCK.tick(FPS)


# vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxesToReveal
def revealBoxesAnimation(board, boxesToReveal):
    # Do the "box reveal" animation.

    # dùng for vẽ hộp màu trắng để che box chứa icon lại
    # coverage mức độ bao phủ
    # for coverage từ BOXSIZE->-REVEALSPEED - 1, step=-REVEALSPEED
    # tức là tiết lộ từ từ giảm từ từ khung trắng
    for coverage in range(BOXSIZE, -REVEALSPEED - 1, -REVEALSPEED):
        # lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToReveal, coverage)


# vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover
def coverBoxesAnimation(board, boxesToCover):
    # Do the "box cover" animation.
    # for coverage từ 0->BOXSIZE + REVEALSPEED,REVEALSPEED
    # tức là bao phủ từ từ, tăng từ từ khung trắng
    for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED):
        # lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToCover, coverage)


# chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()

Bước 9. Bắt các sự kiện trò chơi để thay đổi các biến môi trường của trò chơi

# sử dụng thư viện
import random, pygame, sys
from pygame.locals import *

# tốc độ game chương trình
FPS = 30  # frames per second, the general speed of the program
# độ rộng màn hình game
WINDOWWIDTH = 640  # size of window's width in pixels
# độ cao màn hình game
WINDOWHEIGHT = 480  # size of windows' height in pixels
# tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1  # speed boxes' sliding reveals and covers
# kích thước hộp icon
BOXSIZE = 40  # size of box height & width in pixels
# khoảng cách giữa các hộp
GAPSIZE = 10  # size of gap between boxes in pixels
# số hộp một hàng
BOARDWIDTH = 3  # number of columns of icons
# số hộp một cột
BOARDHEIGHT = 2  # number of rows of icons
# kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
# khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
# khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
# sử dụng tên để đặt cho các màu
#            R    G    B
GRAY = (100, 100, 100)
NAVYBLUE = (60, 60, 100)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 128, 0)
PURPLE = (255, 0, 255)
CYAN = (0, 255, 255)
# đặt màu nền
BGCOLOR = NAVYBLUE
# đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
# màu của hộp icon
BOXCOLOR = WHITE
# màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
# các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
# lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
# kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(
    ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."


# hàm chính của chương trình
def main():
    # FPSCLOCK dùng đề chỉnh tốc độ game
    # DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    # khởi tạo chế độ vẽ
    pygame.init()
    # khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    # thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    # lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0  # used to store x coordinate of mouse event
    mousey = 0  # used to store y coordinate of mouse event
    # Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')
    # mainBoard là danh sách 2 chiều lưu các icon,màu sắc trong game được lấy ngẫu nhiên qua hàm getRandomizedBoard()
    mainBoard = getRandomizedBoard()

    # revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    # hàm generateRevealedBoxesData(False) gán gí trị cho revealedBoxes tất cả là False
    revealedBoxes = generateRevealedBoxesData(False)

    # tọa độ chuột lúc clicked
    firstSelection = None  # stores the (x, y) of the first box clicked.

    # tô màu cho nền
    DISPLAYSURF.fill(BGCOLOR)

    # gọi hàm game start
    startGameAnimation(mainBoard)

    """
    Trạng thái trò chơi cho chương trình Câu đố bộ nhớ được lưu trữ trong các biến sau:
    · mainBoard: là danh sách 2 chiều lưu các icon
    · revealedBoxes: revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    · firstSelection: tọa độ chuột lúc clicked
    · mouseClicked: kiểm tra đã clicked chuột chưa
    · mousex: tọa độ x của chuột
    · mousey: tọa độ y của chuột
    """
    #vòng lặp trò chơi
    while True: # main game loop
        #đặt là chưa click chuột
        mouseClicked = False
        #vẽ, tô màu màn hình game
        DISPLAYSURF.fill(BGCOLOR) # drawing the window
        #vẽ bảng trò chơi từ mainBoard,revealedBoxes
        drawBoard(mainBoard, revealedBoxes)
        #lấy các sự kiện để xử lý
        for event in pygame.event.get(): # event handling loop
            #sự kiện bấm nút ESC
            if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
                pygame.quit()
                sys.exit()
            #sự kiện di chuyển chuột
            elif event.type == MOUSEMOTION:
                #lấy tọa độ chuột x,y
                mousex, mousey = event.pos
            #sự kiện thả chuột (trước đó đã click rồi)
            elif event.type == MOUSEBUTTONUP:
                #lấy tọa độ chuột x,y
                mousex, mousey = event.pos
                #đặt cho mouseClicked=True
                mouseClicked = True

        # Redraw the screen and wait a clock tick.
        pygame.display.update()
        FPSCLOCK.tick(FPS)


# hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val
def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes


# hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều
def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))

    # Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)  # randomize the order of the icons list

    # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình)
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2)  # calculate how many icons are needed

    # lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon
    icons = icons[:numIconsUsed] * 2  # make two of each

    # Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)

    # tạo danh sách 2 chiều chứa các icon, màu sắc
    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            # thêm icon[0] vào tọa độ (x,y)
            column.append(icons[0])

            # xóa icons[0] đi
            del icons[0]  # remove the icons as we assign them
        # thêm cột cho board
        board.append(column)

    # trả lại danh sách 2 chiều chứa icon, colors
    return board


# hàm bắt đầu chơi game
def startGameAnimation(board):
    # Randomly reveal the boxes 8 at a time.
    # gán tất cả các hộp là chưa tiết lộ
    coveredBoxes = generateRevealedBoxesData(False)
    # khởi tạo rỗ cho các hộp
    boxes = []
    # lấy tọa độ hộp
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            boxes.append((x, y))

    # xáo trộn thứ tự các hộp
    random.shuffle(boxes)

    # tách mảng một chiều boxes thành mảng 2 chiều, có khoảng cách nhau 8 bước
    boxGroups = splitIntoGroupsOf(8, boxes)

    # vẽ khung trò chơi với board và coveredBoxes
    drawBoard(board, coveredBoxes)

    # từ boxGroups tạo hiệu ứng tiết lộ các box
    for boxGroup in boxGroups:
        # vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxGroup
        revealBoxesAnimation(board, boxGroup)
        # vẽ hiệu ứng bao phủ hộp với 8 ô trong boxGroup
        coverBoxesAnimation(board, boxGroup)


# tách mảng một chiều theLisst thành mảng 2 chiều kích thước groupSize
def splitIntoGroupsOf(groupSize, theList):
    # splits a list into a list of lists, where the inner lists have at
    # most groupSize number of items.
    result = []
    # for i in range( start, stop, step)
    for i in range(0, len(theList), groupSize):
        # lấy từ vị trí i đến vị trí i+groupSize
        result.append(theList[i:i + groupSize])
    return result


# hàm vẽ khung trò chơi bao gồm các icon, và trạng thái đã tiết lộ hay chưa
def drawBoard(board, revealed):
    # Draws all of the boxes in their covered or revealed state.
    # vẽ từng tọa độ (boxx,boxy)
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            # chuyển tọa độ hộp thày tọa độ pixcel
            left, top = leftTopCoordsOfBox(boxx, boxy)
            # nếu chưa tiết lộ thì vẽ hình vuông trắng che đi
            if not revealed[boxx][boxy]:
                # Draw a covered box.
                pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE))
            # nếu đã tiết lộ thì vẽ icon
            else:
                # Draw the (revealed) icon.
                # lấy shape, color từ board
                shape, color = getShapeAndColor(board, boxx, boxy)
                # vẽ icon với shape, color tại tọa độ, boxx,boxy
                drawIcon(shape, color, boxx, boxy)


# chuyển hộp icon thành tọa độ pixcel
def leftTopCoordsOfBox(boxx, boxy):
    # Convert board coordinates to pixel coordinates
    left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN
    top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN
    return (left, top)


# vẽ icon với shape,corlor tại vị trí boxx, boxy
def drawIcon(shape, color, boxx, boxy):
    # lấy thông số để vẽ hình
    quarter = int(BOXSIZE * 0.25)  # syntactic sugar
    half = int(BOXSIZE * 0.5)  # syntactic sugar
    left, top = leftTopCoordsOfBox(boxx, boxy)  # get pixel coords from board coords
    # Draw the shapes
    # vẽ hình bánh donut vẽ 2 vòng tròn
    if shape == DONUT:
        pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5)
        pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5)
    # vẽ hình vuông
    elif shape == SQUARE:
        pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half))
    # vẽ hình kim cương (hình thoi)
    elif shape == DIAMOND:
        pygame.draw.polygon(DISPLAYSURF, color, (
        (left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half)))
    # vẽ hình đường gạch chéo
    elif shape == LINES:
        for i in range(0, BOXSIZE, 4):
            pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top))
            pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i))
    # vẽ hình elipse (oval)
    elif shape == OVAL:
        pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half))


def getShapeAndColor(board, boxx, boxy):
    # shape value for x, y spot is stored in board[x][y][0]
    # color value for x, y spot is stored in board[x][y][1]
    return board[boxx][boxy][0], board[boxx][boxy][1]


# lấy từ  boxes vẽ các khung trắng bao phủ với mức độ coverage
def drawBoxCovers(board, boxes, coverage):
    # Draws boxes being covered/revealed. "boxes" is a list
    # of two-item lists, which have the x & y spot of the box.
    # lấy từng box ra để vẻ khung trắng che box icon
    for box in boxes:  # box là tọa độ 1 hộp (x,y) =>box[0],box[1]
        # lấy tọa độ pixcel phía trên, bên trái của hộp từ tọa độ hộp (x,y)
        left, top = leftTopCoordsOfBox(box[0], box[1])

        # vẽ hình chữ nhật màu nền (hình vuông), có tọa độ (trái, trên, BOXSIZE, BOXSIZE)
        # vẽ nền cho box cùng nền board
        pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE))

        # lấy icon, màu sắc của hộp tại box(x,y)
        shape, color = getShapeAndColor(board, box[0], box[1])
        # vẽ hình icon với shape, color, box[0],box[1] vừa lấy được
        drawIcon(shape, color, box[0], box[1])

        # vẽ đa gác hình chữ nhật màu trắng để che icon với mức độ coverage
        if coverage > 0:  # only draw the cover if there is an coverage
            pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE))

    # cập nhật lại màn hình
    pygame.display.update()

    # điều khiển tốc độ, làm chậm lại theo FPS, FPS khung hình trên s, FPS càng lơn thì chạy càng nhanh
    FPSCLOCK.tick(FPS)


# vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxesToReveal
def revealBoxesAnimation(board, boxesToReveal):
    # Do the "box reveal" animation.

    # dùng for vẽ hộp màu trắng để che box chứa icon lại
    # coverage mức độ bao phủ
    # for coverage từ BOXSIZE->-REVEALSPEED - 1, step=-REVEALSPEED
    # tức là tiết lộ từ từ giảm từ từ khung trắng
    for coverage in range(BOXSIZE, -REVEALSPEED - 1, -REVEALSPEED):
        # lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToReveal, coverage)


# vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover
def coverBoxesAnimation(board, boxesToCover):
    # Do the "box cover" animation.
    # for coverage từ 0->BOXSIZE + REVEALSPEED,REVEALSPEED
    # tức là bao phủ từ từ, tăng từ từ khung trắng
    for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED):
        # lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToCover, coverage)


# chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main() 

Bước 10. Xử lý khi di chuyển chuột qua hộp

# sử dụng thư viện
import random, pygame, sys
from pygame.locals import *

# tốc độ game chương trình
FPS = 30  # frames per second, the general speed of the program
# độ rộng màn hình game
WINDOWWIDTH = 640  # size of window's width in pixels
# độ cao màn hình game
WINDOWHEIGHT = 480  # size of windows' height in pixels
# tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1  # speed boxes' sliding reveals and covers
# kích thước hộp icon
BOXSIZE = 40  # size of box height & width in pixels
# khoảng cách giữa các hộp
GAPSIZE = 10  # size of gap between boxes in pixels
# số hộp một hàng
BOARDWIDTH = 3  # number of columns of icons
# số hộp một cột
BOARDHEIGHT = 2  # number of rows of icons
# kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
# khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
# khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
# sử dụng tên để đặt cho các màu
#            R    G    B
GRAY = (100, 100, 100)
NAVYBLUE = (60, 60, 100)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 128, 0)
PURPLE = (255, 0, 255)
CYAN = (0, 255, 255)
# đặt màu nền
BGCOLOR = NAVYBLUE
# đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
# màu của hộp icon
BOXCOLOR = WHITE
# màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
# các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
# lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
# kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(
    ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."


# hàm chính của chương trình
def main():
    # FPSCLOCK dùng đề chỉnh tốc độ game
    # DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    # khởi tạo chế độ vẽ
    pygame.init()
    # khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    # thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    # lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0  # used to store x coordinate of mouse event
    mousey = 0  # used to store y coordinate of mouse event
    # Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')
    # mainBoard là danh sách 2 chiều lưu các icon,màu sắc trong game được lấy ngẫu nhiên qua hàm getRandomizedBoard()
    mainBoard = getRandomizedBoard()

    # revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    # hàm generateRevealedBoxesData(False) gán gí trị cho revealedBoxes tất cả là False
    revealedBoxes = generateRevealedBoxesData(False)

    # tọa độ chuột lúc clicked
    firstSelection = None  # stores the (x, y) of the first box clicked.

    # tô màu cho nền
    DISPLAYSURF.fill(BGCOLOR)

    # gọi hàm game start
    startGameAnimation(mainBoard)

    """
    Trạng thái trò chơi cho chương trình Câu đố bộ nhớ được lưu trữ trong các biến sau:
    · mainBoard: là danh sách 2 chiều lưu các icon
    · revealedBoxes: revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    · firstSelection: tọa độ chuột lúc clicked
    · mouseClicked: kiểm tra đã clicked chuột chưa
    · mousex: tọa độ x của chuột
    · mousey: tọa độ y của chuột
    """
    #vòng lặp trò chơi
    while True: # main game loop
        #đặt là chưa click chuột
        mouseClicked = False
        #vẽ, tô màu màn hình game
        DISPLAYSURF.fill(BGCOLOR) # drawing the window
        #vẽ bảng trò chơi từ mainBoard,revealedBoxes
        drawBoard(mainBoard, revealedBoxes)
        #lấy các sự kiện để xử lý
        for event in pygame.event.get(): # event handling loop
            #sự kiện bấm nút ESC
            if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
                pygame.quit()
                sys.exit()
            #sự kiện di chuyển chuột
            elif event.type == MOUSEMOTION:
                #lấy tọa độ chuột x,y
                mousex, mousey = event.pos
            #sự kiện thả chuột (trước đó đã click rồi)
            elif event.type == MOUSEBUTTONUP:
                #lấy tọa độ chuột x,y
                mousex, mousey = event.pos
                #đặt cho mouseClicked=True
                mouseClicked = True

        #lấy hộp (boxx,boxy) từ tọa độ chuột 
        boxx, boxy = getBoxAtPixel(mousex, mousey)

        #nếu co tọa độ hộp
        if boxx != None and boxy != None:
            # The mouse is currently over a box.
            # lúc này chuột đang di chuyển qua hộp

            #kiểm tra xem nếu hộp chưa tiết lộ thì ta vẽ HightlightBox xung quanh hộp
            if not revealedBoxes[boxx][boxy]:
                drawHighlightBox(boxx, boxy)

        # Redraw the screen and wait a clock tick.
        pygame.display.update()
        FPSCLOCK.tick(FPS)

#lấy tọa độ hộp từ tọa độ pixcel
def getBoxAtPixel(x, y):
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            #lấy tọa độ pixcel từ tọa độ hộp
            left, top = leftTopCoordsOfBox(boxx, boxy)
            #vẽ hình chữ nhật với tọa độ vừa lấy
            boxRect = pygame.Rect(left, top, BOXSIZE, BOXSIZE)
            #hàm collidepoint(x,y) kiểm tra xem điểm x,y có nằm trong hình chữ nhật không
            #nếu có thì trả về tọa độ hộp
            if boxRect.collidepoint(x, y):
                return (boxx, boxy)
    #nếu không có thì trả về None
    return (None, None)

#vẽ viền sáng khi di chuyern qua hộp
def drawHighlightBox(boxx, boxy):

    left, top = leftTopCoordsOfBox(boxx, boxy)
    pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, (left - 5, top - 5, BOXSIZE + 10, BOXSIZE + 10), 4)


# hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val
def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes


# hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều
def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))

    # Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)  # randomize the order of the icons list

    # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình)
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2)  # calculate how many icons are needed

    # lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon
    icons = icons[:numIconsUsed] * 2  # make two of each

    # Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)

    # tạo danh sách 2 chiều chứa các icon, màu sắc
    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            # thêm icon[0] vào tọa độ (x,y)
            column.append(icons[0])

            # xóa icons[0] đi
            del icons[0]  # remove the icons as we assign them
        # thêm cột cho board
        board.append(column)

    # trả lại danh sách 2 chiều chứa icon, colors
    return board


# hàm bắt đầu chơi game
def startGameAnimation(board):
    # Randomly reveal the boxes 8 at a time.
    # gán tất cả các hộp là chưa tiết lộ
    coveredBoxes = generateRevealedBoxesData(False)
    # khởi tạo rỗ cho các hộp
    boxes = []
    # lấy tọa độ hộp
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            boxes.append((x, y))

    # xáo trộn thứ tự các hộp
    random.shuffle(boxes)

    # tách mảng một chiều boxes thành mảng 2 chiều, có khoảng cách nhau 8 bước
    boxGroups = splitIntoGroupsOf(8, boxes)

    # vẽ khung trò chơi với board và coveredBoxes
    drawBoard(board, coveredBoxes)

    # từ boxGroups tạo hiệu ứng tiết lộ các box
    for boxGroup in boxGroups:
        # vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxGroup
        revealBoxesAnimation(board, boxGroup)
        # vẽ hiệu ứng bao phủ hộp với 8 ô trong boxGroup
        coverBoxesAnimation(board, boxGroup)


# tách mảng một chiều theLisst thành mảng 2 chiều kích thước groupSize
def splitIntoGroupsOf(groupSize, theList):
    # splits a list into a list of lists, where the inner lists have at
    # most groupSize number of items.
    result = []
    # for i in range( start, stop, step)
    for i in range(0, len(theList), groupSize):
        # lấy từ vị trí i đến vị trí i+groupSize
        result.append(theList[i:i + groupSize])
    return result


# hàm vẽ khung trò chơi bao gồm các icon, và trạng thái đã tiết lộ hay chưa
def drawBoard(board, revealed):
    # Draws all of the boxes in their covered or revealed state.
    # vẽ từng tọa độ (boxx,boxy)
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            # chuyển tọa độ hộp thày tọa độ pixcel
            left, top = leftTopCoordsOfBox(boxx, boxy)
            # nếu chưa tiết lộ thì vẽ hình vuông trắng che đi
            if not revealed[boxx][boxy]:
                # Draw a covered box.
                pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE))
            # nếu đã tiết lộ thì vẽ icon
            else:
                # Draw the (revealed) icon.
                # lấy shape, color từ board
                shape, color = getShapeAndColor(board, boxx, boxy)
                # vẽ icon với shape, color tại tọa độ, boxx,boxy
                drawIcon(shape, color, boxx, boxy)


# chuyển hộp icon thành tọa độ pixcel
def leftTopCoordsOfBox(boxx, boxy):
    # Convert board coordinates to pixel coordinates
    left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN
    top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN
    return (left, top)


# vẽ icon với shape,corlor tại vị trí boxx, boxy
def drawIcon(shape, color, boxx, boxy):
    # lấy thông số để vẽ hình
    quarter = int(BOXSIZE * 0.25)  # syntactic sugar
    half = int(BOXSIZE * 0.5)  # syntactic sugar
    left, top = leftTopCoordsOfBox(boxx, boxy)  # get pixel coords from board coords
    # Draw the shapes
    # vẽ hình bánh donut vẽ 2 vòng tròn
    if shape == DONUT:
        pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5)
        pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5)
    # vẽ hình vuông
    elif shape == SQUARE:
        pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half))
    # vẽ hình kim cương (hình thoi)
    elif shape == DIAMOND:
        pygame.draw.polygon(DISPLAYSURF, color, (
        (left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half)))
    # vẽ hình đường gạch chéo
    elif shape == LINES:
        for i in range(0, BOXSIZE, 4):
            pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top))
            pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i))
    # vẽ hình elipse (oval)
    elif shape == OVAL:
        pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half))


def getShapeAndColor(board, boxx, boxy):
    # shape value for x, y spot is stored in board[x][y][0]
    # color value for x, y spot is stored in board[x][y][1]
    return board[boxx][boxy][0], board[boxx][boxy][1]


# lấy từ  boxes vẽ các khung trắng bao phủ với mức độ coverage
def drawBoxCovers(board, boxes, coverage):
    # Draws boxes being covered/revealed. "boxes" is a list
    # of two-item lists, which have the x & y spot of the box.
    # lấy từng box ra để vẻ khung trắng che box icon
    for box in boxes:  # box là tọa độ 1 hộp (x,y) =>box[0],box[1]
        # lấy tọa độ pixcel phía trên, bên trái của hộp từ tọa độ hộp (x,y)
        left, top = leftTopCoordsOfBox(box[0], box[1])

        # vẽ hình chữ nhật màu nền (hình vuông), có tọa độ (trái, trên, BOXSIZE, BOXSIZE)
        # vẽ nền cho box cùng nền board
        pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE))

        # lấy icon, màu sắc của hộp tại box(x,y)
        shape, color = getShapeAndColor(board, box[0], box[1])
        # vẽ hình icon với shape, color, box[0],box[1] vừa lấy được
        drawIcon(shape, color, box[0], box[1])

        # vẽ đa gác hình chữ nhật màu trắng để che icon với mức độ coverage
        if coverage > 0:  # only draw the cover if there is an coverage
            pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE))

    # cập nhật lại màn hình
    pygame.display.update()

    # điều khiển tốc độ, làm chậm lại theo FPS, FPS khung hình trên s, FPS càng lơn thì chạy càng nhanh
    FPSCLOCK.tick(FPS)


# vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxesToReveal
def revealBoxesAnimation(board, boxesToReveal):
    # Do the "box reveal" animation.

    # dùng for vẽ hộp màu trắng để che box chứa icon lại
    # coverage mức độ bao phủ
    # for coverage từ BOXSIZE->-REVEALSPEED - 1, step=-REVEALSPEED
    # tức là tiết lộ từ từ giảm từ từ khung trắng
    for coverage in range(BOXSIZE, -REVEALSPEED - 1, -REVEALSPEED):
        # lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToReveal, coverage)


# vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover
def coverBoxesAnimation(board, boxesToCover):
    # Do the "box cover" animation.
    # for coverage từ 0->BOXSIZE + REVEALSPEED,REVEALSPEED
    # tức là bao phủ từ từ, tăng từ từ khung trắng
    for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED):
        # lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToCover, coverage)


# chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main() 

Bước 11. Xử lý khi Click chuột vào một hộp chưa tiết lộ

#sử dụng thư viện
import random, pygame, sys
from pygame.locals import *
#tốc độ game chương trình
FPS = 30 # frames per second, the general speed of the program
#độ rộng màn hình game
WINDOWWIDTH = 640 # size of window's width in pixels
#độ cao màn hình game
WINDOWHEIGHT = 480 # size of windows' height in pixels
#tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1 # speed boxes' sliding reveals and covers
#kích thước hộp icon
BOXSIZE = 40 # size of box height & width in pixels
#khoảng cách giữa các hộp
GAPSIZE = 10 # size of gap between boxes in pixels
#số hộp một hàng
BOARDWIDTH = 3 # number of columns of icons
#số hộp một cột
BOARDHEIGHT = 2 # number of rows of icons
#kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
#khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
#khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
#sử dụng tên để đặt cho các màu
#            R    G    B
GRAY     = (100, 100, 100)
NAVYBLUE = ( 60,  60, 100)
WHITE    = (255, 255, 255)
RED      = (255,   0,   0)
GREEN    = (  0, 255,   0)
BLUE     = (  0,   0, 255)
YELLOW   = (255, 255,   0)
ORANGE   = (255, 128,   0)
PURPLE   = (255,   0, 255)
CYAN     = (  0, 255, 255)
#đặt màu nền
BGCOLOR = NAVYBLUE
#đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
#màu của hộp icon
BOXCOLOR = WHITE
#màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
#các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
#lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
#kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."

#hàm chính của chương trình
def main():
    #FPSCLOCK dùng đề chỉnh tốc độ game
    #DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    #khởi tạo chế độ vẽ
    pygame.init()
    #khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    #thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    #lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0 # used to store x coordinate of mouse event
    mousey = 0 # used to store y coordinate of mouse event
    #Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')
    #mainBoard là danh sách 2 chiều lưu các icon,màu sắc trong game được lấy ngẫu nhiên qua hàm getRandomizedBoard()
    mainBoard = getRandomizedBoard()

    #revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    #hàm generateRevealedBoxesData(False) gán gí trị cho revealedBoxes tất cả là False
    revealedBoxes = generateRevealedBoxesData(False)

    #tọa độ chuột lúc clicked
    firstSelection = None # stores the (x, y) of the first box clicked.

    #tô màu cho nền
    DISPLAYSURF.fill(BGCOLOR)

    #gọi hàm game start
    startGameAnimation(mainBoard)

    """
    Trạng thái trò chơi cho chương trình Câu đố bộ nhớ được lưu trữ trong các biến sau:
    · mainBoard: là danh sách 2 chiều lưu các icon
    · revealedBoxes: revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    · firstSelection: tọa độ chuột lúc clicked
    · mouseClicked: kiểm tra đã clicked chuột chưa
    · mousex: tọa độ x của chuột
    · mousey: tọa độ y của chuột
    """

    #vòng lặp trò chơi
    while True: # main game loop
        #đặt là chưa click chuột
        mouseClicked = False
        #vẽ, tô màu màn hình game
        DISPLAYSURF.fill(BGCOLOR) # drawing the window
        #vẽ bảng trò chơi từ mainBoard,revealedBoxes
        drawBoard(mainBoard, revealedBoxes)
        #lấy các sự kiện để xử lý
        for event in pygame.event.get(): # event handling loop
            #sự kiện bấm nút ESC
            if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
                pygame.quit()
                sys.exit()
            #sự kiện di chuyển chuột
            elif event.type == MOUSEMOTION:
                #lấy tọa độ chuột x,y
                mousex, mousey = event.pos
            #sự kiện thả chuột (trước đó đã click rồi)
            elif event.type == MOUSEBUTTONUP:
                #lấy tọa độ chuột x,y
                mousex, mousey = event.pos
                #đặt cho mouseClicked=True
                mouseClicked = True
        #lấy hộp (boxx,boxy) từ tọa độ chuột 
        boxx, boxy = getBoxAtPixel(mousex, mousey)

        #nếu co tọa độ hộp
        if boxx != None and boxy != None:
            # The mouse is currently over a box.
            # lúc này chuột đang di chuyển qua hộp

            #kiểm tra xem nếu hộp chưa tiết lộ thì ta vẽ HightlightBox xung quanh hộp
            if not revealedBoxes[boxx][boxy]:
                drawHighlightBox(boxx, boxy)
            #kiểm tra xem nếu hộp chưa tiết lộ và có click chuột thì ta vẽ hiệu ứng tiết lộ hộp này
            if not revealedBoxes[boxx][boxy] and mouseClicked:
                #vẽ hiệu ứng tiết lộ hộp ra với danh sách hộp là [(boxx, boxy)] (chỉ có 1 hộp thôi)
                revealBoxesAnimation(mainBoard, [(boxx, boxy)])

                #đặt trạng thái tiết lộ tại hộp này là True
                revealedBoxes[boxx][boxy] = True # set the box as "revealed"

                #nếu đây là lần chọn hộp 1 thì gán tọa độ hộp cho firstSelection
                if firstSelection == None: # the current box was the first box clicked
                    firstSelection = (boxx, boxy)

                #nếu đây là lần cọn hợp thứ 2 tức là đã có chọn hộp 1 rồi
                else: # the current box was the second box clicked
                    # Check if there is a match between the two icons.
                    # lấy icon, color từ hộp 1 và hộp 2 để so sánh
                    icon1shape, icon1color = getShapeAndColor(mainBoard, firstSelection[0], firstSelection[1])
                    icon2shape, icon2color = getShapeAndColor(mainBoard, boxx, boxy)

                    #trường hợp 2 hộp giống nhau thì giữ nguyên không cần xử lý gì thêm


        # Redraw the screen and wait a clock tick.
        pygame.display.update()
        FPSCLOCK.tick(FPS)

#hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val
def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes

#hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều
def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))
            
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.      
    random.shuffle(icons) # randomize the order of the icons list

    # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình)
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2) # calculate how many icons are needed

    #lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon
    icons = icons[:numIconsUsed] * 2 # make two of each
    
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)

    # tạo danh sách 2 chiều chứa các icon, màu sắc
    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            # thêm icon[0] vào tọa độ (x,y)
            column.append(icons[0])

            # xóa icons[0] đi
            del icons[0] # remove the icons as we assign them
        #thêm cột cho board
        board.append(column)

    #trả lại danh sách 2 chiều chứa icon, colors
    return board

#tách mảng một chiều theLisst thành mảng 2 chiều kích thước groupSize
def splitIntoGroupsOf(groupSize, theList):
    # splits a list into a list of lists, where the inner lists have at
    # most groupSize number of items.
    result = []
    #for i in range( start, stop, step)
    for i in range(0, len(theList), groupSize):
        #lấy từ vị trí i đến vị trí i+groupSize
        result.append(theList[i:i + groupSize])
    return result

#chuyển hộp icon thành tọa độ pixcel
def leftTopCoordsOfBox(boxx, boxy):
    # Convert board coordinates to pixel coordinates
    left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN
    top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN
    return (left, top)

#lấy tọa độ hộp từ tọa độ pixcel
def getBoxAtPixel(x, y):
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            #lấy tọa độ pixcel từ tọa độ hộp
            left, top = leftTopCoordsOfBox(boxx, boxy)
            #vẽ hình chữ nhật với tọa độ vừa lấy
            boxRect = pygame.Rect(left, top, BOXSIZE, BOXSIZE)
            #hàm collidepoint(x,y) kiểm tra xem điểm x,y có nằm trong hình chữ nhật không
            #nếu có thì trả về tọa độ hộp
            if boxRect.collidepoint(x, y):
                return (boxx, boxy)
    #nếu không có thì trả về None
    return (None, None)

#vẽ icon với shape,corlor tại vị trí boxx, boxy
def drawIcon(shape, color, boxx, boxy):
    #lấy thông số để vẽ hình
    quarter = int(BOXSIZE * 0.25) # syntactic sugar
    half =    int(BOXSIZE * 0.5)  # syntactic sugar
    left, top = leftTopCoordsOfBox(boxx, boxy) # get pixel coords from board coords
    # Draw the shapes
    #vẽ hình bánh donut vẽ 2 vòng tròn 
    if shape == DONUT:
        pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5)
        pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5)
    #vẽ hình vuông
    elif shape == SQUARE:
        pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half))
    #vẽ hình kim cương (hình thoi)        
    elif shape == DIAMOND:
        pygame.draw.polygon(DISPLAYSURF, color, ((left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half)))
    #vẽ hình đường gạch chéo
    elif shape == LINES:
        for i in range(0, BOXSIZE, 4):
            pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top))
            pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i))
    #vẽ hình elipse (oval)        
    elif shape == OVAL:
        pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half))

def getShapeAndColor(board, boxx, boxy):
    # shape value for x, y spot is stored in board[x][y][0]
    # color value for x, y spot is stored in board[x][y][1]
    return board[boxx][boxy][0], board[boxx][boxy][1]


#lấy từ  boxes vẽ các khung trắng bao phủ với mức độ coverage
def drawBoxCovers(board, boxes, coverage):
    # Draws boxes being covered/revealed. "boxes" is a list
    # of two-item lists, which have the x & y spot of the box.
    #lấy từng box ra để vẻ khung trắng che box icon
    for box in boxes: #box là tọa độ 1 hộp (x,y) =>box[0],box[1]
        #lấy tọa độ pixcel phía trên, bên trái của hộp từ tọa độ hộp (x,y)
        left, top = leftTopCoordsOfBox(box[0], box[1])
        
        #vẽ hình chữ nhật màu nền (hình vuông), có tọa độ (trái, trên, BOXSIZE, BOXSIZE)
        #vẽ nền cho box cùng nền board
        pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE))

        #lấy icon, màu sắc của hộp tại box(x,y)
        shape, color = getShapeAndColor(board, box[0], box[1])
        #vẽ hình icon với shape, color, box[0],box[1] vừa lấy được
        drawIcon(shape, color, box[0], box[1])

        #vẽ đa gác hình chữ nhật màu trắng để che icon với mức độ coverage
        if coverage > 0: # only draw the cover if there is an coverage
            pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE))

    #cập nhật lại màn hình
    pygame.display.update()
    
    #điều khiển tốc độ, làm chậm lại theo FPS, FPS khung hình trên s, FPS càng lơn thì chạy càng nhanh
    FPSCLOCK.tick(FPS)


#vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxesToReveal
def revealBoxesAnimation(board, boxesToReveal):
    # Do the "box reveal" animation.
    
    #dùng for vẽ hộp màu trắng để che box chứa icon lại
    #coverage mức độ bao phủ
    #for coverage từ BOXSIZE->-REVEALSPEED - 1, step=-REVEALSPEED
    #tức là tiết lộ từ từ giảm từ từ khung trắng
    for coverage in range(BOXSIZE, -REVEALSPEED - 1, -REVEALSPEED):
        #lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToReveal, coverage)

#vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover
def coverBoxesAnimation(board, boxesToCover):
    # Do the "box cover" animation.
    # for coverage từ 0->BOXSIZE + REVEALSPEED,REVEALSPEED
    # tức là bao phủ từ từ, tăng từ từ khung trắng
    for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED):
        #lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToCover, coverage)

#hàm vẽ khung trò chơi bao gồm các icon, và trạng thái đã tiết lộ hay chưa
def drawBoard(board, revealed):
    # Draws all of the boxes in their covered or revealed state.
    #vẽ từng tọa độ (boxx,boxy)
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            #chuyển tọa độ hộp thày tọa độ pixcel
            left, top = leftTopCoordsOfBox(boxx, boxy)
            #nếu chưa tiết lộ thì vẽ hình vuông trắng che đi
            if not revealed[boxx][boxy]:
                # Draw a covered box.
                pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE))
            #nếu đã tiết lộ thì vẽ icon
            else:
                # Draw the (revealed) icon.
                #lấy shape, color từ board
                shape, color = getShapeAndColor(board, boxx, boxy)
                #vẽ icon với shape, color tại tọa độ, boxx,boxy
                drawIcon(shape, color, boxx, boxy)

def drawHighlightBox(boxx, boxy):
    left, top = leftTopCoordsOfBox(boxx, boxy)
    pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, (left - 5, top - 5, BOXSIZE + 10, BOXSIZE + 10), 4)

#hàm bắt đầu chơi game 
def startGameAnimation(board):
    # Randomly reveal the boxes 8 at a time.
    #gán tất cả các hộp là chưa tiết lộ
    coveredBoxes = generateRevealedBoxesData(False)
    #khởi tạo rỗ cho các hộp
    boxes = []
    #lấy tọa độ hộp
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            boxes.append( (x, y) )

    #xáo trộn thứ tự các hộp
    random.shuffle(boxes)

    #tách mảng một chiều boxes thành mảng 2 chiều, có khoảng cách nhau 8 bước
    boxGroups = splitIntoGroupsOf(8, boxes)

    #vẽ khung trò chơi với board và coveredBoxes
    drawBoard(board, coveredBoxes)

    #từ boxGroups tạo hiệu ứng tiết lộ các box
    for boxGroup in boxGroups:
        #vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxGroup
        revealBoxesAnimation(board, boxGroup)
        #vẽ hiệu ứng bao phủ hộp với 8 ô trong boxGroup
        coverBoxesAnimation(board, boxGroup)

#trường hợp click đúng 2 hộp giống nhau
def gameWonAnimation(board):
    # flash the background color when the player has won
    
    coveredBoxes = generateRevealedBoxesData(True)
    color1 = LIGHTBGCOLOR
    color2 = BGCOLOR
    for i in range(13):
        color1, color2 = color2, color1 # swap colors
        DISPLAYSURF.fill(color1)
        drawBoard(board, coveredBoxes)
        pygame.display.update()
        pygame.time.wait(300)

#hàm kiểm tra xem có tất cả các hộp đã được tiết lộ chưa
def hasWon(revealedBoxes):
    # Returns True if all the boxes have been revealed, otherwise False
    for i in revealedBoxes:
        if False in i:
            return False # return False if any boxes are covered.
    return True

#chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()

Bước 12. Xử lý trường hợp 2 hộp chọn khác nhau

#sử dụng thư viện
import random, pygame, sys
from pygame.locals import *
#tốc độ game chương trình
FPS = 30 # frames per second, the general speed of the program
#độ rộng màn hình game
WINDOWWIDTH = 640 # size of window's width in pixels
#độ cao màn hình game
WINDOWHEIGHT = 480 # size of windows' height in pixels
#tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1 # speed boxes' sliding reveals and covers
#kích thước hộp icon
BOXSIZE = 40 # size of box height & width in pixels
#khoảng cách giữa các hộp
GAPSIZE = 10 # size of gap between boxes in pixels
#số hộp một hàng
BOARDWIDTH = 3 # number of columns of icons
#số hộp một cột
BOARDHEIGHT = 2 # number of rows of icons
#kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
#khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
#khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
#sử dụng tên để đặt cho các màu
#            R    G    B
GRAY     = (100, 100, 100)
NAVYBLUE = ( 60,  60, 100)
WHITE    = (255, 255, 255)
RED      = (255,   0,   0)
GREEN    = (  0, 255,   0)
BLUE     = (  0,   0, 255)
YELLOW   = (255, 255,   0)
ORANGE   = (255, 128,   0)
PURPLE   = (255,   0, 255)
CYAN     = (  0, 255, 255)
#đặt màu nền
BGCOLOR = NAVYBLUE
#đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
#màu của hộp icon
BOXCOLOR = WHITE
#màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
#các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
#lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
#kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."

#hàm chính của chương trình
def main():
    #FPSCLOCK dùng đề chỉnh tốc độ game
    #DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    #khởi tạo chế độ vẽ
    pygame.init()
    #khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    #thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    #lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0 # used to store x coordinate of mouse event
    mousey = 0 # used to store y coordinate of mouse event
    #Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')
    #mainBoard là danh sách 2 chiều lưu các icon,màu sắc trong game được lấy ngẫu nhiên qua hàm getRandomizedBoard()
    mainBoard = getRandomizedBoard()

    #revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    #hàm generateRevealedBoxesData(False) gán gí trị cho revealedBoxes tất cả là False
    revealedBoxes = generateRevealedBoxesData(False)

    #tọa độ chuột lúc clicked
    firstSelection = None # stores the (x, y) of the first box clicked.

    #tô màu cho nền
    DISPLAYSURF.fill(BGCOLOR)

    #gọi hàm game start
    startGameAnimation(mainBoard)

    """
    Trạng thái trò chơi cho chương trình Câu đố bộ nhớ được lưu trữ trong các biến sau:
    · mainBoard: là danh sách 2 chiều lưu các icon
    · revealedBoxes: revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    · firstSelection: tọa độ chuột lúc clicked
    · mouseClicked: kiểm tra đã clicked chuột chưa
    · mousex: tọa độ x của chuột
    · mousey: tọa độ y của chuột
    """

    #vòng lặp trò chơi
    while True: # main game loop
        #đặt là chưa click chuột
        mouseClicked = False
        #vẽ, tô màu màn hình game
        DISPLAYSURF.fill(BGCOLOR) # drawing the window
        #vẽ bảng trò chơi từ mainBoard,revealedBoxes
        drawBoard(mainBoard, revealedBoxes)
        #lấy các sự kiện để xử lý
        for event in pygame.event.get(): # event handling loop
            #sự kiện bấm nút ESC
            if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
                pygame.quit()
                sys.exit()
            #sự kiện di chuyển chuột
            elif event.type == MOUSEMOTION:
                #lấy tọa độ chuột x,y
                mousex, mousey = event.pos
            #sự kiện thả chuột (trước đó đã click rồi)
            elif event.type == MOUSEBUTTONUP:
                #lấy tọa độ chuột x,y
                mousex, mousey = event.pos
                #đặt cho mouseClicked=True
                mouseClicked = True
        #lấy hộp (boxx,boxy) từ tọa độ chuột 
        boxx, boxy = getBoxAtPixel(mousex, mousey)

        #nếu co tọa độ hộp
        if boxx != None and boxy != None:
            # The mouse is currently over a box.
            # lúc này chuột đang di chuyển qua hộp

            #kiểm tra xem nếu hộp chưa tiết lộ thì ta vẽ HightlightBox xung quanh hộp
            if not revealedBoxes[boxx][boxy]:
                drawHighlightBox(boxx, boxy)
            #kiểm tra xem nếu hộp chưa tiết lộ và có click chuột thì ta vẽ hiệu ứng tiết lộ hộp này
            if not revealedBoxes[boxx][boxy] and mouseClicked:
                #vẽ hiệu ứng tiết lộ hộp ra với danh sách hộp là [(boxx, boxy)] (chỉ có 1 hộp thôi)
                revealBoxesAnimation(mainBoard, [(boxx, boxy)])

                #đặt trạng thái tiết lộ tại hộp này là True
                revealedBoxes[boxx][boxy] = True # set the box as "revealed"

                #nếu đây là lần chọn hộp 1 thì gán tọa độ hộp cho firstSelection
                if firstSelection == None: # the current box was the first box clicked
                    firstSelection = (boxx, boxy)

                #nếu đây là lần cọn hợp thứ 2 tức là đã có chọn hộp 1 rồi
                else: # the current box was the second box clicked
                    # Check if there is a match between the two icons.
                    # lấy icon, color từ hộp 1 và hộp 2 để so sánh
                    icon1shape, icon1color = getShapeAndColor(mainBoard, firstSelection[0], firstSelection[1])
                    icon2shape, icon2color = getShapeAndColor(mainBoard, boxx, boxy)

                    #trường hợp 2 hộp giống nhau thì giữ nguyên không cần xử lý gì thêm

                    #nếu 2 hộp khác nhau thì xử lý
                    if icon1shape != icon2shape or icon1color != icon2color:
                        # Icons don't match. Re-cover up both selections.
                        #đợi 1s cho người chơi nhìn thấy khác nhau
                        pygame.time.wait(1000) # 1000 milliseconds = 1 sec
                        #gọi hàm vẽ phủ 2 hộp vừa tiết lộ ra [hộp 1,hộp 2]
                        coverBoxesAnimation(mainBoard, [(firstSelection[0], firstSelection[1]), (boxx, boxy)])
                        #đặt lại tình trạng tiết lộ cho 2 hộp là False
                        revealedBoxes[firstSelection[0]][firstSelection[1]] = False
                        revealedBoxes[boxx][boxy] = False


        # Redraw the screen and wait a clock tick.
        pygame.display.update()
        FPSCLOCK.tick(FPS)

#hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val
def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes

#hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều
def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))
            
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.      
    random.shuffle(icons) # randomize the order of the icons list

    # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình)
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2) # calculate how many icons are needed

    #lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon
    icons = icons[:numIconsUsed] * 2 # make two of each
    
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)

    # tạo danh sách 2 chiều chứa các icon, màu sắc
    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            # thêm icon[0] vào tọa độ (x,y)
            column.append(icons[0])

            # xóa icons[0] đi
            del icons[0] # remove the icons as we assign them
        #thêm cột cho board
        board.append(column)

    #trả lại danh sách 2 chiều chứa icon, colors
    return board

#tách mảng một chiều theLisst thành mảng 2 chiều kích thước groupSize
def splitIntoGroupsOf(groupSize, theList):
    # splits a list into a list of lists, where the inner lists have at
    # most groupSize number of items.
    result = []
    #for i in range( start, stop, step)
    for i in range(0, len(theList), groupSize):
        #lấy từ vị trí i đến vị trí i+groupSize
        result.append(theList[i:i + groupSize])
    return result

#chuyển hộp icon thành tọa độ pixcel
def leftTopCoordsOfBox(boxx, boxy):
    # Convert board coordinates to pixel coordinates
    left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN
    top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN
    return (left, top)

#lấy tọa độ hộp từ tọa độ pixcel
def getBoxAtPixel(x, y):
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            #lấy tọa độ pixcel từ tọa độ hộp
            left, top = leftTopCoordsOfBox(boxx, boxy)
            #vẽ hình chữ nhật với tọa độ vừa lấy
            boxRect = pygame.Rect(left, top, BOXSIZE, BOXSIZE)
            #hàm collidepoint(x,y) kiểm tra xem điểm x,y có nằm trong hình chữ nhật không
            #nếu có thì trả về tọa độ hộp
            if boxRect.collidepoint(x, y):
                return (boxx, boxy)
    #nếu không có thì trả về None
    return (None, None)

#vẽ icon với shape,corlor tại vị trí boxx, boxy
def drawIcon(shape, color, boxx, boxy):
    #lấy thông số để vẽ hình
    quarter = int(BOXSIZE * 0.25) # syntactic sugar
    half =    int(BOXSIZE * 0.5)  # syntactic sugar
    left, top = leftTopCoordsOfBox(boxx, boxy) # get pixel coords from board coords
    # Draw the shapes
    #vẽ hình bánh donut vẽ 2 vòng tròn 
    if shape == DONUT:
        pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5)
        pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5)
    #vẽ hình vuông
    elif shape == SQUARE:
        pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half))
    #vẽ hình kim cương (hình thoi)        
    elif shape == DIAMOND:
        pygame.draw.polygon(DISPLAYSURF, color, ((left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half)))
    #vẽ hình đường gạch chéo
    elif shape == LINES:
        for i in range(0, BOXSIZE, 4):
            pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top))
            pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i))
    #vẽ hình elipse (oval)        
    elif shape == OVAL:
        pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half))

def getShapeAndColor(board, boxx, boxy):
    # shape value for x, y spot is stored in board[x][y][0]
    # color value for x, y spot is stored in board[x][y][1]
    return board[boxx][boxy][0], board[boxx][boxy][1]


#lấy từ  boxes vẽ các khung trắng bao phủ với mức độ coverage
def drawBoxCovers(board, boxes, coverage):
    # Draws boxes being covered/revealed. "boxes" is a list
    # of two-item lists, which have the x & y spot of the box.
    #lấy từng box ra để vẻ khung trắng che box icon
    for box in boxes: #box là tọa độ 1 hộp (x,y) =>box[0],box[1]
        #lấy tọa độ pixcel phía trên, bên trái của hộp từ tọa độ hộp (x,y)
        left, top = leftTopCoordsOfBox(box[0], box[1])
        
        #vẽ hình chữ nhật màu nền (hình vuông), có tọa độ (trái, trên, BOXSIZE, BOXSIZE)
        #vẽ nền cho box cùng nền board
        pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE))

        #lấy icon, màu sắc của hộp tại box(x,y)
        shape, color = getShapeAndColor(board, box[0], box[1])
        #vẽ hình icon với shape, color, box[0],box[1] vừa lấy được
        drawIcon(shape, color, box[0], box[1])

        #vẽ đa gác hình chữ nhật màu trắng để che icon với mức độ coverage
        if coverage > 0: # only draw the cover if there is an coverage
            pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE))

    #cập nhật lại màn hình
    pygame.display.update()
    
    #điều khiển tốc độ, làm chậm lại theo FPS, FPS khung hình trên s, FPS càng lơn thì chạy càng nhanh
    FPSCLOCK.tick(FPS)


#vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxesToReveal
def revealBoxesAnimation(board, boxesToReveal):
    # Do the "box reveal" animation.
    
    #dùng for vẽ hộp màu trắng để che box chứa icon lại
    #coverage mức độ bao phủ
    #for coverage từ BOXSIZE->-REVEALSPEED - 1, step=-REVEALSPEED
    #tức là tiết lộ từ từ giảm từ từ khung trắng
    for coverage in range(BOXSIZE, -REVEALSPEED - 1, -REVEALSPEED):
        #lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToReveal, coverage)

#vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover
def coverBoxesAnimation(board, boxesToCover):
    # Do the "box cover" animation.
    # for coverage từ 0->BOXSIZE + REVEALSPEED,REVEALSPEED
    # tức là bao phủ từ từ, tăng từ từ khung trắng
    for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED):
        #lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToCover, coverage)

#hàm vẽ khung trò chơi bao gồm các icon, và trạng thái đã tiết lộ hay chưa
def drawBoard(board, revealed):
    # Draws all of the boxes in their covered or revealed state.
    #vẽ từng tọa độ (boxx,boxy)
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            #chuyển tọa độ hộp thày tọa độ pixcel
            left, top = leftTopCoordsOfBox(boxx, boxy)
            #nếu chưa tiết lộ thì vẽ hình vuông trắng che đi
            if not revealed[boxx][boxy]:
                # Draw a covered box.
                pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE))
            #nếu đã tiết lộ thì vẽ icon
            else:
                # Draw the (revealed) icon.
                #lấy shape, color từ board
                shape, color = getShapeAndColor(board, boxx, boxy)
                #vẽ icon với shape, color tại tọa độ, boxx,boxy
                drawIcon(shape, color, boxx, boxy)

def drawHighlightBox(boxx, boxy):
    left, top = leftTopCoordsOfBox(boxx, boxy)
    pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, (left - 5, top - 5, BOXSIZE + 10, BOXSIZE + 10), 4)

#hàm bắt đầu chơi game 
def startGameAnimation(board):
    # Randomly reveal the boxes 8 at a time.
    #gán tất cả các hộp là chưa tiết lộ
    coveredBoxes = generateRevealedBoxesData(False)
    #khởi tạo rỗ cho các hộp
    boxes = []
    #lấy tọa độ hộp
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            boxes.append( (x, y) )

    #xáo trộn thứ tự các hộp
    random.shuffle(boxes)

    #tách mảng một chiều boxes thành mảng 2 chiều, có khoảng cách nhau 8 bước
    boxGroups = splitIntoGroupsOf(8, boxes)

    #vẽ khung trò chơi với board và coveredBoxes
    drawBoard(board, coveredBoxes)

    #từ boxGroups tạo hiệu ứng tiết lộ các box
    for boxGroup in boxGroups:
        #vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxGroup
        revealBoxesAnimation(board, boxGroup)
        #vẽ hiệu ứng bao phủ hộp với 8 ô trong boxGroup
        coverBoxesAnimation(board, boxGroup)

#trường hợp click đúng 2 hộp giống nhau
def gameWonAnimation(board):
    # flash the background color when the player has won
    
    coveredBoxes = generateRevealedBoxesData(True)
    color1 = LIGHTBGCOLOR
    color2 = BGCOLOR
    for i in range(13):
        color1, color2 = color2, color1 # swap colors
        DISPLAYSURF.fill(color1)
        drawBoard(board, coveredBoxes)
        pygame.display.update()
        pygame.time.wait(300)

#hàm kiểm tra xem có tất cả các hộp đã được tiết lộ chưa
def hasWon(revealedBoxes):
    # Returns True if all the boxes have been revealed, otherwise False
    for i in revealedBoxes:
        if False in i:
            return False # return False if any boxes are covered.
    return True

#chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()

Bước 13: Chiến thắng trò chơi, và cho chơi lại

#sử dụng thư viện
import random, pygame, sys
from pygame.locals import *
#tốc độ game chương trình
FPS = 30 # frames per second, the general speed of the program
#độ rộng màn hình game
WINDOWWIDTH = 640 # size of window's width in pixels
#độ cao màn hình game
WINDOWHEIGHT = 480 # size of windows' height in pixels
#tốc độ tiết lộ hộp icon, số lượng hình sẽ vẽ, REVEALSPEED càng nhỏ thì vẽ càng nhiều hình
REVEALSPEED = 1 # speed boxes' sliding reveals and covers
#kích thước hộp icon
BOXSIZE = 40 # size of box height & width in pixels
#khoảng cách giữa các hộp
GAPSIZE = 10 # size of gap between boxes in pixels
#số hộp một hàng
BOARDWIDTH = 3 # number of columns of icons
#số hộp một cột
BOARDHEIGHT = 2 # number of rows of icons
#kiểm tra xem số hộp có chẵn không, nếu ko chẵn báo lỗi
assert (BOARDWIDTH * BOARDHEIGHT) % 2 == 0, 'Board needs to have an even number of boxes for pairs of matches.'
#khoảng cách lề hàng
XMARGIN = int((WINDOWWIDTH - (BOARDWIDTH * (BOXSIZE + GAPSIZE))) / 2)
#khoảng cách lề cột
YMARGIN = int((WINDOWHEIGHT - (BOARDHEIGHT * (BOXSIZE + GAPSIZE))) / 2)
#sử dụng tên để đặt cho các màu
#            R    G    B
GRAY     = (100, 100, 100)
NAVYBLUE = ( 60,  60, 100)
WHITE    = (255, 255, 255)
RED      = (255,   0,   0)
GREEN    = (  0, 255,   0)
BLUE     = (  0,   0, 255)
YELLOW   = (255, 255,   0)
ORANGE   = (255, 128,   0)
PURPLE   = (255,   0, 255)
CYAN     = (  0, 255, 255)
#đặt màu nền
BGCOLOR = NAVYBLUE
#đặt màu nổi lên lúc dành chiến thắng
LIGHTBGCOLOR = GRAY
#màu của hộp icon
BOXCOLOR = WHITE
#màu nổi lên lúc hover chuột qua
HIGHLIGHTCOLOR = BLUE
#các icon sử dụng vẽ trong hộp
DONUT = 'donut'
SQUARE = 'square'
DIAMOND = 'diamond'
LINES = 'lines'
OVAL = 'oval'
#lưu tất cả màu sắc, icon vào hai danh sách
ALLCOLORS = (RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE, CYAN)
ALLSHAPES = (DONUT, SQUARE, DIAMOND, LINES, OVAL)
#kiểm tra xem có đủ icon cho bảng game không
assert len(ALLCOLORS) * len(ALLSHAPES) * 2 >= BOARDWIDTH * BOARDHEIGHT, "Board is too big for the number of shapes/colors defined."

#hàm chính của chương trình
def main():
    #FPSCLOCK dùng đề chỉnh tốc độ game
    #DISPLAYSURF dùng để vẽ ra màn hình
    global FPSCLOCK, DISPLAYSURF
    #khởi tạo chế độ vẽ
    pygame.init()
    #khai báo sử dụng đối tượng thời gian trong pygame để chỉnh tốc độ game
    FPSCLOCK = pygame.time.Clock()
    #thiết lập màn hình trò chơi với kích thước WINDOWWIDTH* WINDOWHEIGHT
    DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
    #lưu tọa độ sự kiện lúc di chuyển chuột (hoành độ, tung độ)
    mousex = 0 # used to store x coordinate of mouse event
    mousey = 0 # used to store y coordinate of mouse event
    #Đặt tên hiển thị cho game
    pygame.display.set_caption('Memory Game')
    #mainBoard là danh sách 2 chiều lưu các icon,màu sắc trong game được lấy ngẫu nhiên qua hàm getRandomizedBoard()
    mainBoard = getRandomizedBoard()

    #revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    #hàm generateRevealedBoxesData(False) gán gí trị cho revealedBoxes tất cả là False
    revealedBoxes = generateRevealedBoxesData(False)

    #tọa độ chuột lúc clicked
    firstSelection = None # stores the (x, y) of the first box clicked.

    #tô màu cho nền
    DISPLAYSURF.fill(BGCOLOR)

    #gọi hàm game start
    startGameAnimation(mainBoard)

    """
    Trạng thái trò chơi cho chương trình Câu đố bộ nhớ được lưu trữ trong các biến sau:
    · mainBoard: là danh sách 2 chiều lưu các icon
    · revealedBoxes: revealedBoxes lưu trạng thái true/false để biết hộp này đã tiết lộ chưa
    · firstSelection: tọa độ chuột lúc clicked
    · mouseClicked: kiểm tra đã clicked chuột chưa
    · mousex: tọa độ x của chuột
    · mousey: tọa độ y của chuột
    """

    #vòng lặp trò chơi
    while True: # main game loop
        #đặt là chưa click chuột
        mouseClicked = False
        #vẽ, tô màu màn hình game
        DISPLAYSURF.fill(BGCOLOR) # drawing the window
        #vẽ bảng trò chơi từ mainBoard,revealedBoxes
        drawBoard(mainBoard, revealedBoxes)
        #lấy các sự kiện để xử lý
        for event in pygame.event.get(): # event handling loop
            #sự kiện bấm nút ESC
            if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
                pygame.quit()
                sys.exit()
            #sự kiện di chuyển chuột
            elif event.type == MOUSEMOTION:
                #lấy tọa độ chuột x,y
                mousex, mousey = event.pos
            #sự kiện thả chuột (trước đó đã click rồi)
            elif event.type == MOUSEBUTTONUP:
                #lấy tọa độ chuột x,y
                mousex, mousey = event.pos
                #đặt cho mouseClicked=True
                mouseClicked = True
        #lấy hộp (boxx,boxy) từ tọa độ chuột 
        boxx, boxy = getBoxAtPixel(mousex, mousey)

        #nếu co tọa độ hộp
        if boxx != None and boxy != None:
            # The mouse is currently over a box.
            # lúc này chuột đang di chuyển qua hộp

            #kiểm tra xem nếu hộp chưa tiết lộ thì ta vẽ HightlightBox xung quanh hộp
            if not revealedBoxes[boxx][boxy]:
                drawHighlightBox(boxx, boxy)
            #kiểm tra xem nếu hộp chưa tiết lộ và có click chuột thì ta vẽ hiệu ứng tiết lộ hộp này
            if not revealedBoxes[boxx][boxy] and mouseClicked:
                #vẽ hiệu ứng tiết lộ hộp ra với danh sách hộp là [(boxx, boxy)] (chỉ có 1 hộp thôi)
                revealBoxesAnimation(mainBoard, [(boxx, boxy)])

                #đặt trạng thái tiết lộ tại hộp này là True
                revealedBoxes[boxx][boxy] = True # set the box as "revealed"

                #nếu đây là lần chọn hộp 1 thì gán tọa độ hộp cho firstSelection
                if firstSelection == None: # the current box was the first box clicked
                    firstSelection = (boxx, boxy)

                #nếu đây là lần cọn hợp thứ 2 tức là đã có chọn hộp 1 rồi
                else: # the current box was the second box clicked
                    # Check if there is a match between the two icons.
                    # lấy icon, color từ hộp 1 và hộp 2 để so sánh
                    icon1shape, icon1color = getShapeAndColor(mainBoard, firstSelection[0], firstSelection[1])
                    icon2shape, icon2color = getShapeAndColor(mainBoard, boxx, boxy)

                    #trường hợp 2 hộp giống nhau thì giữ nguyên không cần xử lý gì thêm

                    #nếu 2 hộp khác nhau thì xử lý
                    if icon1shape != icon2shape or icon1color != icon2color:
                        # Icons don't match. Re-cover up both selections.
                        #đợi 1s cho người chơi nhìn thấy khác nhau
                        pygame.time.wait(1000) # 1000 milliseconds = 1 sec
                        #gọi hàm vẽ phủ 2 hộp vừa tiết lộ ra [hộp 1,hộp 2]
                        coverBoxesAnimation(mainBoard, [(firstSelection[0], firstSelection[1]), (boxx, boxy)])
                        #đặt lại tình trạng tiết lộ cho 2 hộp là False
                        revealedBoxes[firstSelection[0]][firstSelection[1]] = False
                        revealedBoxes[boxx][boxy] = False

                    #kiểm tra xem tất cả các hộp đã được tiết lộ chưa
                    elif hasWon(revealedBoxes): # check if all pairs found
                        gameWonAnimation(mainBoard)
                        #cho đợi 2s
                        pygame.time.wait(2000)
                        # Reset the board
                        # khởi tạo lại board cho chơi lại từ đầu
                        mainBoard = getRandomizedBoard()
                        revealedBoxes = generateRevealedBoxesData(False)
                        # Show the fully unrevealed board for a second.
                        drawBoard(mainBoard, revealedBoxes)
                        pygame.display.update()
                        pygame.time.wait(1000)
                        # Replay the start game animation.
                        # cho chơi lại từ đầu
                        startGameAnimation(mainBoard)


                    #đặt lại firstSelection=None   
                    firstSelection = None # reset firstSelection variable
        # Redraw the screen and wait a clock tick.
        pygame.display.update()
        FPSCLOCK.tick(FPS)

#hàm gán cho danh sách 2 chiều revealedBoxes[] mang giá trị val
def generateRevealedBoxesData(val):
    revealedBoxes = []
    for i in range(BOARDWIDTH):
        revealedBoxes.append([val] * BOARDHEIGHT)
    return revealedBoxes

#hàm sinh ngẫu nhiên ra danh sách hai chiều các icon, màu sắc trả lại danh sách 2 chiều
def getRandomizedBoard():
    # Get a list of every possible shape in every possible color.
    icons = []
    for color in ALLCOLORS:
        for shape in ALLSHAPES:
            icons.append((shape, color))
            
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.      
    random.shuffle(icons) # randomize the order of the icons list

    # lấy đủ số icon cần sử dụng = BOARDWIDTH * BOARDHEIGHT /2 với (BOARDWIDTH * BOARDHEIGHT là tổng số hình)
    numIconsUsed = int(BOARDWIDTH * BOARDHEIGHT / 2) # calculate how many icons are needed

    #lấy phần tử từ 0->numIconsUsed-1 trong list icons và gấp đôi lên để tạo danh sách chứa từng cặp icon
    icons = icons[:numIconsUsed] * 2 # make two of each
    
    #Hàm shuffle() giúp sắp xếp các item trong list một cách ngẫu nhiên.
    random.shuffle(icons)

    # tạo danh sách 2 chiều chứa các icon, màu sắc
    # Create the board data structure, with randomly placed icons.
    board = []
    for x in range(BOARDWIDTH):
        column = []
        for y in range(BOARDHEIGHT):
            # thêm icon[0] vào tọa độ (x,y)
            column.append(icons[0])

            # xóa icons[0] đi
            del icons[0] # remove the icons as we assign them
        #thêm cột cho board
        board.append(column)

    #trả lại danh sách 2 chiều chứa icon, colors
    return board

#tách mảng một chiều theLisst thành mảng 2 chiều kích thước groupSize
def splitIntoGroupsOf(groupSize, theList):
    # splits a list into a list of lists, where the inner lists have at
    # most groupSize number of items.
    result = []
    #for i in range( start, stop, step)
    for i in range(0, len(theList), groupSize):
        #lấy từ vị trí i đến vị trí i+groupSize
        result.append(theList[i:i + groupSize])
    return result

#chuyển hộp icon thành tọa độ pixcel
def leftTopCoordsOfBox(boxx, boxy):
    # Convert board coordinates to pixel coordinates
    left = boxx * (BOXSIZE + GAPSIZE) + XMARGIN
    top = boxy * (BOXSIZE + GAPSIZE) + YMARGIN
    return (left, top)

#lấy tọa độ hộp từ tọa độ pixcel
def getBoxAtPixel(x, y):
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            #lấy tọa độ pixcel từ tọa độ hộp
            left, top = leftTopCoordsOfBox(boxx, boxy)
            #vẽ hình chữ nhật với tọa độ vừa lấy
            boxRect = pygame.Rect(left, top, BOXSIZE, BOXSIZE)
            #hàm collidepoint(x,y) kiểm tra xem điểm x,y có nằm trong hình chữ nhật không
            #nếu có thì trả về tọa độ hộp
            if boxRect.collidepoint(x, y):
                return (boxx, boxy)
    #nếu không có thì trả về None
    return (None, None)

#vẽ icon với shape,corlor tại vị trí boxx, boxy
def drawIcon(shape, color, boxx, boxy):
    #lấy thông số để vẽ hình
    quarter = int(BOXSIZE * 0.25) # syntactic sugar
    half =    int(BOXSIZE * 0.5)  # syntactic sugar
    left, top = leftTopCoordsOfBox(boxx, boxy) # get pixel coords from board coords
    # Draw the shapes
    #vẽ hình bánh donut vẽ 2 vòng tròn 
    if shape == DONUT:
        pygame.draw.circle(DISPLAYSURF, color, (left + half, top + half), half - 5)
        pygame.draw.circle(DISPLAYSURF, BGCOLOR, (left + half, top + half), quarter - 5)
    #vẽ hình vuông
    elif shape == SQUARE:
        pygame.draw.rect(DISPLAYSURF, color, (left + quarter, top + quarter, BOXSIZE - half, BOXSIZE - half))
    #vẽ hình kim cương (hình thoi)        
    elif shape == DIAMOND:
        pygame.draw.polygon(DISPLAYSURF, color, ((left + half, top), (left + BOXSIZE - 1, top + half), (left + half, top + BOXSIZE - 1), (left, top + half)))
    #vẽ hình đường gạch chéo
    elif shape == LINES:
        for i in range(0, BOXSIZE, 4):
            pygame.draw.line(DISPLAYSURF, color, (left, top + i), (left + i, top))
            pygame.draw.line(DISPLAYSURF, color, (left + i, top + BOXSIZE - 1), (left + BOXSIZE - 1, top + i))
    #vẽ hình elipse (oval)        
    elif shape == OVAL:
        pygame.draw.ellipse(DISPLAYSURF, color, (left, top + quarter, BOXSIZE, half))

def getShapeAndColor(board, boxx, boxy):
    # shape value for x, y spot is stored in board[x][y][0]
    # color value for x, y spot is stored in board[x][y][1]
    return board[boxx][boxy][0], board[boxx][boxy][1]


#lấy từ  boxes vẽ các khung trắng bao phủ với mức độ coverage
def drawBoxCovers(board, boxes, coverage):
    # Draws boxes being covered/revealed. "boxes" is a list
    # of two-item lists, which have the x & y spot of the box.
    #lấy từng box ra để vẻ khung trắng che box icon
    for box in boxes: #box là tọa độ 1 hộp (x,y) =>box[0],box[1]
        #lấy tọa độ pixcel phía trên, bên trái của hộp từ tọa độ hộp (x,y)
        left, top = leftTopCoordsOfBox(box[0], box[1])
        
        #vẽ hình chữ nhật màu nền (hình vuông), có tọa độ (trái, trên, BOXSIZE, BOXSIZE)
        #vẽ nền cho box cùng nền board
        pygame.draw.rect(DISPLAYSURF, BGCOLOR, (left, top, BOXSIZE, BOXSIZE))

        #lấy icon, màu sắc của hộp tại box(x,y)
        shape, color = getShapeAndColor(board, box[0], box[1])
        #vẽ hình icon với shape, color, box[0],box[1] vừa lấy được
        drawIcon(shape, color, box[0], box[1])

        #vẽ đa gác hình chữ nhật màu trắng để che icon với mức độ coverage
        if coverage > 0: # only draw the cover if there is an coverage
            pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, coverage, BOXSIZE))

    #cập nhật lại màn hình
    pygame.display.update()
    
    #điều khiển tốc độ, làm chậm lại theo FPS, FPS khung hình trên s, FPS càng lơn thì chạy càng nhanh
    FPSCLOCK.tick(FPS)


#vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxesToReveal
def revealBoxesAnimation(board, boxesToReveal):
    # Do the "box reveal" animation.
    
    #dùng for vẽ hộp màu trắng để che box chứa icon lại
    #coverage mức độ bao phủ
    #for coverage từ BOXSIZE->-REVEALSPEED - 1, step=-REVEALSPEED
    #tức là tiết lộ từ từ giảm từ từ khung trắng
    for coverage in range(BOXSIZE, -REVEALSPEED - 1, -REVEALSPEED):
        #lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToReveal, coverage)

#vẽ hiệu ứng bao phủ hộp với 8 ô trong boxesToCover
def coverBoxesAnimation(board, boxesToCover):
    # Do the "box cover" animation.
    # for coverage từ 0->BOXSIZE + REVEALSPEED,REVEALSPEED
    # tức là bao phủ từ từ, tăng từ từ khung trắng
    for coverage in range(0, BOXSIZE + REVEALSPEED, REVEALSPEED):
        #lấy từ boxesToCover vẽ các khung trắng bao phủ với mức độ coverage
        drawBoxCovers(board, boxesToCover, coverage)

#hàm vẽ khung trò chơi bao gồm các icon, và trạng thái đã tiết lộ hay chưa
def drawBoard(board, revealed):
    # Draws all of the boxes in their covered or revealed state.
    #vẽ từng tọa độ (boxx,boxy)
    for boxx in range(BOARDWIDTH):
        for boxy in range(BOARDHEIGHT):
            #chuyển tọa độ hộp thày tọa độ pixcel
            left, top = leftTopCoordsOfBox(boxx, boxy)
            #nếu chưa tiết lộ thì vẽ hình vuông trắng che đi
            if not revealed[boxx][boxy]:
                # Draw a covered box.
                pygame.draw.rect(DISPLAYSURF, BOXCOLOR, (left, top, BOXSIZE, BOXSIZE))
            #nếu đã tiết lộ thì vẽ icon
            else:
                # Draw the (revealed) icon.
                #lấy shape, color từ board
                shape, color = getShapeAndColor(board, boxx, boxy)
                #vẽ icon với shape, color tại tọa độ, boxx,boxy
                drawIcon(shape, color, boxx, boxy)

def drawHighlightBox(boxx, boxy):
    left, top = leftTopCoordsOfBox(boxx, boxy)
    pygame.draw.rect(DISPLAYSURF, HIGHLIGHTCOLOR, (left - 5, top - 5, BOXSIZE + 10, BOXSIZE + 10), 4)

#hàm bắt đầu chơi game 
def startGameAnimation(board):
    # Randomly reveal the boxes 8 at a time.
    #gán tất cả các hộp là chưa tiết lộ
    coveredBoxes = generateRevealedBoxesData(False)
    #khởi tạo rỗ cho các hộp
    boxes = []
    #lấy tọa độ hộp
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            boxes.append( (x, y) )

    #xáo trộn thứ tự các hộp
    random.shuffle(boxes)

    #tách mảng một chiều boxes thành mảng 2 chiều, có khoảng cách nhau 8 bước
    boxGroups = splitIntoGroupsOf(8, boxes)

    #vẽ khung trò chơi với board và coveredBoxes
    drawBoard(board, coveredBoxes)

    #từ boxGroups tạo hiệu ứng tiết lộ các box
    for boxGroup in boxGroups:
        #vẽ hiệu ứng tiết lộ hộp với 8 ô trong boxGroup
        revealBoxesAnimation(board, boxGroup)
        #vẽ hiệu ứng bao phủ hộp với 8 ô trong boxGroup
        coverBoxesAnimation(board, boxGroup)

#trường hợp click đúng 2 hộp giống nhau
def gameWonAnimation(board):
    # flash the background color when the player has won
    
    coveredBoxes = generateRevealedBoxesData(True)
    color1 = LIGHTBGCOLOR
    color2 = BGCOLOR
    for i in range(13):
        color1, color2 = color2, color1 # swap colors
        DISPLAYSURF.fill(color1)
        drawBoard(board, coveredBoxes)
        pygame.display.update()
        pygame.time.wait(300)

#hàm kiểm tra xem có tất cả các hộp đã được tiết lộ chưa
def hasWon(revealedBoxes):
    # Returns True if all the boxes have been revealed, otherwise False
    for i in revealedBoxes:
        if False in i:
            return False # return False if any boxes are covered.
    return True

#chương trình bắt đầu chạy từ đây và gọi hàm main
if __name__ == '__main__':
    main()


Cám ơn bạn đã xem bài viết.

 

Bài viết cùng chủ đề