파이썬으로 시작하는 게임 개발의 세계 🐍🎮
안녕하세요! Python Game Dev 블로그의 열세 번째 포스팅입니다. 지난 포스팅에서는 블랙잭 카드 게임을 만들어 보았는데요. 오늘은 약속드린 대로 '메모리 매칭 게임(Memory Matching Game)'을 파이썬으로 구현해보겠습니다.
오늘의 게임: 파이썬으로 만드는 메모리 매칭 게임 🧠🃏
메모리 매칭 게임은 카드를 뒤집어 같은 짝을 찾는 간단하면서도 재미있는 게임입니다. 기억력을 테스트하는 동시에 프로그래밍 기술을 연습하기에 완벽한 프로젝트입니다. Pygame 라이브러리를 활용하여 그래픽 인터페이스와 애니메이션, 사운드 효과까지 구현해보겠습니다.
게임의 규칙 📜
- 여러 장의 카드가 뒤집혀 있습니다.
- 플레이어는 한 번에 두 장의 카드를 선택합니다.
- 두 카드가 일치하면 화면에 남고, 일치하지 않으면 다시 뒤집힙니다.
- 모든 카드 짝을 찾으면 게임이 종료됩니다.
- 최소한의 시도로 모든 짝을 찾는 것이 목표입니다.
전체 코드 💻
import pygame
import random
import time
import sys
from pygame import mixer
# 초기화
pygame.init()
mixer.init()
# 색상 정의
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
BLUE = (0, 0, 255)
DARK_BLUE = (0, 0, 150)
RED = (255, 0, 0)
# 게임 설정
CARD_WIDTH = 100
CARD_HEIGHT = 150
CARD_MARGIN = 20
GRID_SIZE = (4, 4) # 4x4 그리드
WINDOW_WIDTH = GRID_SIZE[0] * (CARD_WIDTH + CARD_MARGIN) + CARD_MARGIN
WINDOW_HEIGHT = GRID_SIZE[1] * (CARD_HEIGHT + CARD_MARGIN) + CARD_MARGIN + 100
# 화면 설정
screen = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
pygame.display.set_caption("메모리 매칭 게임")
# 폰트 설정
font = pygame.font.SysFont('malgungothic', 36)
small_font = pygame.font.SysFont('malgungothic', 24)
# 사운드 로드
try:
flip_sound = mixer.Sound('flip.wav')
match_sound = mixer.Sound('match.wav')
win_sound = mixer.Sound('win.wav')
wrong_sound = mixer.Sound('wrong.wav')
except:
print("사운드 파일을 로드할 수 없습니다.")
flip_sound = match_sound = win_sound = wrong_sound = None
# 카드 클래스
class Card:
def __init__(self, value, x, y):
self.value = value
self.x = x
self.y = y
self.width = CARD_WIDTH
self.height = CARD_HEIGHT
self.flipped = False
self.matched = False
self.rect = pygame.Rect(x, y, CARD_WIDTH, CARD_HEIGHT)
# 카드 디자인 (간단한 색상과 심볼)
card_colors = [RED, BLUE, DARK_BLUE, (0, 150, 0), (150, 0, 150),
(150, 150, 0), (0, 150, 150), (255, 128, 0)]
# 카드 값에 따라 색상 선택
self.color = card_colors[value % len(card_colors)]
def draw(self, screen):
if self.matched:
# 매치된 카드는 흐릿하게 표시
pygame.draw.rect(screen, (self.color[0]//2, self.color[1]//2, self.color[2]//2), self.rect, 0, 10)
pygame.draw.rect(screen, BLACK, self.rect, 2, 10)
# 중앙에 심볼(숫자) 그리기
symbol = small_font.render(str(self.value + 1), True, WHITE)
screen.blit(symbol, (self.x + self.width//2 - symbol.get_width()//2,
self.y + self.height//2 - symbol.get_height()//2))
elif self.flipped:
# 앞면 - 색상으로 채우기
pygame.draw.rect(screen, self.color, self.rect, 0, 10)
pygame.draw.rect(screen, BLACK, self.rect, 2, 10)
# 중앙에 심볼(숫자) 그리기
symbol = font.render(str(self.value + 1), True, WHITE)
screen.blit(symbol, (self.x + self.width//2 - symbol.get_width()//2,
self.y + self.height//2 - symbol.get_height()//2))
else:
# 뒷면 - 회색
pygame.draw.rect(screen, GRAY, self.rect, 0, 10)
pygame.draw.rect(screen, BLACK, self.rect, 2, 10)
# 카드 뒷면 디자인
pygame.draw.line(screen, BLACK, (self.x + 20, self.y + 20),
(self.x + self.width - 20, self.y + self.height - 20), 3)
pygame.draw.line(screen, BLACK, (self.x + self.width - 20, self.y + 20),
(self.x + 20, self.y + self.height - 20), 3)
def flip(self):
if not self.matched:
self.flipped = not self.flipped
if flip_sound:
flip_sound.play()
def is_clicked(self, pos):
return self.rect.collidepoint(pos) and not self.flipped and not self.matched
# 게임 초기화 함수
def initialize_game():
# 카드 값 생성 (0부터 GRID_SIZE[0]*GRID_SIZE[1]/2-1까지의 숫자 페어)
card_values = list(range(GRID_SIZE[0] * GRID_SIZE[1] // 2)) * 2
random.shuffle(card_values)
cards = []
index = 0
for row in range(GRID_SIZE[1]):
for col in range(GRID_SIZE[0]):
x = col * (CARD_WIDTH + CARD_MARGIN) + CARD_MARGIN
y = row * (CARD_HEIGHT + CARD_MARGIN) + CARD_MARGIN
cards.append(Card(card_values[index], x, y))
index += 1
return cards
# 게임 메인 함수
def main():
cards = initialize_game()
clock = pygame.time.Clock()
flipped_cards = []
matched_pairs = 0
total_pairs = GRID_SIZE[0] * GRID_SIZE[1] // 2
attempts = 0
game_over = False
wait_time = None
start_time = time.time()
# 게임 루프
running = True
while running:
current_time = time.time()
elapsed_time = int(current_time - start_time)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_r:
# 게임 재시작
cards = initialize_game()
flipped_cards = []
matched_pairs = 0
attempts = 0
game_over = False
start_time = time.time()
if event.key == pygame.K_q or event.key == pygame.K_ESCAPE:
running = False
if not game_over and event.type == pygame.MOUSEBUTTONDOWN and wait_time is None:
pos = pygame.mouse.get_pos()
for card in cards:
if card.is_clicked(pos) and len(flipped_cards) < 2:
card.flip()
flipped_cards.append(card)
if len(flipped_cards) == 2:
attempts += 1
# 두 카드가 일치하는지 확인
if flipped_cards[0].value == flipped_cards[1].value:
flipped_cards[0].matched = True
flipped_cards[1].matched = True
matched_pairs += 1
if match_sound:
match_sound.play()
flipped_cards = []
# 모든 카드 매치 확인
if matched_pairs == total_pairs:
game_over = True
if win_sound:
win_sound.play()
else:
# 일치하지 않으면 잠시 후 다시 뒤집기
wait_time = current_time + 1 # 1초 대기
if wrong_sound:
wrong_sound.play()
# 정해진 시간이 지나면 일치하지 않는 카드 뒤집기
if wait_time and current_time >= wait_time:
for card in flipped_cards:
card.flipped = False
flipped_cards = []
wait_time = None
# 화면 그리기
screen.fill(WHITE)
# 카드 그리기
for card in cards:
card.draw(screen)
# 게임 정보 표시
info_text = f"시도: {attempts} 일치: {matched_pairs}/{total_pairs} 시간: {elapsed_time}초"
info_surface = small_font.render(info_text, True, BLACK)
screen.blit(info_surface, (CARD_MARGIN, WINDOW_HEIGHT - 80))
# 게임 종료 메시지
if game_over:
message = f"축하합니다! {attempts}번 시도, {elapsed_time}초 걸렸습니다!"
message_surface = font.render(message, True, BLACK)
restart_msg = "R키로 재시작, Q키로 종료"
restart_surface = small_font.render(restart_msg, True, BLACK)
# 메시지 배경
msg_rect = pygame.Rect(WINDOW_WIDTH//2 - message_surface.get_width()//2 - 10,
WINDOW_HEIGHT//2 - 50,
message_surface.get_width() + 20,
message_surface.get_height() + restart_surface.get_height() + 30)
pygame.draw.rect(screen, WHITE, msg_rect)
pygame.draw.rect(screen, BLACK, msg_rect, 2)
screen.blit(message_surface, (WINDOW_WIDTH//2 - message_surface.get_width()//2,
WINDOW_HEIGHT//2 - 40))
screen.blit(restart_surface, (WINDOW_WIDTH//2 - restart_surface.get_width()//2,
WINDOW_HEIGHT//2 + 10))
# 게임 조작 안내
controls = "조작: 마우스 클릭으로 카드 선택, R키로 재시작, Q키로 종료"
controls_surface = small_font.render(controls, True, BLACK)
screen.blit(controls_surface, (CARD_MARGIN, WINDOW_HEIGHT - 40))
pygame.display.flip()
clock.tick(60)
pygame.quit()
sys.exit()
if __name__ == "__main__":
main()
게임 화면 📸
화면 구성요소
- 카드 그리드: 4x4 형태로 배열된 카드들
- 게임 정보 표시: 하단에 시도 횟수, 찾은 짝 수, 경과 시간을 표시
- 게임 조작 안내: 화면 하단에 키 조작법 안내
- 결과 화면: 게임 종료 시 중앙에 결과와 재시작 안내 메시지 표시
코드 설명 📝
1. 게임 초기화 및 설정
# 초기화
pygame.init()
mixer.init()
# 색상 정의
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
# ...
# 게임 설정
CARD_WIDTH = 100
CARD_HEIGHT = 150
CARD_MARGIN = 20
GRID_SIZE = (4, 4) # 4x4 그리드
게임에 필요한 Pygame 라이브러리를 초기화하고, 사용할 색상과 카드 크기, 여백, 그리드 크기 등 기본 설정을 정의합니다.
2. Card 클래스
class Card:
def __init__(self, value, x, y):
self.value = value
self.x = x
self.y = y
# ...
def draw(self, screen):
# 카드 그리기 로직
def flip(self):
# 카드 뒤집기 로직
def is_clicked(self, pos):
# 카드 클릭 감지 로직
카드의 속성(값, 위치, 상태 등)과 동작(그리기, 뒤집기, 클릭 감지)을 관리하는 클래스입니다. 객체 지향 프로그래밍의 장점을 활용하여 코드를 구조화했습니다.
3. 게임 초기화 함수
def initialize_game():
# 카드 값 생성 (0부터 GRID_SIZE[0]*GRID_SIZE[1]/2-1까지의 숫자 페어)
card_values = list(range(GRID_SIZE[0] * GRID_SIZE[1] // 2)) * 2
random.shuffle(card_values)
cards = []
# ... 카드 배치 로직
return cards
게임 시작 시 카드 값을 생성하고 랜덤하게 섞은 후, 화면에 배치하는 함수입니다.
4. 메인 게임 루프
def main():
# 변수 초기화
# 게임 루프
running = True
while running:
# 이벤트 처리
# 카드 매칭 로직
# 화면 그리기
# 게임 정보 표시
게임의 메인 루프입니다. 이벤트 처리, 카드 매칭 로직, 화면 그리기 등 게임의 핵심 기능을 수행합니다.
게임 실행 방법 🚀
1. 필요한 라이브러리 설치:
pip install pygame
2. 사운드 파일 준비:
- flip.wav: 카드 뒤집기 효과음
- match.wav: 카드 매칭 성공 효과음
- win.wav: 게임 승리 효과음
- wrong.wav: 카드 매칭 실패 효과음
3. 코드를 memory_game.py 파일로 저장
4. 파이썬으로 실행:
python memory_game.py
게임에서 사용된 프로그래밍 개념 📚
- 객체 지향 프로그래밍 (OOP)
- Card 클래스를 통해 카드의 속성과 메서드를 캡슐화하여 관리
- 객체(카드) 간의 상호작용을 통한 게임 로직 구현
- 이벤트 기반 프로그래밍
- Pygame의 이벤트 시스템을 활용하여 마우스 클릭, 키보드 입력 등의 이벤트 처리
- 시간 관리
- time 모듈과 Pygame의 Clock 객체를 활용하여 게임 타이밍 관리
- 카드 뒤집기 딜레이, 경과 시간 계산 등에 활용
- 랜덤화
- random.shuffle() 함수를 사용하여 카드 배치 랜덤화
- 그래픽 인터페이스
- Pygame 라이브러리를 활용한 그래픽 요소 구현
- 직접 그리기 함수를 활용한 카드 디자인 및 UI 구성
- 사운드 효과
- Pygame의 mixer 모듈을 활용한 게임 사운드 효과 구현
알고리즘 설명 🧮
1. 카드 매칭 알고리즘
if len(flipped_cards) == 2:
attempts += 1
# 두 카드가 일치하는지 확인
if flipped_cards[0].value == flipped_cards[1].value:
flipped_cards[0].matched = True
flipped_cards[1].matched = True
matched_pairs += 1
# ...
else:
# 일치하지 않으면 잠시 후 다시 뒤집기
wait_time = current_time + 1 # 1초 대기
두 카드를 선택했을 때 값을 비교하여 일치 여부를 판단합니다. 일치하면 matched 상태로 변경하고, 일치하지 않으면 잠시 후 다시 뒤집습니다.
2. 게임 종료 조건 확인
if matched_pairs == total_pairs:
game_over = True
if win_sound:
win_sound.play()
찾은 짝의 수가 전체 짝의 수와 같아지면 게임을 종료합니다.
3. 타이머 로직
start_time = time.time()
# ...
current_time = time.time()
elapsed_time = int(current_time - start_time)
게임 시작 시간을 저장하고, 현재 시간과의 차이를 계산하여 경과 시간을 표시합니다.
확장 가능성 및 개선 아이디어 💡
- 난이도 조절: 그리드 크기를 조절하거나 제한 시간을 추가하여 난이도를 조절할 수 있습니다.
- 테마 추가: 숫자 대신 다양한 이미지나 테마(동물, 과일, 캐릭터 등)를 추가할 수 있습니다.
- 애니메이션 개선: 카드 뒤집기 애니메이션을 더 부드럽게 구현할 수 있습니다.
- 점수 시스템: 시도 횟수와 시간을 기반으로 점수를 계산하는 시스템을 추가할 수 있습니다.
- 멀티플레이어 모드: 두 명 이상의 플레이어가 번갈아가며 진행하는 모드를 구현할 수 있습니다.
다음 포스팅 예고 🔮
다음 포스팅에서는 '파이썬으로 만드는 미니 RPG 게임'에 대해 알아보겠습니다. 텍스트 기반 RPG 게임의 기본 구조를 설계하고, 캐릭터 클래스, 전투 시스템, 인벤토리 관리 등 RPG의 핵심 요소들을 구현해볼 예정입니다. 객체 지향 프로그래밍의 장점을 살려 확장 가능한 게임 구조를 설계하는 방법에 대해 배워보겠습니다.
여러분의 의견과 질문은 언제나 환영합니다. 행복한 코딩 되세요! 🐍🎮✨
'파이썬 기초문법 > 파이썬 게임 만들기' 카테고리의 다른 글
파이썬 게임 만들기 - 테트리스 🎲🎯 (1) | 2025.03.15 |
---|---|
파이썬으로 게임 만들기 - 2048 게임 🧠🎲 (0) | 2025.03.15 |
파이썬 게임 만들기 - 블랙잭 🃏♠️ (0) | 2025.03.11 |
파이썬 게임 만들기 - 전략 시뮬레이션 게임(Strategy Simulation Game) 🏰⚔️ (0) | 2025.03.11 |
파이썬 게임 만들기 - 플랫포머 게임(Platformer Game) 🏃♂️🌟 (1) | 2025.03.09 |