파이썬 기초문법/파이썬 게임 만들기

파이썬 게임 만들기 - 메모리 매칭 게임 🧠🃏

Family in August 2025. 3. 11. 21:21
반응형

파이썬으로 시작하는 게임 개발의 세계 🐍🎮

안녕하세요! 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()

 

게임 화면 📸

게임 화면

화면 구성요소

  1. 카드 그리드: 4x4 형태로 배열된 카드들
  2. 게임 정보 표시: 하단에 시도 횟수, 찾은 짝 수, 경과 시간을 표시
  3. 게임 조작 안내: 화면 하단에 키 조작법 안내
  4. 결과 화면: 게임 종료 시 중앙에 결과와 재시작 안내 메시지 표시

코드 설명 📝

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

게임에서 사용된 프로그래밍 개념 📚

  1. 객체 지향 프로그래밍 (OOP)
    • Card 클래스를 통해 카드의 속성과 메서드를 캡슐화하여 관리
    • 객체(카드) 간의 상호작용을 통한 게임 로직 구현
  2. 이벤트 기반 프로그래밍
    • Pygame의 이벤트 시스템을 활용하여 마우스 클릭, 키보드 입력 등의 이벤트 처리
  3. 시간 관리
    • time 모듈과 Pygame의 Clock 객체를 활용하여 게임 타이밍 관리
    • 카드 뒤집기 딜레이, 경과 시간 계산 등에 활용
  4. 랜덤화
    • random.shuffle() 함수를 사용하여 카드 배치 랜덤화
  5. 그래픽 인터페이스
    • Pygame 라이브러리를 활용한 그래픽 요소 구현
    • 직접 그리기 함수를 활용한 카드 디자인 및 UI 구성
  6. 사운드 효과
    • 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)

게임 시작 시간을 저장하고, 현재 시간과의 차이를 계산하여 경과 시간을 표시합니다.

확장 가능성 및 개선 아이디어 💡

  1. 난이도 조절: 그리드 크기를 조절하거나 제한 시간을 추가하여 난이도를 조절할 수 있습니다.
  2. 테마 추가: 숫자 대신 다양한 이미지나 테마(동물, 과일, 캐릭터 등)를 추가할 수 있습니다.
  3. 애니메이션 개선: 카드 뒤집기 애니메이션을 더 부드럽게 구현할 수 있습니다.
  4. 점수 시스템: 시도 횟수와 시간을 기반으로 점수를 계산하는 시스템을 추가할 수 있습니다.
  5. 멀티플레이어 모드: 두 명 이상의 플레이어가 번갈아가며 진행하는 모드를 구현할 수 있습니다.

다음 포스팅 예고 🔮

다음 포스팅에서는 '파이썬으로 만드는 미니 RPG 게임'에 대해 알아보겠습니다. 텍스트 기반 RPG 게임의 기본 구조를 설계하고, 캐릭터 클래스, 전투 시스템, 인벤토리 관리 등 RPG의 핵심 요소들을 구현해볼 예정입니다. 객체 지향 프로그래밍의 장점을 살려 확장 가능한 게임 구조를 설계하는 방법에 대해 배워보겠습니다.

여러분의 의견과 질문은 언제나 환영합니다. 행복한 코딩 되세요! 🐍🎮✨

반응형