유치한 게임

[파이썬 간단한 게임 만들기] 8. 지뢰찾기

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

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

이번에는 지뢰찾기 게임을 만들어 보겠습니다.

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

 

1. 목표

2. 사전 준비

3. 소스 코드 (전체)

4. 사전 지식

5. 구현 순서

6. 정리

 

 


1. 목표

이번 장에서는 지뢰찾기 게임을 만들어 보도록 하겠습니다.

 

이번에 만들 게임은 지뢰찾기입니다. 지뢰찾기 게임의 규칙은 다음과 같습니다.

게임판 내부에 각 셀이 존재하며 이 셀은 3가지의 타입으로 되어 있습니다. 셀을 누르게 되면 해당하는 셀에 대한 타입이 보이게 되며 만약, 숫자 셀이나 아무것도 없는 셀을 클릭하게 되면 게임이 계속 진행되지만 지뢰가 있는 셀을 누르게 되면 게임이 끝나게 됩니다.

따라서 지뢰가 없는 모든 셀을 활성화 시켜 모든 지뢰를 찾으면 되는 게임입니다. 

숫자가 들어있는 셀은 해당 셀을 반경으로 상하좌우 대각선까지 총 8칸의 셀에 존재하는 지뢰의 갯수를 의미하며 이 숫자 셀을 잘 조합하여 지뢰의 위치를 파악하는 것이 중요합니다.

 

[그림 8-1] 지뢰찾기 게임

 

이번 장에서는 이 지뢰찾기 게임을 구현 해보도록 하겠습니다.

 

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
import pygame # 1. pygame 선언
 
pygame.init() # 2. pygame 초기화
 
# 3. pygame에 사용되는 전역변수 선언
 
BLACK = (000)
RED = (25500)
GRAY = (128128128)
WHITE = (255255255)
YELLOW = (2552550)
large_font = pygame.font.SysFont(None72)
small_font = pygame.font.SysFont(None36)
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
 
CELL_SIZE = 50
COLUMN_COUNT = SCREEN_WIDTH // CELL_SIZE
ROW_COUNT = SCREEN_HEIGHT // CELL_SIZE
 
clock= pygame.time.Clock()
 
# 4. pygame 무한루프
 
def runGame():
    global done
    while not done:
        clock.tick(10)
        screen.fill(WHITE)
 
runGame()
pygame.quit()
cs

 

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

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

12,13번 라인(게임 폰트) : 게임 내부에서 점수를 표시할 폰트 설정

14,15번 라인(화면 크기) : 넓이(width) - 800, 높이(height) - 600/ 800x600

18~20번 라인 : 지뢰가 들어갈 각 셀의 사이즈 및 갯수

 

 

3. 소스 코드 (전체)

파일명 : minesweeper.py

 

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

 

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

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

drive.google.com

4. 사전 지식

1) 지뢰찾기 게임에 필요한 요소 정의

지뢰찾기 게임에서 가장 중요한 것은 셀을 배치하고 지뢰가 들어갈 셀을 지정하는 것입니다.

게임 조작 방식은 오로지 마우스를 이용하기 때문에 저희가 가장 중요시 할 부분은 셀을 관리하는 것입니다.

 

먼저 각 셀을 관리할 grid라는 2차원 리스트를 만들고 지뢰를 배치하는 부분을 먼저 보도록 하겠습니다.

 

1
2
3
4
5
6
7
8
9
10
grid = [[{'mine'False'open'False'mine_count_around'0'flag'Falsefor _ in range(COLUMN_COUNT)] for _ in range(ROW_COUNT)]
MINE_COUNT = 15
for _ in range(MINE_COUNT):
    while True:
        column_index = random.randint(0, COLUMN_COUNT - 1)
        row_index = random.randint(0, ROW_COUNT - 1)
        tile = grid[row_index][column_index]
        if not tile['mine']:
            tile['mine'= True 
            break
cs

 

 

1번 라인에서 먼저 각 리스트에 들어갈 하나의 요소는 mine, open, mine_count_around, flag 로 구성되어 있습니다.

mine - 지뢰의 여부

open - 활성화 여부

mine_count_around - 주변의 지뢰 갯수

flag - flag 활성화 여부(지뢰라고 체크)

각 요소들을 각 열과 행에 맞게 반복문을 통해 구성을 해주도록 합니다.

2번 라인은 지뢰의 총 갯수를 의미합니다.

3번 라인부터는 반복문을 돌며 랜덤한 행,열에 지뢰를 설정하도록 하는데, 지뢰가 중복되지 않도록 먼저 지뢰가 있는지 여부를 확인한 후에 지뢰를 배치하도록 하겠습니다.

 

 

2) 마우스 클릭을 통한 판정

각 셀을 마우스로 클릭했을 때, 앞서 설명드린 게임 규칙처럼 총 3개의 액션을 취할 수 있습니다. 

1. 숫자 셀(주변의 지뢰 갯수)

2. 빈 셀

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
def in_bound(column_index, row_index):
    if (0 <= column_index < COLUMN_COUNT and 0 <= row_index < ROW_COUNT):
        return True
    else:
        return False
 
def open_tile(column_index, row_index): 
    if not in_bound(column_index, row_index):
        return
 
    tile = grid[row_index][column_index]
    if not tile['open']:
        tile['open'= True
    else:    
        return
 
    if tile['mine']:
        return
 
    mine_count_around = get_mine_count_around(column_index, row_index)
    if mine_count_around > 0:
        tile['mine_count_around'= mine_count_around
    else:
        for dc, dr in [(01), (0-1), (10), (-10), (11), (1-1), (-11), (-1-1)]:
            column_index_around, row_index_around = (column_index + dc, row_index + dr)
            open_tile(column_index_around, row_index_around)
 
def get_mine_count_around(column_index, row_index):
    count = 0
 
    for dc, dr in [(01), (0-1), (10), (-10), (11), (1-1), (-11), (-1-1)]:
        column_index_around, row_index_around = (column_index + dc, row_index + dr)
        if in_bound(column_index_around, row_index_around) and grid[row_index_around][column_index_around]['mine']:
            count += 1
    return count
cs

1~5번 라인(in_bound) : 접근 가능한 셀인지 판단 

 

7~26번 라인(open_tile) : 각 타일을 오픈

만약 타일이 열려있지 않으면 타일의 open을 true로 변경(지뢰 여부 상관없이)

다음으로 주변의 지뢰의 갯수를 파악( get_mine_count_around 함수 실행) 후 주변에 빈셀들을 전부 오픈

 

28~35번 라인(get_mine_count_around) : 주변의 지뢰 갯수를 리턴

([(01), (0, -1), (10), (-10), (11), (1, -1), (-11), (-1, -1)])의 모든 값들을 돌며 체크해서 지뢰가 몇개 있는지 카운트 해서 결과를 러턴

 

이렇게 총 3개의 함수를 이용해서 타일을 판별하고 오픈하도록 합니다.

 

 

[그림 8-2] 3가지 셀 타입.png

 

 

 

5. 구현 순서

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

Step1 마우스 이벤트 처리(셀 오픈)
Step2 각 타일 출력

지뢰찾기 게임은 게임의 로직을 이해하는 것이 중요합니다. 각 셀에 대한 반응이 얽혀 재귀함수도 이용되고 여러 반응이 생길 수 있기 때문에 하나씩 천천히 배워보도록 하겠습니다.

 

Step1) 마우스 이벤트 처리(셀 오픈)

 

먼저 마우스 이벤트 처리를 구현해보도록 하겠습니다. 이전까지는 마우스를 사용하는 게임은 좌클릭만 이용하였지만, 지뢰찾기 게임은 지뢰로 의심되는 타일을 오픈하지 않고 flag로 체크를 해야하기 때문에 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
38
39
40
def runGame():
    SUCCESS = 1
    FAILURE = 2
    game_over = 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
                row_index = event.pos[1// CELL_SIZE
                if event.button == 1:
                    if in_bound(column_index, row_index):
                        tile = grid[row_index][column_index]
                        if tile['mine']:
                            tile['open'= True
                            game_over = FAILURE
                        else:
                            open_tile(column_index, row_index)
                elif event.button == 3:
                    if in_bound(column_index, row_index):
                        tile = grid[row_index][column_index]
                        if not tile['flag']:
                            tile['flag'= True
                        else:
                            tile['flag'= False
 
                        success = True
                        for row_index in range(ROW_COUNT):
                            for column_index in range(COLUMN_COUNT):
                                tile = grid[row_index][column_index]
                                if tile['mine'and not tile['flag']:
                                    success = False
                                    break
                        if success:
                            game_over = SUCCESS
cs

 

2~4번 라인은 이전 장에서 배운 플래그와 동일하게 게임 종료와 게임 클리어에 대한 플래그로 사용하여 마지막에 텍스트를 출력하도록 하는 변수입니다.

13번 라인은 MOUSEBUTTONDOWN으로 마우스 클릭 이벤트를 캐치합니다.

14,15번 라인에서 마우스 클릭이 발생한 위치를 기반으로 셀의 행열 인덱스를 잡도록하겠습니다.

16~23번 라인은 마우스 좌클릭에 대한 이벤트 처리입니다. 마우스 좌클릭을 하게 되면 event.button 값이 1로 설정이 됩니다. 따라서 이를 이용하여 죄클릭 이벤트를 검증하여 만약 지뢰 타일이 open되어 있다면 game_over 플래그를 설정하여 게임을 종료하도록 합니다.

만약 지뢰 타일이 open 되어 있는 것이 아니라면, 이전에 작성한 open_tile 함수를 통해 타일을 열어주도록 하겠습니다.

24~40번 라인은 마우스 우클릭에 대한 이벤트 처리입니다. 마우스 우클릭을 하게 되면 event.button 값이 3으로 설정됩니다.

먼저 플래그는 지뢰의 여부, 빈 셀, 숫자 셀에 상관없이 모두 플래그 처리가 가능함으로 기존 tile['flag']의 값에 따라 새로운 플래그를 설정하주도록 합니다. 쉽게 말해 플래그가 있는 타일이면 플래그를 없애고 플래그가 없는 타일이면 플래그를 활성화 하도록 합니다.

다음은, 모든 셀을 돌면서 지뢰 셀에 플래그가 대응하여 활성화 되어 있다면 game_over 플래그를 성공으로 처리하여 게임 승리 조건으로 끝내도록 설정 해줍니다. 만일 하나라도 대응하지 않는다면 게임 성공 플래그는 활성화 되지 않습니다.

 

 

 

Step2) 각 타일 출력

 

이번에는 각 타일의 open에 대한 정보를 기반으로 모든 타일을 출력하도록 하겠습니다.

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
for column_index in range(COLUMN_COUNT):
    for row_index in range(ROW_COUNT):
        tile = grid[row_index][column_index]
        if tile['mine_count_around']:
            mine_count_around_image = small_font.render('{}'.format(tile['mine_count_around']), True, YELLOW)
            screen.blit(mine_count_around_image, mine_count_around_image.get_rect(centerx=column_index * CELL_SIZE + CELL_SIZE // 2, centery=row_index * CELL_SIZE + CELL_SIZE // 2))
        if tile['mine']: 
            mine_image = small_font.render('x'True, RED)
            screen.blit(mine_image, mine_image.get_rect(centerx=column_index * CELL_SIZE + CELL_SIZE // 2, centery=row_index * CELL_SIZE + CELL_SIZE // 2)) #지뢰 설치
        if not tile['open']:
            pygame.draw.rect(screen, GRAY, pygame.Rect(column_index * CELL_SIZE, row_index * CELL_SIZE, CELL_SIZE, CELL_SIZE)) #커버
        if tile['flag']: 
            v_image = small_font.render('v'True, WHITE)
            screen.blit(v_image, (column_index * CELL_SIZE + 10, row_index * CELL_SIZE + 10))        
        pygame.draw.rect(screen, WHITE, pygame.Rect(column_index * CELL_SIZE, row_index * CELL_SIZE, CELL_SIZE, CELL_SIZE), 1)
 
if game_over > 0:
    if game_over == SUCCESS:
        success_image = large_font.render('성공'True, RED)
        screen.blit(success_image, success_image.get_rect(centerx=SCREEN_WIDTH // 2, centery=SCREEN_HEIGHT // 2))
    elif game_over == FAILURE:
        failure_image = large_font.render('실패'True, RED)
        screen.blit(failure_image, failure_image.get_rect(centerx=SCREEN_WIDTH // 2, centery=SCREEN_HEIGHT // 2))
 
pygame.display.update()
cs

 

1,2번 라인은 이중 반복문을 통해 모든 타일을 돌며 각각 출력을 하도록 합니다.

다음으로는 grid로 설정한 tile의 모든 요소(mine_count_around, mine, open)을 검증하여 화면에 출력하도록 합니다.

겹치는 조건이 발생할 수도 있기 때문에 우선순위를 바탕으로 숫자 > 지뢰 > 오픈여부 > 플래그 순으로 조건문을 돌도록 합니다.

각 조건에 대해서 검증을 하더라도 tile['open']이 활성화 되지 않았다면 회색 타일로 덮어 해당 타일이 어떤 타일인지 보이지 않도록 합니다.

4~6번 라인은 숫자 타일에 대한 정보입니다. 숫자 타일은 앞서 계산한 값을 기반으로 타일에 숫자를 출력하도록 blit() 함수를 사용하도록 합니다.

7~9번 라인은 지뢰 타일에 대한 정보입니다. 지뢰 타일은 숫자 타일과 동일하게 blit() 함수를 사용하여 x라는 문자를 출력해줍니다.

10,11번 라인은 타일이 오픈되지 않았을 때 즉, 기본 타일에 대해 출력을 하며, rect() 함수를 사용하여 빈 회색 타일을 출력하도록 합니다.

12~14번 라인은 플래그 활성화 여부에 따라, blit() 함수를 이용해 v라는 문자를 출력해줍니다.

 

17~23번 라인은 이전 장에서 학습한 game_over 플래그에 따른 게임 종료 메세지를 출력하는 코드입니다. 만약 SUCCESS로 활성화 되어 있다면 '성공'이라는 메세지를, FAILURE로 활성화 되어 있다면 '실패'라는 메세지를 출력하도록 합니다.

 

[그림 8-3] 게임 완성 화면

6. 정리

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

 

먼저, 게임에 필요한 요소 정의를 하고 타일 오픈 상황 처리를 통해 게임을 진행할 수 있도록 사전학습을 한 뒤 진행을 했으며,

Step 1) 마우스 이벤트 처리(셀 오픈) >  마우스 좌, 우클릭 이벤트 처리

Step 2) 각 타일 출력 -> 2차원 리스트를 통한 셀 관리 및 각 요소에 대한 조건 처리

를 배웠습니다.

 

이번 장에는 pygame과 함께 여러가지 요소를 배치하여 각 요소에 대한 검증 진행을 통해 게임을 구현하는 방법을 배웠습니다. 이를 기반으로 각 기능에 대한 함수를 생성하여 코드를 모듈화 하는법을 꾸준히 배운다면 조금 더 깔끔한 코드를 통해 재밌고 읽기 쉬운(코드) 게임을 만들어 보세요^^

 

ㅁ 참고

토닥토닥 파이썬

 

 


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

 

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

 

반응형