ساخت بازی دوز با پایتون

tic-tac-toe

06 آبان 1400
tic-tac-toe-game

پیش گفتار

دومین بازی که با pygame می سازیم بازی دوز یا tictactoe است.در این بازی به جای X و O از تصویر گربه و موش استفاده می کنیم تا به نوعی هم با تصویرها کار کرده باشیم و هم در ساخت بازی تنوع به خرج داده باشیم.

بازی دوز

بازی دوز یک بازی دو نفره ‌است. این بازی در یک صفحه با ۳ سطر و ۳ ستون انجام می‌شود. یکی از بازیکن ها علامت‌ X و دیگری O را برمی گزیند.برنده کسی است که بتواند هر سه علامت یا نشانه خود را در یک سطر یا ستون قرار بدهد. بازی که در این قسمت ساخت آن یاد خواهیم گرفت به جای X و O از تصویر گربه و موش استفاده می کند. تصویر بازی در زیر آمده است.

کد کامل بازی در زیر آمده است:

import pygame
import math

pygame.init()

WIDTH = 480
HEIGHT=480
ROWS = 3
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("TicTacToe")

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
RED = (255, 0, 0)
BLUE = (0, 0, 255)

CAT_IMAGE = pygame.transform.scale(pygame.image.load("images/cat.png"), (150, 150))
MOUSE_IMAGE = pygame.transform.scale(pygame.image.load("images/mouse.png"), (150, 150))


END_FONT = pygame.font.SysFont('arial', 40)

def draw_grid():
    gap = WIDTH // ROWS

    x = 0
    y = 0

    for i in range(ROWS):
        x = i * gap
        pygame.draw.line(SCREEN, GRAY, (x, 0), (x, WIDTH), 3)
        pygame.draw.line(SCREEN, GRAY, (0, x), (WIDTH, x), 3)


def initialize_grid():
    dis_to_center = WIDTH // ROWS // 2

    game_array = [[None, None, None], [None, None, None], [None, None, None]]

    for i in range(len(game_array)):
        for j in range(len(game_array[i])):
            x = dis_to_center * (2 * j + 1)
            y = dis_to_center * (2 * i + 1)

            game_array[i][j] = (x, y,"", True)

    return game_array


def click(game_array):
    global cat_turn, mouse_turn, images

    mouse_x, mouse_y = pygame.mouse.get_pos()

    for i in range(len(game_array)):
        for j in range(len(game_array[i])):
            x, y, char, can_play = game_array[i][j]

            dis = math.sqrt((x - mouse_x) ** 2 + (y - mouse_y) ** 2)

            if dis < WIDTH // ROWS // 2 and can_play:
                if cat_turn:  
                    images.append((x, y, CAT_IMAGE))
                    cat_turn = False
                    mouse_turn = True
                    game_array[i][j] = (x, y, 'cat', False)

                elif mouse_turn:  
                    images.append((x, y, MOUSE_IMAGE))
                    cat_turn = True
                    mouse_turn = False
                    game_array[i][j] = (x, y, 'mouse', False)


def has_won(game_array):
    for row in range(len(game_array)):
        if (game_array[row][0][2] == game_array[row][1][2] == game_array[row][2][2]) and game_array[row][0][2] != "":
            display_message(game_array[row][0][2].upper() + "  Win")
            return True

    for col in range(len(game_array)):
        if (game_array[0][col][2] == game_array[1][col][2] == game_array[2][col][2]) and game_array[0][col][2] != "":
            display_message(game_array[0][col][2].upper() + "  Win")
            return True

    if (game_array[0][0][2] == game_array[1][1][2] == game_array[2][2][2]) and game_array[0][0][2] != "":
        display_message(game_array[0][0][2].upper() + "  Won")
        return True

    if (game_array[0][2][2] == game_array[1][1][2] == game_array[2][0][2]) and game_array[0][2][2] != "":
        display_message(game_array[0][2][2].upper() + "  Won")
        return True

    return False


def has_drawn(game_array):
    for i in range(len(game_array)):
        for j in range(len(game_array[i])):
            if game_array[i][j][2] == "":
                return False

    display_message("It's a tie!")
    return True


def display_message(content):
    pygame.time.delay(500)
    SCREEN.fill(WHITE)
    end_text = END_FONT.render(content, 1, BLACK)
    SCREEN.blit(end_text, ((WIDTH - end_text.get_width()) // 2, (WIDTH - end_text.get_height()) // 2))
    pygame.display.update()
    pygame.time.delay(3000)


def render():
    SCREEN.fill(WHITE)
    draw_grid()

    for image in images:
        x, y, IMAGE = image
        SCREEN.blit(IMAGE, (x - IMAGE.get_width() // 2, y - IMAGE.get_height() // 2))

    pygame.display.update()


def main():
    global cat_turn, mouse_turn, images, draw

    images = []
    draw = False

    run = True

    cat_turn = True
    mouse_turn = False

    game_array = initialize_grid()

    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
            if event.type == pygame.MOUSEBUTTONDOWN:
                click(game_array)

        render()

        if has_won(game_array) or has_drawn(game_array):
            run = False

while True:
    if __name__ == '__main__':
        main()

وارد کردن کتابخانه ها و initialize کردن pygame

برای استفاده از pygame آن را وارد برنامه می کنیم. در این برنامه از ماژول math هم استفاده می کنیم. در خط بعدی pygame را initialize می کنیم.اگر این کار را نکنیم pygame کار نخواهد کرد.

import pygame
import math

pygame.init()

تعریف ثابت ها و متغیرها

ثابت ها و متغیر های خود را به ترتیب زیر تعریف خواهیم کرد. رنگ ها را نیز به شکل ثابت تعریف می کنیم.

WIDTH = 480
HEIGHT=480
ROWS = 3
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("TicTacToe")

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
RED = (255, 0, 0)
BLUE = (0, 0, 255)

تعریف تصاویر و فونت

همان طور که از پیش گفتم در این بازی به جای X و O از تصویر گربه و موش استفاده می کنیم. این تصویر ها را در پوشه ای به نام images قرار می دهیم. سپس با کد زیر آن ها را وارد برنامه می کنیم. تصویر گربه را در ثابت CAT_IMAGE و تصویر موش را در MOUSE_IMAGE ذخیره می کنیم. برای بارگذاری یک عکس در برنامه از ماژول pygame.image.load استفاده می کنیم.این ماژول مسیر عکس را به عنوان ورودی می گیرد.

پس با دو دستور زیر عکس موش و گربه را وارد برنامه می کنیم:

pygame.image.load("images/cat.png")
pygame.image.load("images/mouse.png")

می خواهیم که عکس موش و گربه دارای طول و عرض 150 پیکسل باشند. برای تغییر اندازه یک تصویر ماژول pygame.transform.scale را در pygame داریم. این ماژول دو پارامتر می گیرد. پارامتر نخست برای تصویر و پارامتر دوم یک تاپل دوتایی است. در این تاپل طول و عرض تصویر مشخص می شود. مانند کد زیر:

CAT_IMAGE = pygame.transform.scale(pygame.image.load("images/cat.png"), (150, 150))
MOUSE_IMAGE = pygame.transform.scale(pygame.image.load("images/mouse.png"), (150, 150))

اگر بازیکن برنده شود یا مساوی شود می خواهیم پیغام مناسب را به او نشان دهیم برای همین باید از متن ها استفاده کنیم. پس در زیر شی فونت را تعریف می کنیم. برای ساخت این شی از فونت سیستم استفاده می کنیم:

END_FONT = pygame.font.SysFont('arial', 40)

روش ساخت برنامه و تابع main

در این بازی انجام هر کاری را به یک تابع واگذار می کنیم مانند رسم صفحه، نمایش پیام ها و غیره.مهم ترین تابعی که در این بازی داریم تابع main است. سایر توابع در تابع main فراخوانی و استفاده می شوند. حلقه بازی که قلب بازی است در این تابع قرار دارد. این تابع را به صورت زیر تعریف می کنیم:

def main():
    global cat_turn, mouse_turn, images, draw

    images = []
    draw = False

    run = True

    cat_turn = True
    mouse_turn = False

    game_array = initialize_grid()

    while run:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
            if event.type == pygame.MOUSEBUTTONDOWN:
                click(game_array)

        render()

        if has_won(game_array) or has_drawn(game_array):
            run = False

در خط اول متغیرهای global را که cat_turn, mouse_turn, images, draw هستند، تعریف می کنیم. در ابتدای هیچ تصویر موش یا گربه نمایش داده نمی شود و تنها با کلیک کردن روی خانه ها است که تصویر آن ها نمایش داده می شود.پس آرایه images در ابتدا باید خالی باشد و تا وقتی کلیک نکنیم کشیدن تصویر انجام نمی شود:

images = []
draw = False

برای کنترل کردن اجرای حلقه کد زیر را داریم:

run = True

تا هنگامی که متغیر run مقدار True را دارد حلقه اجرا می شود. اولین کسی که بازی را آغاز می کند گربه است. برای پیاده سازی این ویژگی دو متغیر زیر را که برای تعیین نوبت بازیکن است تعریف می کنیم :

cat_turn = True
mouse_turn = False

سپس برای رسم خانه ها کد زیر را می نویسیم:

game_array = initialize_grid()

متد initialize_grid یک آرایه سه در سه را برمی گرداند که در متغیر game_array ذخیره می شود.زیرا سه سطر و سه ستون داریم. شرح این متد را در پایین خواهیم دید. برای رسم خانه های بازی که نه عدد هستند به این متد نیاز داریم.سپس به سراغ حلقه بازی می رویم:

while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
        if event.type == pygame.MOUSEBUTTONDOWN:
            click(game_array)

    render()

    if has_won(game_array) or has_drawn(game_array):
        run = False

در حلقه بالا که به آن حلقه رویداد هم می گوییم دو نوع رویداد بررسی می شوند.رویداد نسخت خروج یا QUIT است و رویداد دوم فشرده شدن موس یا MOUSEBUTTONDOWN است.اگر رویداد QUIT رخ دهد از بازی خارج می شویم. اگر رویداد MOUSEBUTTONDOWN رخ دهد متد click را فراخوانی می کنیم و game_array را به آن می فرستیم. این متد نیز در پایین توضیح داده شده است. کاری که این متد می کند، تعیین نوبت بازیکن ها و نمایش تصویر ها روی صفحه است. پس از آن متد render را فراخوانی می کنیم تا صفحه با رنگ پوشیده شود و صفحه بازی رسم شود.

خط بعدی یک دستور شرطی است. اگر یکی از بازیکن ها برنده شده باشند یا مساوی کرده باشند بازی باید تمام شود. کد زیر این بررسی را انجام می دهد و اگر برد یا تساوی داشتیم بازی را پایان می دهد.برای این کار باید از حلقه رویداد خارج شویم. برای خروج از این حلقه run را مساوی False قرار می دهیم. متد hasWon برای بررسی وجود برنده و متد has_drawn برای بررسی تساوی است. این دو متد نیز در پایین توضیح داده شده اند.

if has_won(game_array) or has_drawn(game_array):
    run = False

رسم خطوط شبکه بندی پنجره

def draw_grid():
    gap = WIDTH // ROWS

    x = 0
    y = 0

    for i in range(ROWS):
        x = i * gap

        pygame.draw.line(SCREEN, GRAY, (x, 0), (x, WIDTH), 3)
        pygame.draw.line(SCREEN, GRAY, (0, x), (WIDTH, x), 3)

بازی در آغاز یک صفحه جدول مانند به سه سطر و سه ستون است. اگر بخواهیم همین تصویر را روی کاغذ بکشیم ابتدا یک مربع بزرگ می کشیم و سپس با سه خط سه سطر و با سه خط دیگر سه ستون می کشیم. پس به شش خط برای شبکه بندی صفحه بازی نیاز داریم.

ساخت آرایه حرکت های بازیکنان

def initialize_grid():
    dis_to_center = WIDTH // ROWS // 2

    game_array = [[None, None, None], [None, None, None], [None, None, None]]

    for i in range(len(game_array)):
        for j in range(len(game_array[i])):
            x = dis_to_center * (2 * j + 1)
            y = dis_to_center * (2 * i + 1)

            game_array[i][j] = (x, y, "", True)

    return game_array

از تابع بالا برای ذخیره مقدار تک تک خانه های بازی استفاده می کنیم.مقدار اولیه خانه ها در ابتدای بازی None است.اگر روی خانه ای کلیک شود آن گاه مقدار مناسب برای آن خانه در آرایه game_array ذخیره می شود.

مدیریت کلیک کردن خانه های بازی

def click(game_array):
    global x_turn, o_turn, images

    # Mouse position
    m_x, m_y = pygame.mouse.get_pos()

    for i in range(len(game_array)):
        for j in range(len(game_array[i])):
            x, y, char, can_play = game_array[i][j]

            # Distance between mouse and the centre of the square
            dis = math.sqrt((x - m_x) ** 2 + (y - m_y) ** 2)

            # If it's inside the square
            if dis < WIDTH // ROWS // 2 and can_play:
                if x_turn:  # If it's X's turn
                    images.append((x, y, X_IMAGE))
                    x_turn = False
                    o_turn = True
                    game_array[i][j] = (x, y, 'x', False)

                elif o_turn:  # If it's O's turn
                    images.append((x, y, O_IMAGE))
                    x_turn = True
                    o_turn = False
                    game_array[i][j] = (x, y, 'o', False)

تابع بالا برای مدیریت کلیک خانه ها به کار می رود.برای تعیین نوبت بازیکن از متغیرهای سراسری cat_turn و mouse_turn استفاده می کنیم. جایی که موس کلیک می شود دارای یک مختصات خواهد بود.این مختصات را در mouse_x و mouse_y ذخیره می کنیم.

global cat_turn, mouse_turn, images

mouse_x, mouse_y = pygame.mouse.get_pos()

تعیین برنده

def has_won(game_array):
    for row in range(len(game_array)):
        if (game_array[row][0][2] == game_array[row][1][2] == game_array[row][2][2]) and game_array[row][0][2] != "":
            display_message(game_array[row][0][2].upper() + "  Win")
            return True

    for col in range(len(game_array)):
        if (game_array[0][col][2] == game_array[1][col][2] == game_array[2][col][2]) and game_array[0][col][2] != "":
            display_message(game_array[0][col][2].upper() + "  Win")
            return True

    if (game_array[0][0][2] == game_array[1][1][2] == game_array[2][2][2]) and game_array[0][0][2] != "":
        display_message(game_array[0][0][2].upper() + "  Won")
        return True

    if (game_array[0][2][2] == game_array[1][1][2] == game_array[2][0][2]) and game_array[0][2][2] != "":
        display_message(game_array[0][2][2].upper() + "  Won")
        return True

    return False

کسی برنده است که همه شکل های خود را در یک سطر یا یک ستون یا قطر های اصلی و فرعی صفحه قرار دهد. اگر حالت های برنده شدن را بشماریم هشت حالت به دست می آید.به شکل زیر نگاه کنید:

شماره خانه ها در زیر آمده است:

باید حرکت های بازیکنان را برررسی کنیم تا ببینیم آیا هیچ یک از هشت حالت بالا به وجود آمده است یا نه. برای این کار سطرها را جدا و ستون ها را جدا بررسی می کنیم. کد زیر سطرها را بررسی می کند:

for row in range(len(game_array)):
    if (game_array[row][0][2] == game_array[row][1][2] == game_array[row][2][2]) and game_array[row][0][2] != "":
        display_message(game_array[row][0][2].upper() + "  Win")
        return True

کد زیر ستون ها را بررسی می کند:

for col in range(len(game_array)):
    if (game_array[0][col][2] == game_array[1][col][2] == game_array[2][col][2]) and game_array[0][col][2] != "":
        display_message(game_array[0][col][2].upper() + "  Win")
        return True

کد زیر قطر اصلی را بررسی می کند:

if (game_array[0][0][2] == game_array[1][1][2] == game_array[2][2][2]) and game_array[0][0][2] != "":
    display_message(game_array[0][0][2].upper() + "  Won")
    return True

کد زیر قطر فرعی را بررسی می کند. اگر هیچ یک از هشت حال بالا رخ ندهد در خط آخر False برگردانده می شود:

if (game_array[0][2][2] == game_array[1][1][2] == game_array[2][0][2]) and game_array[0][2][2] != "":
    display_message(game_array[0][2][2].upper() + "  Won")
    return True

return False

تعیین تساوی

def has_drawn(game_array):
    for i in range(len(game_array)):
        for j in range(len(game_array[i])):
            if game_array[i][j][2] == "":
                return False

    display_message("It's a tie!")
    return True

اگر هیچ یک از هشت حال بالا رخ ندهد و در صفحه جایی برای ادامه بازی نباشد آن گاه دو بازیکن مساوی می شوند. پس باید خانه ها را باید سطر به سطر و ستون به ستون بررسی کنیم تا ببینیم آیا جایی برای ادامه بازی هست یا نه. آرایه game_array به عنوان پارامتر به این تابع فرستاده می شود. تابع با استفاده از این آرایه سطرها و ستون ها را بررسی می کند. اگر هنوز جایی برای بازی باشد False را برمی گرداند در غیر این صورت تابع display_message("It's a tie!") با پیام "It's a tie!" اجرا می شود و مقدار True برگردانده می شود.

نمایش پیام ها

def display_message(content):
    pygame.time.delay(500)
    SCREEN.fill(WHITE)
    end_text = END_FONT.render(content, 1, BLACK)
    SCREEN.blit(end_text, ((WIDTH - end_text.get_width()) // 2, (WIDTH - end_text.get_height()) // 2))
    pygame.display.update()
    pygame.time.delay(3000)

اگر بازیکنی برنده یا بازیکنان با هم مساوی کنند باید پیام مناسب به آن ها نمایش داده شود.پس از معلوم شدن نتیجه پیروزی یا تساوی 500 میلی ثانیه درنگ می کنیم سپس صفحه را با رنگ سفید پاک می کنیم.

pygame.time.delay(500)
SCREEN.fill(WHITE)

تابع display_message پارامتر content را به عنوان ورودی می گیرد.این پارامتر همان پیامی است که می خواهیم به بازیکن نمایش دهیم. رنگ آن را مشکی قرار می دهیم و دوست داریم که محل نمایش آن مرکز صفحه باشد.

end_text = END_FONT.render(content, 1, BLACK)
SCREEN.blit(end_text, ((WIDTH - end_text.get_width()) // 2, (WIDTH - end_text.get_height()) // 2))

در آخر تغییرها را به روزآوری می کنیم و نتیجه را در صفحه نمایش می دهیم. برای این که بازیکنان پیام را با دقت بخوانند آن را به مدت سه ثانیه نمایش می دهیم.

pygame.display.update()
pygame.time.delay(3000)

به روزآوری وضعیت بازی

def render():
    SCREEN.fill(WHITE)
    draw_grid()

    for image in images:
        x, y, IMAGE = image
        SCREEN.blit(IMAGE, (x - IMAGE.get_width() // 2, y - IMAGE.get_height() // 2))

    pygame.display.update()

در این تابع نمایش نهایی تصویر ها و رسم نهایی شبکه انجام می شود. در آخر تغییرها به روزآوری می شوند و نتیجه در صفحه نمایش داده می شود.

کد کامل بازی را می توانید از نشانی دانلود کنید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری بازی‌سازی با پایتون توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.