-
[Python3] 파이썬으로 지뢰찾기(Mine Sweeper) 만들기Study/Python3 2021. 6. 21. 04:48반응형
파이썬으로 만들어 본 지뢰찾기 프로그램입니다.
사용한 모듈 : PyQt5(난이도 선택창), Pygame(게임GUI)
setting.py
#각종 환경 설정을 위한 "값"을 모아놓는 곳. #다른 파일에서 코드를 읽을 때 숫자가 무슨 의미를 하는지 이해하기 어려운 요소들은 전부 여기에 넣고 관리하기. #예를들면 RGB값, 블럭 크기와 같은 것은 코드를 읽을 때 갑자기 숫자로 표현해서 읽는 것이 난해해짐 #다른 py에서 이 값을 불러오고싶으면 import settings를 해주고, settings.WHITE와 같은 방법으로 불러올 수 있음 #창 크기 SCREEN_SIZE_BEGINNER=[250,250] SCREEN_SIZE_INTERMEDIATE=[500,500] SCREEN_SIZE_ADVANCED=[1000,500] #게임 영역 관련 #난이도에 따른 맵 크기 설정 (가로축, 세로축, 지뢰수) BEGINNER = (5,5,5) #5x5사이즈의 맵 크기에 지뢰 1개 INTERMEDIATE = (10,10,10) ADVANCED = (20,10,20) #색상 WHITE = (255,255,255) RED = (255,0,0) GREEN = (0,255,0) BLUE = (0,0,255) ORANGE = (255,69,0) GOLD = (255,125,0) PURPLE = (128,0,128) CYAN = (0,255,255) BLACK = (0,0,0) GRAY = (128, 128, 128) #셀 크기 CELL_SIZE = 50 #intro 난이도 선택 버튼 위치 BTN_X = 50 BTN_Y = 250
각종 세팅 값(변수)을 여기에 몰아넣어서 앞으로 사용되어야 하는 각종 값을 미리 정의했습니다.
gameLogic.py
import random from MineSweeper.settings import * class GameLogic(): def __init__(self): #게임 초기화 지점 pass def run(self): #실행 부분 level = self.getLevel() map = self.createMap(level) def getLevel(self): #난이도 가져오기 while True: level = input("난이도를 입력하세요 (초급, 중급, 고급): ") if level == '초보': return BEGINNER elif level == '중급': return INTERMEDIATE elif level == '고급': return ADVANCED else: print("난이도를 제대로 입력해주세요.") def createMap(self, level): #레벨에 따른 맵 생성 + 레벨에 따른 지뢰 심기 + 지뢰 근처에 적절한 숫자 생성 한번에 처리함. width = level[0] height = level[1] mine = level[2] arr = [[0 for row in range(width)] for column in range(height)] num = 0 while num < mine: x = random.randint(0, width - 1) # 지뢰의 x축 범위 랜덤으로 설정 y = random.randint(0, height - 1) # 지뢰의 y축 범위 랜덤으로 설정 print(num) if arr[y][x] == 'X': print(num) continue print("[HINT]생성된 지뢰 위치 : (x:", x, ", y:", y, ")") # 테스트를 용이하게 하기 위해 넣음 (GUI에서는 출력되지 않아야함.) arr[y][x] = 'X' # 생성된 지뢰 좌표에 X로 표현 # 지뢰 주변 힌트 숫자 생성 if (x >= 0 and x <= width - 2) and (y >= 0 and y <= height - 1): if arr[y][x + 1] != 'X': arr[y][x + 1] += 1 # center right if (x >= 1 and x <= width - 1) and (y >= 0 and y <= height - 1): if arr[y][x - 1] != 'X': arr[y][x - 1] += 1 # center left if (x >= 1 and x <= width - 1) and (y >= 1 and y <= height - 1): if arr[y - 1][x - 1] != 'X': arr[y - 1][x - 1] += 1 # top left if (x >= 0 and x <= width - 2) and (y >= 1 and y <= height - 1): if arr[y - 1][x + 1] != 'X': arr[y - 1][x + 1] += 1 # top right if (x >= 0 and x <= width - 1) and (y >= 1 and y <= height - 1): if arr[y - 1][x] != 'X': arr[y - 1][x] += 1 # top center if (x >= 0 and x <= width - 2) and (y >= 0 and y <= height - 2): if arr[y + 1][x + 1] != 'X': arr[y + 1][x + 1] += 1 # bottom right if (x >= 1 and x <= width - 1) and (y >= 0 and y <= height - 2): if arr[y + 1][x - 1] != 'X': arr[y + 1][x - 1] += 1 # bottom left if (x >= 0 and x <= width - 1) and (y >= 0 and y <= height - 2): if arr[y + 1][x] != 'X': arr[y + 1][x] += 1 # bottom center num += 1 return arr def displayMap(self, map): # 콘솔 출력용 (GUI랑은 상관 없음) for row in map: print(" ".join(str(cell) for cell in row)) print("") def checkMine(self, map, x, y): # 지뢰 찾는 함수. 이기면 True, 지면 False 라는 의미로 설정함. if map[y][x] == 'X': return False return True
게임을 돌릴 때 사용할 알고리즘의 집합입니다.
gui_intro.py
from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QVBoxLayout, QLabel, QRadioButton, QWidget from PyQt5 import QtGui from settings import * from PyQt5.QtWidgets import * from PyQt5.QtCore import Qt from PyQt5 import QtGui import sys, gui #gui 첫 시작 페이지 #버튼 클릭을 통해 난이도를 gui로 넘겨 난이도별 화면 출력 class Window(QWidget): def __init__(self): super().__init__() self.count = 0 self.initialize() def initialize(self): self.setGeometry(700, 300, 500, 400) layout = QVBoxLayout() self.setLayout(layout) self.label = QLabel("Minesweeper") self.label.setAlignment(Qt.AlignCenter) self.label.setFont(QtGui.QFont('Hack', 15)) layout.addWidget(self.label) # GUI를 실행하는 버튼 btnRun0 = QPushButton("초급", self) # 버튼 텍스트 btnRun0.move(BTN_X, BTN_Y) # 버튼 위치 btnRun0.clicked.connect(self.btnRun_clicked_0) # 클릭 시 실행할 function btnRun1 = QPushButton("중급", self) # 버튼 텍스트 btnRun1.move(BTN_X, BTN_Y+30) # 버튼 위치 btnRun1.clicked.connect(self.btnRun_clicked_1) # 클릭 시 실행할 function btnRun2 = QPushButton("고급", self) # 버튼 텍스트 btnRun2.move(BTN_X, BTN_Y+60) # 버튼 위치 btnRun2.clicked.connect(self.btnRun_clicked_2) # 클릭 시 실행할 function def btnRun_clicked_0(self): #버튼을 눌렀을 때 gui 실행 gui.GUI("초급") def btnRun_clicked_1(self): #버튼을 눌렀을 때 gui 실행 gui.GUI("중급") def btnRun_clicked_2(self): #버튼을 눌렀을 때 gui 실행 gui.GUI("고급") app = QApplication(sys.argv) screen = Window() screen.show() sys.exit(app.exec_())
난이도 선택창입니다.
gui.py
import sys, pygame import MineSweeper.gameLogic from MineSweeper.settings import * from pygame.locals import * from MineSweeper.gameLogic import GameLogic # 내 생각에는 일단 " "(스페이스 한칸)로 채워져 있는 판을 만들고, 해당 칸을 클릭했을 때, map에 있는 해당 칸으로 바꿔주는게 어떨까 싶음. class GUI(): #임시. pygame 안써도 됨. def __init__(self, level): #초기화 pygame.init() # pygame 초기화. 초기화를 해야 pygame을 사용할 수 있다고 함. super().__init__() SCREEN_SIZE=self.getScreenSize(level) SCREEN_WIDTH = self.getScreenSize(level)[0] SCREEN_HEIGHT = self.getScreenSize(level)[1] self.count = 0 self.screen = pygame.display.set_mode(SCREEN_SIZE) # 디스플레이 크기 설정 pygame.display.set_caption('Minesweeper') # 프로그램 이름 설정 gameLevel=self.getLevel(level) #레벨을 tuple 형태로 받아옴. arr = GameLogic.createMap(self, gameLevel) #맵을 생성하고 저장 OPENED = [[False for column in range(len(arr[0]))] for row in range(len(arr))] #오픈한 칸인지 확인 CHECKED = [[False for column in range(len(arr[0]))] for row in range(len(arr))] #깃발을 체크한 칸인지 확인 self.draw_Cells(arr) # 칸 그리기 while True: # main game loop (게임에 필요한 메소드 불러오기) for event in pygame.event.get(): #창 안의 이벤트를 받는 영역. 예를 들면 종료키, 키보드키, 마우스 클릭 등 if event.type == QUIT:#상단의 X키 누를 때 까지 프로그램 종료 안하고 유지하기 (필수임) pygame.quit() sys.exit() elif event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: # 마우스 왼쪽 클릭시 column_index = event.pos[0] // CELL_SIZE row_index = event.pos[1] // CELL_SIZE num = 0 print(column_index, row_index) if arr[row_index][column_index] == 'X': # 선택된 칸이 X이면 종료 self.open_All(arr, OPENED) #모든 칸 열기 print("패배") fail_font = pygame.font.SysFont('Sans', 70) fail_image = fail_font.render('Lose', True, RED) self.screen.blit(fail_image, fail_image.get_rect(centerx=SCREEN_WIDTH // 2,centery=SCREEN_HEIGHT // 2)) else: # 선택된 칸 오픈 OPENED=self.open_Cell(arr,OPENED, column_index, row_index) for i in range(len(arr)): #열리지 않은 칸 수 셈 for j in range(len(arr[0])): if not OPENED[i][j]: num += 1 if num == gameLevel[2]: #열리지 않은 칸의 수와 지뢰의 수가 같으면 성공 출력 == 지뢰가 없는 칸 모두 오픈 success_font = pygame.font.SysFont('Sans', 70) success_image = success_font.render('Win', True, RED) self.screen.blit(success_image, success_image.get_rect(centerx=SCREEN_WIDTH // 2, centery=SCREEN_HEIGHT // 2)) elif event.button == 3: # 마우스 우클릭시 깃발 column_index = event.pos[0] // CELL_SIZE row_index = event.pos[1] // CELL_SIZE flag_font = pygame.font.SysFont('Sans', 30) flag_image = flag_font.render('V', True, WHITE) if CHECKED[row_index][column_index]: #이미 깃발이 체크된 칸을 선택시 체크 지우기 flag_image.fill(GRAY) CHECKED[row_index][column_index] = False else: CHECKED[row_index][column_index] = True self.screen.blit(flag_image, (column_index * CELL_SIZE + 10, row_index * CELL_SIZE + 5)) '''while num>4: # 승리표시가 되는지 확실히 모르겠음 if GameLogic().createMap(self) == arr[row_index][column_index]: num+=1 continue success_font = pygame.font.SysFont('malgungothic', 70) success_image = success_font.render('승리', True, RED) self.screen.blit(success_image,success_image.get_rect(centerx=SCREEN_WIDTH // 2, centery=SCREEN_HEIGHT // 2))''' # self.draw_Cells(arr) # 칸 그리기 pygame.display.update() def getLevel(self, level): #레벨 가져오기(나중에 수정 필요) if level=='초급': return BEGINNER elif level=='중급': return INTERMEDIATE elif level=='고급': return ADVANCED def getScreenSize(self,level): if level=='초급': return SCREEN_SIZE_BEGINNER elif level=='중급': return SCREEN_SIZE_INTERMEDIATE elif level=='고급': return SCREEN_SIZE_ADVANCED def draw_Cells(self, arr): COLUMN_COUNT = len(arr[0]) ROW_COUNT = len(arr) for column_index in range(COLUMN_COUNT): for row_index in range(ROW_COUNT): rect = (CELL_SIZE * column_index, CELL_SIZE * row_index, CELL_SIZE, CELL_SIZE) pygame.draw.rect(self.screen, GRAY, rect) pygame.draw.rect(self.screen, BLACK, rect, 1) def open_Cell(self,arr,OPENED,col,row): if col < 0 or col >= len(arr[0]) or row < 0 or row >= len(arr): return arr cell = arr[row][col] # 선택된 칸 if OPENED[row][col]: #이미 확인한 칸 return arr OPENED[row][col] = True if cell == 0: #셀이 0이면 1 이상의 수가 나올때까지 반복해서 여는 재귀함수 생성 / for 문으로 고쳐야할 듯 self.open_Cell(arr, OPENED, col + 1, row) self.open_Cell(arr, OPENED,col, row + 1) self.open_Cell(arr, OPENED,col + 1, row + 1) self.open_Cell(arr, OPENED,col - 1, row) self.open_Cell(arr, OPENED,col, row - 1) self.open_Cell(arr, OPENED,col - 1, row - 1) self.open_Cell(arr, OPENED,col + 1, row - 1) self.open_Cell(arr, OPENED,col - 1, row + 1) # font5 = pygame.font.SysFont('notarisation', 50) font5 = pygame.font.SysFont('Sans', 30) img5 = font5.render(str(cell), True, BLACK) self.screen.blit(img5, (CELL_SIZE*col+10, CELL_SIZE*row+10)) return OPENED def open_All(self,arr,OPENED): for i in range(len(arr)): for j in range(len(arr[0])): self.open_Cell(arr,OPENED,j,i)
앞서 올린 gameLogic.py를 적극 활용하여 게임 상황을 실시간으로 표시합니다.
실제 동작 화면
반응형'Study > Python3' 카테고리의 다른 글
[정보보호개론] Substitution Cipher (치환 암호 / 카이사르 암호) 해독 코드 (0) 2021.03.22