컴퓨터/Python

Python (14) - Tic-Tac-Toe게임을 만들어보자 (pygame)

달서비 2023. 2. 14. 16:52

파이썬에는 pygame이라는 멀티미디어 표현을 위한 라이브러리가 있습니다. 해당 라이브러리를 통해 다양한 게임들을 제작할 수 있습니다. 해당 라이브러리 소개를 위한 첫 프로젝트로 간단하게 제작이 가능한 틱택토를 구현해보았습니다.

 

완성된 소스 코드가 필요하신 분은 아래 링크 클릭하여 소스 코드로 바로 이동하면 됩니다.

소스 코드

 

Tic-Tac-Toe (틱택토)

Tic Tac Toe - pixabay

틱택토는 서양 놀이 중 하나로 3x3 보드에서 가로, 세로, 대각선 중 한 줄을 만들면 승리하는 게임입니다. 간단하게 삼목입니다.  플레이 방법은 다음과 같습니다.

  1. 가로 두 줄 세로 두 줄을 그립니다.
  2. 1P와 2P는 3x3 공간 안에 각각 O, X를 번갈아 가면서 그립니다.
  3. 가로, 세로, 대각선 중 한 줄이 완성되는 경우 만든 사람이 승리합니다.

게임을 만들어보자

위에서 적은 플레이 방법을 알고리즘도로 표현하면 다음과 같습니다.

틱택토 알고리즘도

해당 알고리즘도를 이용하여 간단하게 표시하였습니다.

여기서 주요한 부분이 되는 것은

  1. init & 3x3 보드 제작
  2. O, X 표시
  3. 승리, 무승부 여부 확인
  4. 리셋

해당부분들을  한개 씩 해결해보겠습니다.

 

Step1) init & 3x3 보드 제작

pip install pygame

보드를 제작하기 전에 기반이 되는 pygame 라이브러리를 설치합니다.

 

그리고 초기 환경 구축을 합니다.

import pygame
import numpy

#step1) init
pygame.init()

#color
WHITE = (255,255,255)
YELLOW = (255,255,0)
BLACK = (0,0,0)
RED = (255,0,0)

#font
OX_font = pygame.font.SysFont("arial", 72, True, False) #(폰트,사이즈,볼드,기울기)
result_font = pygame.font.SysFont("arial", 120, True, False) #(폰트,사이즈,볼드,기울기)

#game
grid = numpy.array([[" ", " ", " "],[" ", " ", " "],[" ", " ", " "]]) #3x3 배열 선언
turn = 0
playGame = 1

#design
window =pygame.display.set_mode((600,600))
pygame.display.set_caption('Tic-Tac-Toe')
clock = pygame.time.Clock()
Size = 200

while playGame:
    clock.tick(10)
    window.fill(BLACK) #검정색 화면으로 물들이기

    for event in pygame.event.get(): # 종료키를 누를때 종료하는 기능
        if event.type == pygame.QUIT:
            playGame = 0


    pygame.display.update() #그리기 화면 업데이트

pygame.quit() #pygame 종료

먼저 가로 init 과정까지의 소스 코드입니다. 그리드 부분은 이중 배열을 이용하여 가로 세로를 쉽게 확인할 수 있도록 numpy를 이용하여 진행하였습니다. 게임에서 필요한 변수와 환경을 구현하였습니다. 

 

이제 그리드를 만들어보겠습니다.

def drawGrid(Size):
    COL = 3
    ROW = 3

    for column_index in range(COL):
        for row_index in range(ROW):
            rect = (Size * column_index, Size * row_index, Size, Size)
            pygame.draw.rect(window, WHITE, rect, 1)

그리드 기능은 함수로 구현하였습니다. 현실에서 게임을 할 때는 가로줄 두 개, 세로줄 두 개를 그려 게임을 진행하지만 여기서는 각각의 위치에 사각형으로 분리하였습니다. 

 

Step2) O, X 표시

완성된 그리드에 O, X를 표시하도록 하자

마우스를 이용하여 O, X를 표시하려고 합니다. 이를 이행하기 위한 순서는 다음과 같습니다.

  1. 마우스를 이용해 화면에 둘 위치를 선정합니다.
  2. O, X를 순서대로 설정합니다.
  3. O 또는 X를 그립니다.

먼저는 게임을 실행하는 while 문에 나머지를 이용하여 OX를 순서대로 분리할 수 있습니다. (2번 과정)

    for event in pygame.event.get(): 
        if event.type == pygame.QUIT: # 종료키를 누를 때 이벤트 처리
            playGame=False
        elif event.type == pygame.MOUSEBUTTONDOWN: #클릭할 때 이벤트 처리
            COL = pygame.mouse.get_pos()[1] #세로 마우스 좌표
            ROW = pygame.mouse.get_pos()[0] #가로 마우스 좌표
            loc = locate(COL, ROW, Size)

            if checkBlank(grid, loc[0], loc[1]):
                if turn % 2 == 0:
                    grid[loc[0], loc[1]] = 'O'
                    turn = turn +1
                else:
                    grid[loc[0], loc[1]] = 'X'
                    turn = turn +1

클릭한 좌표를 배열로 변환시키는 과정을 locate라는 함수를 제작하여 인식하였습니다. 그리고 채워진 칸이 중복인 경우 작동을 하지 않도록 checkBlank 함수로 구현하였습니다.

def locate(COL, ROW, Size):
    for column_index in range(COL):
        for row_index in range(ROW):
            if (column_index == int(COL/Size) and row_index == int(ROW/Size)):
                return (column_index, row_index)
                
def checkBlank(grid, COL, ROW):
    if grid[COL][ROW] == ' ':
        return True
    else:
        return False

마지막으로 그리드 안에 O, X를 표시하는 것은 drawOX 함수를 이용하여 구현하였습니다.

def drawOX(grid, Size):
    COL = 3
    ROW = 3
    for column_index in range(COL):
        for row_index in range(ROW):
            if grid[column_index][row_index] == 'O':
                text = OX_font.render('O', False, WHITE)
            elif grid[column_index][row_index] == 'X':
                text = OX_font.render('X', True, YELLOW)
            else:
                text = OX_font.render('X', True, BLACK)
            window.blit(text, (Size * row_index + Size / 3, Size * column_index + Size / 4))

 

 

Step3) 승리, 무승부 여부 확인

결과를 인식하고 출력해보자

승리 여부를 알기 위해서는 한 줄이 만들어지는 것을 확인하여야 합니다. 한 줄이 나타나는 가능성은 아래와 같습니다.

가로, 세로, 좌대각, 우대각

해당 조건들을 먼저 확인한 다음 없는 경우 다음 턴으로 넘깁니다. 그리고 보드에 모든 공간이 다 차 있고 승리 조건이 맞지 않는 경우 무승부로 마무리합니다. 

def winGame(grid, OX):
    if (grid[0][0] == OX and grid[0][1] == OX and grid[0][2] == OX) or \
        (grid[1][0] == OX and grid[1][1] == OX and grid[1][2] == OX) or \
        (grid[2][0] == OX and grid[2][1] == OX and grid[2][2] == OX) or \ #가로
        (grid[0][0] == OX and grid[1][0] == OX and grid[2][0] == OX) or \
        (grid[0][1] == OX and grid[1][1] == OX and grid[2][1] == OX) or \
        (grid[0][2] == OX and grid[1][2] == OX and grid[2][2] == OX) or \ #세로
        (grid[0][0] == OX and grid[1][1] == OX and grid[2][2] == OX) or \ #좌대각
        (grid[0][2] == OX and grid[1][1] == OX and grid[2][0] == OX): #우대각
        return True
    else:
        return False
    
def drawGame(turn):
    if turn == 9:
        return True
    else:
        return False

해당 결과를 인식하여 텍스트로 출력하는 drawResult함수를 추가로 제작하였습니다.

def drawResult(grid, Size, turn):

    if winGame(grid, 'O'):
        text = OX_font.render('"O" player win!', False, WHITE)
    elif winGame(grid, 'X'):
        text = OX_font.render('"X" player win!', False, WHITE)
    elif drawGame(turn):
        text = OX_font.render('draw game', False, WHITE)
    else:
        text = OX_font.render(' ', True, BLACK)

    window.blit(text, (Size, Size))

 

Step4) 리셋

마지막으로 초기화 기능을 구현해보자

승리 혹은 무승부가 된 경우에 게임 다시 시작하시겠습니까? 기능을 제작한 뒤 게임에 영향을 주는 변수들을 초기화한 뒤 다시 게임을 플레이합니다.

if event.type == pygame.KEYUP: #R 버튼을 누를때 이벤트 처리
	if event.key == pygame.K_r:
    	grid = numpy.array([[" ", " ", " "],[" ", " ", " "],[" ", " ", " "]])
        turn = 0

 

전체 소스코드

import pygame
import numpy

pygame.init()

#step1) init

WHITE = (255,255,255)
YELLOW = (255,255,0)
BLACK = (0,0,0)
RED = (255,0,0)

OX_font = pygame.font.SysFont("arial", 72, True, False) #(폰트,사이즈,볼드,기울기)
result_font = pygame.font.SysFont("arial", 90, True, False) #(폰트,사이즈,볼드,기울기)

grid = numpy.array([[" ", " ", " "],[" ", " ", " "],[" ", " ", " "]]) #3x3 배열 선언
turn = 0
playGame = 1

window =pygame.display.set_mode((600,600)) #가로 600, 세로 600화면
pygame.display.set_caption('Tic-Tac-Toe')
clock = pygame.time.Clock()
Size = 200

#step1) grid
def drawGrid(Size): #draw Grid
    COL = 3
    ROW = 3

    for column_index in range(COL):
        for row_index in range(ROW):
            rect = (Size * column_index, Size * row_index, Size, Size)
            pygame.draw.rect(window, WHITE, rect, 1)


#step2) play game and write O or X
def locate(COL, ROW, Size): #find aray with location
    for column_index in range(COL):
        for row_index in range(ROW):
            if (column_index == int(COL/Size) and row_index == int(ROW/Size)):
                return (column_index, row_index)

def checkBlank(grid, COL, ROW): #duplicate prevention
    if grid[COL][ROW] == ' ': 
        return True
    else:
        return False
    
def drawOX(grid, Size): #show O and X
    COL = 3
    ROW = 3
    for column_index in range(COL):
        for row_index in range(ROW):
            if grid[column_index][row_index] == 'O':
                text = OX_font.render('O', False, WHITE)
            elif grid[column_index][row_index] == 'X':
                text = OX_font.render('X', True, YELLOW)
            else:
                text = OX_font.render(' ', True, BLACK)
            window.blit(text, (Size * row_index + Size / 3, Size * column_index + Size / 4))

#step3) who's win? or draw?
def winGame(grid, OX): #check Win
    if (grid[0][0] == OX and grid[0][1] == OX and grid[0][2] == OX) or \
        (grid[1][0] == OX and grid[1][1] == OX and grid[1][2] == OX) or \
        (grid[2][0] == OX and grid[2][1] == OX and grid[2][2] == OX) or \
        (grid[0][0] == OX and grid[1][0] == OX and grid[2][0] == OX) or \
        (grid[0][1] == OX and grid[1][1] == OX and grid[2][1] == OX) or \
        (grid[0][2] == OX and grid[1][2] == OX and grid[2][2] == OX) or \
        (grid[0][0] == OX and grid[1][1] == OX and grid[2][2] == OX) or \
        (grid[0][2] == OX and grid[1][1] == OX and grid[2][0] == OX):
        return True
    else:
        return False
    
def drawGame(turn): #check Draw
    if turn == 9:
        return True
    else:
        return False
        
def drawResult(grid, turn): #show Result

    if winGame(grid, 'O'):
        text = result_font.render('"O" player win!', False, RED)
    elif winGame(grid, 'X'):
        text = result_font.render('"X" player win!', False, RED)
    elif drawGame(turn):
        text = result_font.render('draw game', False, RED)
    else:
        text = result_font.render(' ', True, BLACK)

    window.blit(text, (0, 0))

#step ALL) play it!
while playGame:
    clock.tick(10)
    window.fill(BLACK)

    drawGrid(Size)
    drawOX(grid, Size)
    drawResult(grid, turn)


    for event in pygame.event.get(): 
        if event.type == pygame.QUIT: # 종료키를 누를때 이벤트 처리
            playGame = 0
        if event.type == pygame.MOUSEBUTTONDOWN: # 마우스 클릭을 할때 이벤트 처리
            COL = pygame.mouse.get_pos()[1] #세로 마우스 좌표
            ROW = pygame.mouse.get_pos()[0] #가로 마우스 좌표
            loc = locate(COL, ROW, Size)

            if checkBlank(grid, loc[0], loc[1]):
                if turn % 2 == 0:
                    grid[loc[0], loc[1]] = 'O'
                    turn = turn +1

                else:
                    grid[loc[0], loc[1]] = 'X'
                    turn = turn +1
                print(grid)
        #step4) reset
        if event.type == pygame.KEYUP: #R 버튼을 누를때 이벤트 처리
            if event.key == pygame.K_r:
                grid = numpy.array([[" ", " ", " "],[" ", " ", " "],[" ", " ", " "]])
                turn = 0


    pygame.display.update() #그리기 화면 업데이트

pygame.quit() #pygame 종료

 

pygame을 이용하여 미디어 관련으로 다양한 프로젝트를 실행할 수 있으나 pygame 라이브러리를 한번 정리해야겠다는 생각이 들었습니다. 시간이 날 때 자주 사용하는 메서드를 모아서 정리해보겠습니다.