유치한 게임

[파이썬 간단한 게임 만들기] 9. 오목 아니고 4목

ai-creator 2021. 5. 8. 13:44
반응형

유치한 게임에 오신 것을 환영합니다.

이번에는 4목 게임을 만들어 보겠습니다.

아래와 같은 순서로 배워보겠습니다.

 

1. 목표

2. 사전 준비

3. 소스 코드 (전체)

4. 사전 지식

5. 구현 순서

6. 정리

 

 


1. 목표

이번 장에서는 4목 게임을 만들어 보도록 하겠습니다.

 

이번에 만들 게임은 4목 게임입니다. 4목 게임의 규칙은 다음과 같습니다.

오목과 비슷한 형태로 진행되며, 2명의 플레이어가 번갈아 가며 자신의 돌을 놓습니다. 하지만 오목과 달리 2차원의 공간에 돌을 놓는 형태가 아니라 3차원 형태로 공이 중력의 영향을 받아 각 행에 하나씩 차곡차곡 쌓이게 되는 형태입니다. 따라서 열별로 자신의 돌을 잘 놓아 상대방의 연속 배치를 막으며, 자신의 돌이 4개가 연속되게 놓는 것이 목표입니다.

 

 

[그림 9-1] 사목 게임

이번 장에서는 이 4목 게임을 구현 해보도록 하겠습니다.

 

2. 사전 준비

- 게임판 구상

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import pygame # 1. pygame 선언
 
pygame.init() # 2. pygame 초기화
 
# 3. pygame에 사용되는 전역변수 선언
 
BLUE = (00255)
BLACK = (000)
RED = (25500)
YELLOW = (2552550)
large_font = pygame.font.SysFont(None72)
CELL_SIZE = 100
COLUMN_COUNT = 7
ROW_COUNT = 6
P1_WIN = 1
P2_WIN = 2
DRAW = 3
SCREEN_WIDTH = 700
SCREEN_HEIGHT = 700
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) 
 
grid = np.zeros((ROW_COUNT, COLUMN_COUNT))
mouse_x, mouse_y = pygame.mouse.get_pos()
 
clock= pygame.time.Clock()
 
# 4. pygame 무한루프
 
def runGame():
    global done
    while not done:
        clock.tick(10)
        screen.fill(WHITE)
 
runGame()
pygame.quit()
 
cs

 

게임을 진행할 화면을 다음과 같이 설정하도록 하겠습니다.

7~10번 라인 :  게임에 사용되는 색상 RGB 값

11번 라인(게임 폰트) : 게임 내부에서 게임 결과를 표시할 폰트 설정

12번 라인 : 각 셀의 크기

13~14번 라인 : 4목 게임판의 가로열과 세로행의 갯수

15~17번 라인 : 게임 종료 플래그

18~19번 라인 : 게임판의 크기(700X700)

22번 라인 : numpy의 zeros() 함수를 이용하여 6X7의 2차원 배열을 생성(전부 0으로 초기화)

23번 라인 : 마우스로 돌을 놓을 위치를 잡기 위한 초기 위치 지정

 

 

3. 소스 코드 (전체)

파일명 : connectFour.py

drive.google.com/drive/folders/1P7-ibtlJKUEet1lbqkSAg7N8RyxOza_o?usp=sharing

 

ai-creator 공유폴더(유치한게임) - Google Drive

이 폴더에 파일이 없습니다.이 폴더에 파일을 추가하려면 로그인하세요.

drive.google.com

 

4. 사전 지식

1) 4목의 게임승리 조건

4목의 승리조건은 같은 색의 돌이 연속하여 4개가 위치하게 되는 것입니다. 따라서 게임의 승리조건에 대하여 수직, 수평, 대각선(/, \) 방향의 모든 연속된 돌을 검증하는 것이 필요합니다. 

따라서 각 게임루프마다 매번 승리 조건을 탐색해야 합니다. 이 승리조건에 대한 검증을 함수를 통해 구현해보도록 하겠습니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def is_winner(grid, piece):
    for column_index in range(COLUMN_COUNT - 3):
        for row_index in range(ROW_COUNT):
            if grid[row_index][column_index] == piece and grid[row_index][column_index + 1== piece and grid[row_index][column_index + 2== piece and grid[row_index][column_index + 3== piece:
                return True
 
    for column_index in range(COLUMN_COUNT):
        for row_index in range(ROW_COUNT - 3):
            if grid[row_index][column_index] == piece and grid[row_index + 1][column_index] == piece and grid[row_index + 2][column_index] == piece and grid[row_index + 3][column_index] == piece:
                return True
 
    for column_index in range(COLUMN_COUNT - 3):
        for row_index in range(ROW_COUNT - 3):
            if grid[row_index][column_index] == piece and grid[row_index + 1][column_index + 1== piece and grid[row_index + 2][column_index + 2== piece and grid[row_index + 3][column_index + 3== piece:
                return True
 
    for column_index in range(COLUMN_COUNT - 3):
        for row_index in range(3, ROW_COUNT):
            if grid[row_index][column_index] == piece and grid[row_index - 1][column_index + 1== piece and grid[row_index - 2][column_index + 2== piece and grid[row_index - 3][column_index + 3== piece:
                return True
cs

먼저 공통적인 코드 구성부터 살펴보도록 하겠습니다.

각 4개의 For문이 크게 존재하며 각가 수직, 수평, 대각선(/ , \)의 연속 검증을 하는 코드입니다.

column_index와 row_index를 활용해 모든 셀을 돌아가며 검증을 하게 됩니다. 하지만, 각각의 셀을 모두 찾는 것이 아니라 구조적으로 출발점이 될 수 있는 위치에서만 검증을 하도록 하고 있습니다.

예를 들어 수평 검증을 한다고 하면 총 7개의 열 중 좌측의 4개의 열만 가로로 연속하는지 검증하면 됩니다.

 

 

[그림 9-2] 4목의 승리 조건

다음으로 for문 안에있는 검증하는 코드는 다음과 같습니다.

먼저 인자로 들어온 piece는 각 돌의 색깔을 의미하며, 시작점 부터 각 인덱스에 1씩 추가해 4개의 모든 돌이 일치하는지 AND를 활용하여 조건문을 돌게 됩니다. 이때 하나라도 일치하지 않는다면 혹은 돌이 존재하지 않는 다면 조건에 부합하지 않게 됩니다.

 

1~5번 라인 : 수평 검증(-)

7~10번 라인 : 수직 검증(ㅣ)

12~15번 라인 :  대각선 검증(/)

17~20번 라인 : 대각선 검증(\)

5. 구현 순서

구현 순서는 다음과 같습니다.

Step1 마우스 이벤트 처리(돌 놓기)
Step2 화면 그리기

Step1) 마우스 이벤트 처리(돌 놓기)

 

먼저 돌을 놓기 전 해당 돌을 놓을 수 있는지에 대한 검증을 하는 함수부터 선언하도록 하겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def is_free_column_index(grid, column_index):
    if column_index < 0 or column_index > COLUMN_COUNT - 1:
        return False
 
    return grid[ROW_COUNT - 1][column_index] == 0
 
def get_free_row_index(grid, column_index):
    for row_index in range(ROW_COUNT):
        if grid[row_index][column_index] == 0:
            return row_index
 
def is_grid_full(count):
    if count == 42:
        return True
    return  False
cs

is_free_column_index : column_index를 활용하여 해당 열에 돌이 들어갈 수 있는지를 판별합니다. 만약 열의 크기인 7을 초과하여 돌을 놓게 된다면 해당 돌은 놓을 수 없도록 False를 리턴해줍니다.

get_free_row_index : 해당하는 좌표에 돌이 없는지 판별하는 함수 입니다. 이후 마우스 이벤트 처리 부분에서 만약 빨간 돌이 놓이게 되면 해당 위치의 값은 1, 노란 돌이 놓이게 되면 해당 위치의 값을 2로 변경해주기 때문에 해당 값이 0이면 비어있다고 판단하여 돌을 놓을 수 있게 row_index를 아래부터 검증하여 리턴합니다.(최하단 부터 차곡차곡)

is_grid_full : 돌을 놓을 때마다 count를 추가하여 이 count가 42값이 되게 되면 draw 플래그로 변경하도록 True, False를 리턴합니다.

 

다음은 게임루프에서 처리입니다.

이번에는 게임 진행을 마우스를 이용하여 조금 특이한 방식으로 진행해보도록 하겠습니다. 

이전 장에서 학습한 것 처럼 pygame에서 event가 발생했을 때, event.pos()를 통해 마우스의 해당 좌표를 구할 수 있었습니다. 이번에는 마우스의 x좌표만을 이용하여 게임을 진행합니다.

이유는 앞서 설명한 것 처럼 오목과 같이 x, y축을 기준으로 빈공간에 돌을 아무데나 넣을 수 있는 것이 아니라, y축에 돌이 차곡차곡 쌓이는 구조이기 때문에 x축만 사용하여 돌을 놓을 수 있는 공간을 좌우로만 설정하도록 하겠습니다.

 

[그림 9-3] 돌의 가동범위

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
def runGame():
    game_over = 0
    turn = 0
    count = 0
 
    while True
        clock.tick(30
        screen.fill(BLACK) 
 
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                break
            elif event.type == pygame.MOUSEBUTTONDOWN:
                column_index = event.pos[0// CELL_SIZE
                count += 1
 
                if turn == 0:
                    if is_free_column_index(grid, column_index):
                        row_index = get_free_row_index(grid, column_index)
                        grid[row_index][column_index] = 1
 
                        if is_winner(grid, 1):
                            game_over = P1_WIN
                        elif is_grid_full(count):
                            game_over = DRAW
 
                        turn += 1
                        turn %= 2
 
                elif turn == 1:    
                    if is_free_column_index(grid, column_index):
                        row_index = get_free_row_index(grid, column_index)
                        grid[row_index][column_index] = 2
 
                        if is_winner(grid, 2):
                            game_over = P2_WIN
                        elif is_grid_full(count):
                            game_over = DRAW
 
                        turn += 1
                        turn %= 2
cs

 

먼저 게임 종료 플래그로 사용할 변수와 무승부를 검증하기 위한 count 변수 마지막으로 빨간돌, 노란돌의 turn을 체크할 변수를 선언해주도록 합니다.

13~15번 라인은 돌을 놓을 좌표를 정하는 부분입니다. 먼저 마우스 클릭이벤트가 발생하게 되면 event.pos()를 이용하여 x좌표만을 가져오며 count 값을 증가시켜주도록 합니다.

17~28번 라인은 빨간돌의 차례를 진행하는 부분입니다. turn 변수를 활용하여 0, 1값을 번갈아가며 사용하도록 하기에 0일 때는 빨간돌 1일때는 노란돌의 차례를 진행하도록 합니다. 먼저 앞서 선언한 is_free_column_index() 함수를 이용하여 검증을 한 뒤, get_free_row_index()함수를 호출하여 row_index값을 가져옵니다.

이후 grid의 해당 좌표 값에 앞서 설명한 것 처럼 해당하는 돌의 값(빨 : 1, 노 : 2)으로 변경하도록 합니다.

다음으로 앞서 선언한 is_winner() 함수를 이용하여 해당 grid에서 발생하는 모든 승리조건에 대한 검증을 진행한 뒤, 만약 승리가 아니라면 is_full_grid() 함수를 통해 무승부를 체크합니다.

마지막으로 turn을 변경하도록 합니다.

30~41번 라인은 노란돌의 차례를 진행하는 부분입니다. 앞의 빨간돌의 차례와 동일하게 진행되며 grid의 해당 위치 값을 2로 바꿔주는 부분에만 차이가 있습니다.

 

 

Step2) 화면 그리기

 

이번에는 앞의 코드를 기반으로 게임판을 출력해보도록 하겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
width = 700
pygame.draw.rect(screen, BLACK, pygame.Rect(00, width, CELL_SIZE))
if turn == 0:
    pygame.draw.circle(screen, RED, (mouse_x, CELL_SIZE // 2), CELL_SIZE // 2 - 5)
else
    pygame.draw.circle(screen, YELLOW, (mouse_x, CELL_SIZE // 2), CELL_SIZE // 2 - 5
 
for column_index in range(COLUMN_COUNT):
    for row_index in range(ROW_COUNT):
        pygame.draw.rect(screen, BLUE, pygame.Rect(column_index * CELL_SIZE, row_index * CELL_SIZE + CELL_SIZE, CELL_SIZE, CELL_SIZE))
        pygame.draw.circle(screen, BLACK, (column_index * CELL_SIZE + CELL_SIZE // 2, row_index * CELL_SIZE + CELL_SIZE + CELL_SIZE // 2), CELL_SIZE // 2 - 5
 
for column_index in range(COLUMN_COUNT):
    for row_index in range(ROW_COUNT):   
        if grid[row_index][column_index] == 1:
            height = CELL_SIZE * (ROW_COUNT + 1)
            pygame.draw.circle(screen, RED, (column_index * CELL_SIZE + CELL_SIZE // 2, height - (row_index * CELL_SIZE + CELL_SIZE // 2)), CELL_SIZE // 2 - 5)
        elif grid[row_index][column_index] == 2
            height = CELL_SIZE * (ROW_COUNT + 1)
            pygame.draw.circle(screen, YELLOW, (column_index * CELL_SIZE + CELL_SIZE // 2, height - (row_index * CELL_SIZE + CELL_SIZE // 2)), CELL_SIZE // 2 - 5)  
 
if game_over > 0
    if game_over == P1_WIN:
        p1_win_image = large_font.render('Red (1) Win'True, RED)
        screen.blit(p1_win_image, p1_win_image.get_rect(centerx=SCREEN_WIDTH // 2, centery=SCREEN_HEIGHT // 2))
    elif game_over == P2_WIN:
        p2_win_image = large_font.render('Yellow (2) Win'True, RED)
        screen.blit(p2_win_image, p2_win_image.get_rect(centerx=SCREEN_WIDTH // 2, centery=SCREEN_HEIGHT // 2))
    else:
        draw_image = large_font.render('Draw'True, RED)
        screen.blit(draw_image, draw_image.get_rect(centerx=SCREEN_WIDTH // 2, centery=SCREEN_HEIGHT // 2))
 
pygame.display.update()
cs

 

1,2번 라인은 돌을 놓기 전 마우스를 기반으로 미리 위치를 볼 수 있도록 게임타일 위에 직사각형의 검은색 타일을 출력하는 코드입니다.

3~6번 라인은 위에서 생성한 타일 위에 각 턴에 맞추어 색깔별로 미리 보는 돌을 출력하는 라인입니다. 해당 돌은 circle() 함수를 이용해서 출력되며 이 돌은 게임판에 놓이는 것이 아닌 놓이기 전 미리보는 용도로 마우스 클릭이벤트 전에는 이동만 하는 용도입니다.

8~11번 라인은 게임판을 생성하는 코드입니다.

먼저 각 셀을 돌면서 정사각형의 파란색 도형을 그려줍니다. 이 정사각형의 크기는 셀의 사이즈를 기반으로 위치 인덱스(row, column) 에 맞추어 생성하도록 합니다. 

다음으로는 앞서 그린 파란색 정사각형 도형과 곂쳐 circle()을 이용한 원을 그려주어, 마치 해당 칸안에 동그란 원이 비어있는 것처럼 만들어 주도록합니다.

13~20번 라인은 앞서 마우스 이벤트 처리를 통해 처리한 좌표값을 기반으로 1일 때는 빨간색 원을, 2일 때는 노란색 원을 그려주는데, 원도 동일하게 모든 셀을 돌며 각각의 색깔 도형을 그려줍니다.

22~31번 라인은 게임 종료플래그를 기반으로 종료 결과를 화면에 출력하는 부분입니다.

초기에 선언한 large_font를 기반으로 메세지를 작성하며, blit() 함수를 이용해 화면 정중앙에 메세지를 그려줍니다.

각각 red 승리, yellow 승리, 무승부를 출력합니다.

 

[그림 9-4] 게임 종료

6. 정리

이번 장에서는 앞서 배운 pygame의 기본 구조와 게임 루프, 이벤트 처리를 기반으로 4목 게임을 구현 해봤습니다.

 

먼저, 게임승리 조건에 대한 처리를 기반으로 게임을 진행할 수 있도록 사전학습을 한 뒤 진행을 했으며,

Step 1) 마우스 이벤트 처리(돌 놓기) >  마우스 좌클릭을 기반으로 각 위치에 대한 조건 처리 후 게임 진행

Step 2) 화면 그리기 -> 사각형과 원을 곂쳐 그려 새로운 도형그리기 및 마우스 미리보기 출력

을 배웠습니다.

 

이번 게임 생성을 기반으로 조금 더 쉬운 오목 게임을 직접 구현해보는 것은 어떨까요?^^

 

ㅁ 참고

토닥토닥 파이썬

 

 


도움이 되셨다면, 좋아요 / 구독 버튼 눌러주세요~

 

저작물의 저작권은 작성자에게 있습니다.
공유는 자유롭게 하시되 댓글 남겨주세요~
상업적 용도로는 무단 사용을 금지합니다.
끝까지 읽어주셔서 감사합니다^^

 

반응형