지뢰찾기 게임

지뢰찾기 게임 제작기 3

잡코신 2024. 3. 19. 18:00
728x90
반응형

지난 시간

지난번엔 지뢰찾기에 GUI를 적용시키고 기본 로직을 완성했다.

이번엔 지난번에 만든 로직을 바탕으로 게임처럼 만들 것이다.

지뢰찾기 게임 만들기

지뢰찾기 룰에 기반해 파이썬으로 제작한다.

 

현재 코드 상태는 그저 지뢰찾기라고는 할 수 있겠지만 게임이라고 부를 수 없다.

게임처럼 만들기 위해 게임 오버와 다시 하기 등을 만들어야 한다.

message_label = tk.Label(root, text="", font=("Helvetica", 16))

먼저 게임 종료 메시지를 표시해 줄 레이블 위젯을 만들어준다.

tkinter.Label()이라는 함수를 이용한다. 각 매개변수는 root라는 이름의 윈도우, text=""는 레이블의 초기 텍스트를 설정하고 font=는 글씨의 폰트와 크기를 설정해 준다.

그리고 위젯을 그리드 레이아웃으로 배치해 줄 텐데 그전에 지난 시간부터 배치에 궁금증이나 레이아웃에 대하여 잘 모르는 사람들도 있을 것 같아 tkinter의 레이아웃에 대해서 간단하게 알아보고 넘어가겠다.

# 팩(Pack) 레이아웃
button1.pack(side="top")

# 그리드(Grid) 레이아웃
button2.grid(row=0, column=1)

# 플레이스(Place) 레이아웃
button3.place(x=50, y=50)

 tkinter에서 사용할 수 있는 레이아웃 방식엔 3가지가 존재한다.

바로 pack, grid, place 이다.

  1.  pack 레이아웃 : 위젯들을 차례대로 패킹하는 레이아웃 방식이다. pack() 메서드를 사용하여 위젯을 배치하고 기본적으로 위젯을 수평 또는 수직으로 패킹할 수 있다.
  2. grid 레이아웃 : 위젯들을 격자 형태로 배치하는 레이아웃 방식이다. grid() 메서드를 사용하여 위젯을 특정 행(row)과 열(column)에 배치한다.
  3. place 레이아웃 : 위젯들을 절대적인 위치에 배치하는 레이아웃 방식이다. place() 메서드를 사용하여 위젯을 특정 위치에 배치한다. 이 레이아웃 방식은 보통 위젯의 크기와 위치를 직접 지정할 수 있다.

예시는 위 코드에 순서대로 나와있다.

그렇다면 우리는 왜 Grid 레이아웃을 사용하는가 하면 지뢰게임은 보드가 격자 형태로 배치되어 있어서, 그리드 레이아웃을 사용하면 쉽게 배치하고 조정할 수 있다. 또한, 그리드 레이아웃을 사용하면 요소들 사이의 간격을 일정하게 유지할 수 있고, 윈도우 크기가 변경될 때 요소들이 유연하게 조정되게 할 수 있다.

message_label.grid(row=board_size, columnspan=board_size)

다시 돌아와서 grid라는 함수로 배치해 준다.

위젯.grid함수의 매개변수엔 다음의 종류가 있다.

매개변수 의미 속성
row 위젯이 배치될 행의 인덱스 상수
column 위젯이 배치될 열의 인덱스 상수
rowspan 위젯이 여러 행에 걸쳐서 배치될 경우, 차지하는 행의 수 상수
columnspan 위젯이 여러 열에 걸쳐서 배치될 경우, 차지하는 열의 수 상수
sticky 위젯이 셀 내에서 어떤 방향으로 늘어날지를 설정 n, e, s, w, nw, ne, sw, se
ipadx 위젯에 대한 x 방향 내부 패딩 상수
ipady 위젯에 대한 y 방향 내부 패딩 상수
padx 위젯에 대한 x 방향 외부 패딩 상수
pady 위젯에 대한 y 방향 외부 패딩 상수

그럼 보드 바로 아래 버튼이 생기게 된다. 

remaining_cells = board_size * board_size - num_mines

def check_game_over():
    for row in range(board_size):
        for col in range(board_size):
            if revealed[row][col] == 'X':
                message_label.config(text="지뢰를 밟았습니다! 게임 종료!")
                root.update()  # 화면 갱신
                root.after(2000, root.destroy)  # 2초 후에 게임 종료
                return
    if remaining_cells == 0:
        message_label.config(text="축하합니다! 모든 안전한 셀을 찾았습니다. 게임 승리!")
        root.update()  # 화면 갱신
        root.after(2000, root.destroy)  # 2초 후에 게임 종료

이제 게임 종료 로직을 만들어줘야 한다.

셀을 열었을 때 지뢰면 게임을 종료하고 남는 셀이 0이면 승리로 게임을 종료한다.

다시 하기 버튼이 없어서 2초 후에 자동으로 프로그램이 종료되도록 만들었다.

restart_button = tk.Button(root, text="다시하기", command=restart_game, font=("Helvetica", 12))
restart_button.grid(row=board_size + 1, column=0, columnspan=board_size)

다시 하기 버튼을 만들어줬다.

tkinter.Button 함수를 이용해서 command= 변수로 버튼이 눌러진다면 restart_game 함수가 실행되게 했다.

버튼은 메시지 표시위젯 밑에 배치했다. 다른 버튼을 추가할 일이 생긴다면 이 행에 추가하겠다.

def restart_game():
    global board, revealed, remaining_cells

    # 새로운 게임 보드 생성
    board = initialize_board(board_size, num_mines)
    revealed = [[' ' for _ in range(board_size)] for _ in range(board_size)]
    remaining_cells = board_size * board_size - num_mines

    # 메시지 초기화
    message_label.config(text="")

    update_board()

restart_game 함수이다.

똑같이 새로운 지뢰찾기 보드를 생성시키고 게임 종료 메시지와 보드를 초기화시킨다.

def update_info_labels():
    mine_count_label.config(text=f"지뢰 개수: {num_mines}")
    remaining_cells_label.config(text=f"남은 셀 개수: {remaining_cells}")
    
info_label_frame = tk.Frame(root)
info_label_frame.grid(row=board_size + 2, column=0, columnspan=board_size)

mine_count_label = tk.Label(info_label_frame, text=f"지뢰 개수: {num_mines}", font=("Helvetica", 12))
mine_count_label.grid(row=0, column=0)

remaining_cells_label = tk.Label(info_label_frame, text=f"남은 셀 개수: {remaining_cells}", font=("Helvetica", 12))
remaining_cells_label.grid(row=0, column=1)

또 나중에 난이도 표기를 위하여 지뢰 개수와 남은 셀 수를 알려주는 레이블도 만든다.

다시 하기 버튼 밑에 배치하는 데 지뢰와 남은 셀의 개수를 한 행에서 표기하기 위해 frame을 사용했다.

Tkinter에서 Frame은 다른 위젯들을 그룹화하거나 배치할 때 사용되는 컨테이너 위젯이다.

Frame은 다른 위젯들을 포함하고 배치하는데 사용된다. 예를 들어, 여러 개의 레이블, 버튼 또는 입력 필드를 그룹화하여 함께 배치하거나, 프레임 내에서 서로 다른 위젯들을 서로 다른 영역에 배치할 때 사용한다.

사용법은 간단하다. 만들고 넣어주면 된다.

 

지뢰찾기(ver.3) 플레이해 보기

import tkinter as tk
import random

# 게임 설정
board_size = 9
num_mines = 10
remaining_cells = board_size * board_size - num_mines

# 게임 보드 초기화
def initialize_board(size, mines):
    board = [[' ' for _ in range(size)] for _ in range(size)]
    placed_mines = 0
    while placed_mines < mines:
        row, col = random.randint(0, size - 1), random.randint(0, size - 1)
        if board[row][col] != '*':
            board[row][col] = '*'
            placed_mines += 1
    return board

# 지뢰 주변의 지뢰 개수 계산
def count_mines_around(board, row, col):
    count = 0
    for r in range(row - 1, row + 2):
        for c in range(col - 1, col + 2):
            if 0 <= r < len(board) and 0 <= c < len(board[0]) and board[r][c] == '*':
                count += 1
    return count

def update_info_labels():
    mine_count_label.config(text=f"지뢰 개수: {num_mines}")
    remaining_cells_label.config(text=f"남은 셀 개수: {remaining_cells}")

# 다시 시작
def restart_game():
    global board, revealed, remaining_cells

    board = initialize_board(board_size, num_mines)
    revealed = [[' ' for _ in range(board_size)] for _ in range(board_size)]
    remaining_cells = board_size * board_size - num_mines

    message_label.config(text="")

    update_board()
    update_info_labels()

# GUI 초기화
root = tk.Tk()
root.title("지뢰찾기")
root.resizable(False, False)

# 게임 보드 생성
board = initialize_board(board_size, num_mines)
revealed = [[' ' for _ in range(board_size)] for _ in range(board_size)]

# 게임 보드의 각 셀을 버튼으로 만들기
buttons = []

for row in range(board_size):
    row_buttons = []
    for col in range(board_size):
        button = tk.Button(root, text='', width=4, height=2)
        button.grid(row=row, column=col)
        row_buttons.append(button)
    buttons.append(row_buttons)


# 게임 종료 메시지 표시
message_label = tk.Label(root, text="", font=("Helvetica", 16))
message_label.grid(row=board_size, columnspan=board_size)


# 다시하기 버튼 추가
restart_button = tk.Button(root, text="다시하기", command=restart_game, font=("Helvetica", 12))
restart_button.grid(row=board_size + 1, column=0, columnspan=board_size)

info_label_frame = tk.Frame(root)
info_label_frame.grid(row=board_size + 2, column=0, columnspan=board_size)

mine_count_label = tk.Label(info_label_frame, text=f"지뢰 개수: {num_mines}", font=("Helvetica", 12))
mine_count_label.grid(row=0, column=0)

remaining_cells_label = tk.Label(info_label_frame, text=f"남은 셀 개수: {remaining_cells}", font=("Helvetica", 12))
remaining_cells_label.grid(row=0, column=1)

# 게임 종료
def check_game_over():
    for row in range(board_size):
        for col in range(board_size):
            if revealed[row][col] == 'X':
                message_label.config(text="지뢰를 밟았습니다! 게임 종료!")
                root.update()  # 화면 갱신
                # root.after(2000, root.destroy)  # 2초 후에 게임 종료
                return
    if remaining_cells == 0:
        message_label.config(text="축하합니다! 모든 안전한 셀을 찾았습니다. 게임 승리!")
        # root.update()  # 화면 갱신
        root.after(2000, root.destroy)  # 2초 후에 게임 종료

# 게임 실행
def update_board():
    for row in range(board_size):
        for col in range(board_size):
            if revealed[row][col] == ' ':
                buttons[row][col].config(text='', state='normal')
            else:
                buttons[row][col].config(text=revealed[row][col], state='disabled')

def left_click(event, row, col):
    if board[row][col] == '*':
        revealed[row][col] = 'X'  # 지뢰 밟음
    else:
        revealed[row][col] = str(count_mines_around(board, row, col))
        global remaining_cells
        remaining_cells -= 1

    update_board()
    update_info_labels()
    check_game_over()

def right_click(event, row, col):
    if revealed[row][col] == ' ':
        revealed[row][col] = 'F'  # 깃발 표시
    elif revealed[row][col] == 'F':
        revealed[row][col] = ' '  # 깃발 제거

    update_board()

# 각 버튼에 클릭 이벤트 바인딩
for row in range(board_size):
    for col in range(board_size):
        buttons[row][col].bind("<Button-1>", lambda event, row=row, col=col: left_click(event, row, col))
        buttons[row][col].bind("<Button-3>", lambda event, row=row, col=col: right_click(event, row, col))

update_board()

root.mainloop()

전에 코드에서 새로 코드를 정리한 최종 코드이다.

이제 실행해서 플레이해보자.

지뢰찾기 ver3

지뢰찾기를 플레이해봤는데 이제 게임이라고 부를만하게 플레이되는 것을 확인할 수 있다.

 

Next

이제 얼추 게임의 형상을 갖췄으니 다음엔 기능을 더 보안하겠다.

 

728x90
반응형

'지뢰찾기 게임' 카테고리의 다른 글

지뢰찾기 게임 제작기 5  (0) 2024.04.02
지뢰찾기 게임 제작기 4  (0) 2024.03.26
지뢰찾기 게임 제작기 2  (7) 2024.03.12
지뢰찾기 게임 제작기 1  (0) 2024.03.05