지난 시간
지난번엔 지뢰찾기에 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 이다.
- pack 레이아웃 : 위젯들을 차례대로 패킹하는 레이아웃 방식이다. pack() 메서드를 사용하여 위젯을 배치하고 기본적으로 위젯을 수평 또는 수직으로 패킹할 수 있다.
- grid 레이아웃 : 위젯들을 격자 형태로 배치하는 레이아웃 방식이다. grid() 메서드를 사용하여 위젯을 특정 행(row)과 열(column)에 배치한다.
- 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()
전에 코드에서 새로 코드를 정리한 최종 코드이다.
이제 실행해서 플레이해보자.

지뢰찾기를 플레이해봤는데 이제 게임이라고 부를만하게 플레이되는 것을 확인할 수 있다.
Next
이제 얼추 게임의 형상을 갖췄으니 다음엔 기능을 더 보안하겠다.
'지뢰찾기 게임' 카테고리의 다른 글
지뢰찾기 게임 제작기 5 (0) | 2024.04.02 |
---|---|
지뢰찾기 게임 제작기 4 (0) | 2024.03.26 |
지뢰찾기 게임 제작기 2 (7) | 2024.03.12 |
지뢰찾기 게임 제작기 1 (0) | 2024.03.05 |