지난 시간
지난번엔 간단한 코딩으로 지뢰찾기 게임을 만들었다.
이번엔 지난번 코드를 더 발전 시켜서 GUI를 적용시키고 클릭 이벤트를 처리해 본다.
지뢰찾기 게임 만들기
언어는 파이썬을 사용하고, 만드는 기준은 지뢰찾기 게임 룰에 기반한다.
import tkinter as tk
이번에 또 다른 라이브러리가 필요하다.
GUI로 표시해야 하기 때문에 tkinter 라이브러리를 추가해 주는 것이다.
tkinter는 GUI에 대한 표준 Python 인터페이스이며 Window 창을 생성할 수 있다.
4. GUI 적용
root = tk.Tk()
root.title("지뢰찾기")
root.geometry("400x400+100+100")
root.resizable(False, False)
root.mainloop()
윈도우이름 = tkinter.Tk()를 이용하여 가장 상위 레벨의 윈도우 창을 생성할 수 있다.
위 코드에선 실행했을 때 나타나는 창이 바로 코드의 root에 해당한다.
윈도우이름.mainloop()를 사용하여 윈도우이름의 윈도우 창을 윈도우가 종료될 때까지 실행시킨다.
위 코드에선 mainloop()에 의해 root 창은 종료되지 않고 버튼 클릭 등의 이벤트를 수신하거나 사용자의 입력을 처리하는 등의 일을 계속 수행할 수 있게 되는 것이다.
윈도우이름.title("제목")을 이용하여 윈도우 창의 제목을 설정할 수도 있다.
위 코드에선 root 창의 이름을 지뢰찾기로 설정한 것을 알 수 있다.
윈도우이름.geometry("너비x높이+x좌표+y좌표")를 이용하여 윈도우 창의 너비와 높이, 초기 화면 위치의 x좌표와 y좌표를 설정할 수 있다.
위 코드에선 가로 400 세로 400의 정사각형 창을 좌표(100, 100)에 나오게 만든 것을 알 수 있다.
윈도우이름.resizeable(상하, 좌우)을 이용하여 윈도우 창의 창 크기 조절 가능 여부를 설정할 수 있다.
True로 설정할 경우 윈도우 창의 크기를 조절할 수 있지만 위 코드에선 False로 막아놓은 것을 알 수 있다.
resizeable()을 적용할 때, True=1, False=0을 의미하여 상수를 입력해도 적용이 가능하다.
buttons = [] # 버튼들을 저장할 2차원 리스트
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)
아까 만든 창에 지뢰들을 표시할 버튼들을 배치해 준다.
버튼을 누르면 그 셀이 지뢰인지 아닌지 보여줄 것이기 때문에 버튼으로 하는 것이다.
tk.Button()은 창, 들어갈 텍스트, 너비, 높이로 버튼을 만들어준다.
button.grid(row=row, column=col)는 그리드 레이아웃으로 행과 열에 버튼을 배치해준다.
5. 클릭 이벤트 처리
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))
GUI를 사용하면 전 코드처럼 main의 while 문 없이 tk.Tk()와 mainloop() 사이에 코드를 작동하는 식으로 기능하기 때문에 그 안쪽에만 코드를 작성하면 된다.
그렇기에 안쪽에 버튼의 클릭 이벤트 코드를 작성했다.
위젯.Bind("이벤트", 함수)를 사용하여 위젯의 이벤트가 작동할 때 실행할 함수를 설정할 수 있다.
이벤트 | 작동 |
<Button-1> | 마우스 왼쪽 버튼을 누를 때 |
<Button-2> | 마우스 휠 버튼을 누를 때 |
<Button-3> | 마우스 오른쪽 버튼을 누를 때 |
<Button-4> | 마우스 휠 버튼을 올릴 때 |
<Button-5> | 마우스 휠 버튼을 내 때 |
<MouseWheel> | 마우스 휠을 이동할 때 |
Bind 이벤트 부분에 들어갈 수 있는 마우스 이벤트 들은 이런 것들이 있다.
우린 마우스 우클릭과 좌클릭으로 셀에 깃발과 셀 오픈을 관리할 것이기에 1번과 3번을 이용한다.
함수 부분에선 파이썬에 lamda를 이용하여 익명함수를 만들었다.
간단하게 람다함수에 대해 설명하고 넘어가겠다.
# 기본 함수 표현식
def hap(x, y):
return x + y
hap(10, 20)
# 람다 함수 표현식
(lambda x,y: x + y)(10, 20)
위에 두 코드의 결과는 같게 나오는데 그 이유는 두 코드의 생김새는 다르지만 동작은 같기 때문이다.
lambda함수는 lambda라는 키워드를 입력하고 뒤에는 매개변수(인자)를 입력하고 콜론(:)을 넣은 다음에 그 매개변수(인자)를 이용한 동작들을 적으면 된다.
위 코드로 보면 인자로 들어온 값 x와 y를 더해서 반환하는 코드가 되는 것이다.
다시 돌아가서 코드를 보면 Bind 함수 부분에 lambda event, row=row, col=col: right_click(event, row, col)라고 람다 함수로 이벤트, 열, 행을 넣어 클릭 함수를 실행시키게 만들었다.
# 좌클릭
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))
# 우클릭
def right_click(event, row, col):
if revealed[row][col] == ' ':
revealed[row][col] = 'F' # 깃발 표시
elif revealed[row][col] == 'F':
revealed[row][col] = ' ' # 깃발 제거
좌우클릭의 함수들이다.
- 좌클릭 : 셀 오픈으로 지뢰면 지뢰를 밟아 X로 표시하고 아니라면 주위 지뢰 개수를 표시해 준다.
- 우클릭 : 지뢰가 있다고 깃발로 표시하는 것으로 클릭한다면 F로 표시하고 다시 클릭하면 지운다.
이제 다 만들어 작동이 될 것이라고 생각하겠지만 사실 작동시키고 셀을 누르면 아무 일도 일어나지 않는다.
이유는 사용자가 셀을 클릭할 때마다 GUI가 최신 정보로 업데이트되어 현재 게임 상태를 잘 표시해야 하는데
그런 지금 코드엔 없기 때문이다.
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')
셀을 반복하면서 업데이트해주는 코드이다. 현재 상태를 버튼에 반영한다.
그리고 이 함수를 적절한 부분에 넣어주면 판이 업데이트되면서 코드가 잘 작동되게 된다.
지뢰찾기(ver.2) 플레이해보기
import tkinter as tk
import random
# 게임 설정
board_size = 9
num_mines = 10
# 게임 보드 생성
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
# 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)
# 게임 보드 업데이트
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))
update_board()
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
간단한 로직과 UI를 만들었으니 이제 부가 기능적인 것을 만들어보겠다.
'지뢰찾기 게임' 카테고리의 다른 글
지뢰찾기 게임 제작기 5 (0) | 2024.04.02 |
---|---|
지뢰찾기 게임 제작기 4 (0) | 2024.03.26 |
지뢰찾기 게임 제작기 3 (0) | 2024.03.19 |
지뢰찾기 게임 제작기 1 (0) | 2024.03.05 |