본문 바로가기

programming/pygame

[pygame] 파이썬으로 게임 만들기 - 틱택토

[pygame] 파이썬으로 게임 만들기 - 틱택토

 

안녕하세요. 심심한 코딩쟁이입니다.

 

오늘은 pygame을 활용해 틱택토 게임을 만들어보려 합니다.

 

천천히 따라 하시면 이해가 가실 테니 느긋하게 살펴보시죠.

 

시작합니다.

 

pygame
pygame


틱택토 전체 코드

 

코드에 주석으로 설명을 달아두었습니다.

 

주석을 보면서 코드를 천천히 이해해 봅시다.

 

import pygame as pg
import sys
from random import randint

WIN_SIZE = 500 # 정사각형 창의 크기
CELL_SIZE = WIN_SIZE // 3 # 틱택토는 3x3 칸이 필요하므로 한 칸의 크기를 구하기 위해 3을 나눔
INF = float('inf') # 초기값을 무한으로 설정하려고 정의한 변수
vec2 = pg.math.Vector2 # 2차원 벡터
CELL_CENTER = vec2(CELL_SIZE / 2) # [CELL_SIZE / 2, CELL_SIZE / 2] 가 대입됨

class TicTacToe: # 틱택토 게임 클래스
    def __init__(self, game): # 초기화 함수에서 게임 클래스 객체를 매개변수로 사용
        self.game = game # 받아온 게임 클래스 객체를 틱택토 객체의 변수에 대입
        # 필드와 o,x 이미지 경로 및 크기 지정
        self.field_image = self.get_scaled_image(path = 'img/field.png', res = [WIN_SIZE] * 2)
        self.o_image = self.get_scaled_image(path = 'img/o.png', res = [CELL_SIZE] * 2)
        self.x_image = self.get_scaled_image(path = 'img/x.png', res = [CELL_SIZE] * 2)
        
        # 모든 칸을 무한으로 설정
        self.game_array = [[INF, INF, INF],
                           [INF, INF, INF],
                           [INF, INF, INF]]
        
        self.player = randint(0, 1) # 선공 플레이어 랜덤으로 선택
        
        # 틱택토 필드를 좌표로 표현
        # (0, 0) (0, 1) (0, 2)
        # (1, 0) (1, 1) (1, 2)
        # (2, 0) (2, 1) (2, 2)
        
        # 좌표를 기반으로한 승리의 조건
        self.line_indices_array = [[(0, 0), (0, 1), (0, 2)],
                                   [(1, 0), (1, 1), (1, 2)],
                                   [(2, 0), (2, 1), (2, 2)],
                                   [(0, 0), (1, 0), (2, 0)],
                                   [(0, 1), (1, 1), (2, 1)],
                                   [(0, 2), (1, 2), (2, 2)],
                                   [(0, 0), (1, 1), (2, 2)],
                                   [(0, 2), (1, 1), (2, 0)]]
        
        self.winner = None # 승자 None 값으로 초기화
        self.game_steps = 0 # 게임 진행도
        self.font = pg.font.SysFont('Verdana', CELL_SIZE // 4, True) # 폰트 설정
    
    def check_winner(self): # 승자가 존재하는지 체크하는 함수
        for line_indices in self.line_indices_array:
            sum_line = sum([self.game_array[i][j] for i, j in line_indices])# 위 아래 대각선으로 접근해 총합이 0 또는 3이면 승자가 발생한 상황입니다.        
            if sum_line in {0, 3}: # 한 줄의 총합이 0 이나 3인지를 살펴봄
                self.winner = 'XO'[sum_line == 0] # 각 줄의 값의 총합이 0이면 O가 승리 3이면 X가 승리
                self.winner_line = [vec2(line_indices[0][::-1]) * CELL_SIZE + CELL_CENTER,
                                    vec2(line_indices[2][::-1]) * CELL_SIZE + CELL_CENTER]
                # pygame에서 사용하는 좌표에 알맞도록 직접 설정한 좌표를 역으로 사용함 [::-1] 이용
                
    def run_game_process(self): 
        current_cell = vec2(pg.mouse.get_pos()) // CELL_SIZE # 어느 칸을 클릭하는지 좌표 계산
        col, row = map(int, current_cell) # 백터값을 정수형으로 각각 받아옴
        left_click = pg.mouse.get_pressed()[0]
        # 마우스 클릭을 안하고있으면 pg.mouse.get_pressed()의 반환값은 (False, False, False)이다.
        # 좌클릭을 하면 (True, False, False) 을 반환한다.
        # 우클릭을 하면 (False, False, True) 을 반환한다.
        # 휠클릭을 하면 (False, True, False) 을 반환한다.
                
        if left_click and self.game_array[row][col] == INF and not self.winner: # 좌클릭을 하고 아직 클릭되지 않은 칸을 클릭했고 승자가 없을 때 아래 동작을 실행
            self.game_array[row][col] = self.player # O,X 플레이어가 클릭했다는 표시를 위해 값을 대입
            self.player = not self.player # 상대방 차례로 변경
            self.game_steps += 1 # 경기 진행 횟수 증가
            self.check_winner() # 승자가 존재하는지 검사
            
    def draw_objects(self): # o 또는 x 의 이미지를 그리는 함수
        for y, row in enumerate(self.game_array): # 반복 첫회에 y 에는 0, row 에는 [INF or 0 or 1,INF or 0 or 1,INF or 0 or 1] 대입된다.
            for x, obj in enumerate(row): # 반복 첫회에 x 에는 0, obj 에는 INF or 0 or 1 대입된다.
                if obj != INF: # obj가 INF가 아니라면 클릭이 발생한 칸이므로 o 또는 x 이미지를 그려줘야한다.
                    self.game.screen.blit(self.x_image if obj else self.o_image,  vec2(x, y) * CELL_SIZE)
                    # obj가 1이면 x 이미지를, 0이면 o 이미지를 그려준다.
    
    def draw_winner(self): # 승자가 생기면 승리를 알리는 메시지와 승리한 줄을 표시함
        if self.winner:
            pg.draw.line(self.game.screen, 'purple', *self.winner_line, CELL_SIZE // 10)
            label = self.font.render(f'Player "{self.winner}" wins!', True, 'white', 'black')
            self.game.screen.blit(label, (WIN_SIZE // 2 - label.get_width() // 2, WIN_SIZE // 4))
            
    def draw(self): # 화면에 그려줄 이미지들과 멘트를 모아둔 함수
        self.game.screen.blit(self.field_image, (0,0)) # 필드 이미지 그림
        self.draw_objects()
        self.draw_winner()
        
    @staticmethod # 정적메서드는 객체를 통하지 않고 클래스에서 바로 호출이 가능하다.
    def get_scaled_image(path, res): # 이미지를 불러오면서 크기를 같이 지정하도록 만든 함수
        img = pg.image.load(path)
        return pg.transform.smoothscale(img, res) # 지정한 크기로 조정된 이미지를 반환
    
    def print_caption(self): # 창의 제목을 변경하는 함수
        pg.display.set_caption(f'Player "{"OX"[self.player]}" turn!') # 플레이어 순서에 맞게 제목 변경
        if self.winner: # 승자가 존재할 때 창 제목
            pg.display.set_caption(f'Player "{self.winner}" wins! Press Space to Restart')
        elif self.game_steps == 9: # 무승부일 때 창 제목
            pg.display.set_caption(f'Draw! Press Space to Restart')

    def run(self): # 틱택톡 게임 실행
        self.print_caption()
        self.draw()
        self.run_game_process()
        
class Game:
    def __init__(self): # 초기화
        pg.init()
        self.screen = pg.display.set_mode([WIN_SIZE] * 2) # 게임 화면의 크기 조절
        self.clock = pg.time.Clock() # 프레임 조절을 위해 생성하는 객체
        self.tic_tac_toe = TicTacToe(self) # 틱택토 클래스 객체 생성
    
    def new_game(self): # 새로운 게임 시작을 위한 함수
        self.tic_tac_toe = TicTacToe(self) # 틱택토 객체 새로 생성
    
    def check_events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT: # 창 종료 버튼 (x버튼) 눌럿을 때 발생되는 이벤트
                pg.quit()
                sys.exit()
            if event.type == pg.KEYDOWN: # 키를 눌렀을 때 발생하는 이벤트
                if event.key == pg.K_SPACE: # 스페이스바를 누른경우
                    self.new_game()
    
    def run(self): # 게임의 메인루프
        while True:
            self.tic_tac_toe.run() # 틱택토 게임 실행
            self.check_events() # 지정해둔 이벤트가 발생하는지 감시
            pg.display.update() # 게임 화면 갱신
            self.clock.tick(60) # fps를 60 언저리로 맞춤

if __name__ == '__main__':
    game = Game() # 게임 객체 생성
    game.run() # 게임 실행

실행 화면

 

상황 별 화면을 첨부하였습니다.


첫 화면

 

first-screen
첫 화면


무승부 화면

 

draw
무승부


승리 화면

 

win
승리



게임 조작법

 

조작법 동작
마우스 좌클릭 해당 차례 플레이어의 마크가 클릭된 칸에 표시됨
SPACE 키 게임 재시작

 


사용된 이미지

 

field
필드 이미지

 

o-image
o 마크

 

x-image
x 마크


여기까지 파이썬으로 게임 만들기 틱택토 편이었습니다.

 

코드에 궁금하신부분이 있으시면 댓글로 남겨주세요. 성심성의껏 답변드리겠습니다.

 

pygame으로 이미 있는 게임을 구현하거나 새로운 게임을 생각해 코드로 바꿔보는 과정을 반복하다 보면

 

사용법을 금방 익히실 수 있을 겁니다.

 

다음에도 재미있는 게임을 구현해서 포스팅하도록 하겠습니다.

 

감사합니다.

반응형