반응형
파이썬으로 시작하는 게임 개발의 세계 🐍🎮
안녕하세요! Python Game Dev 블로그의 열두 번째 포스팅입니다. 지난 포스팅에서는 전략 시뮬레이션 게임을, 그 전에는 플랫포머 게임을 만들어 보았는데요. 오늘은 약속드린 대로 '카드 게임(Card Game)'을 파이썬으로 구현해보겠습니다.
오늘의 게임: 파이썬으로 만드는 카드 게임 - 블랙잭 🃏♠️
카드 게임은 전 세계적으로 사랑받는 게임 장르 중 하나입니다. 오늘은 클래식한 카드 게임인 '블랙잭'을 파이썬으로 구현해보겠습니다. 텍스트 기반 버전과 Pygame을 활용한 그래픽 버전, 두 가지로 접근하여 여러분의 실력과 취향에 맞게 선택할 수 있도록 준비했습니다.
게임의 규칙 📜
블랙잭(21점)의 기본 규칙은 다음과 같습니다:
- 플레이어와 딜러는 각각 카드를 받습니다.
- 카드의 숫자 합이 21에 가까우면서 21을 넘지 않는 사람이 이깁니다.
- 숫자 카드(2-10)는 카드에 표시된 숫자대로 점수가 계산됩니다.
- 페이스 카드(J, Q, K)는 모두 10점입니다.
- 에이스(A)는 1점 또는 11점으로 계산할 수 있으며, 유리한 쪽으로 적용됩니다.
- 처음에 딜러는 카드 한 장을 공개합니다.
- 플레이어는 카드를 더 받거나(히트) 멈출(스탠드) 수 있습니다.
- 플레이어가 21점을 초과하면 즉시 패배합니다(버스트).
- 플레이어가 스탠드하면 딜러는 17점 이상이 될 때까지 카드를 계속 뽑아야 합니다.
전체 코드 - 텍스트 기반 💻
import random
import time
class Card:
def __init__(self, suit, value):
self.suit = suit
self.value = value
def __str__(self):
return f"{self.value} of {self.suit}"
def get_numeric_value(self):
if self.value in ['J', 'Q', 'K']:
return 10
elif self.value == 'A':
return 11
else:
return int(self.value)
class Deck:
def __init__(self):
self.cards = []
self.build()
def build(self):
suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades']
values = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
self.cards = [Card(suit, value) for suit in suits for value in values]
def shuffle(self):
random.shuffle(self.cards)
def deal(self):
if len(self.cards) > 0:
return self.cards.pop()
else:
return None
class Hand:
def __init__(self):
self.cards = []
self.value = 0
def add_card(self, card):
self.cards.append(card)
def calculate_value(self):
self.value = 0
has_ace = False
for card in self.cards:
self.value += card.get_numeric_value()
if card.value == 'A':
has_ace = True
# 에이스를 11로 계산했을 때 합이 21을 초과하면 에이스를 1로 계산
if has_ace and self.value > 21:
self.value -= 10
return self.value
def display(self, show_all_dealer_cards=True):
for i, card in enumerate(self.cards):
if i == 0 and not show_all_dealer_cards:
print("Hidden Card")
else:
print(card)
if show_all_dealer_cards:
print(f"Total Value: {self.calculate_value()}")
print()
class Blackjack:
def __init__(self):
self.deck = Deck()
self.deck.shuffle()
self.player_hand = Hand()
self.dealer_hand = Hand()
self.money = 1000
self.bet = 0
def deal_initial_cards(self):
for _ in range(2):
self.player_hand.add_card(self.deck.deal())
self.dealer_hand.add_card(self.deck.deal())
def place_bet(self):
while True:
try:
print(f"현재 보유금: ${self.money}")
self.bet = int(input("얼마를 베팅하시겠습니까? $"))
if 0 < self.bet <= self.money:
break
else:
print("유효한 베팅 금액을 입력해주세요.")
except ValueError:
print("숫자를 입력해주세요.")
def player_turn(self):
while True:
print("\n--- 플레이어의 카드 ---")
self.player_hand.display()
# 블랙잭 체크
if self.player_hand.calculate_value() == 21:
print("블랙잭! 플레이어 승리!")
return True
# 버스트 체크
elif self.player_hand.calculate_value() > 21:
print("버스트! 플레이어 패배!")
return False
# 히트 또는 스탠드 선택
choice = input("히트(h) 또는 스탠드(s)? ").lower()
if choice == 'h':
self.player_hand.add_card(self.deck.deal())
elif choice == 's':
break
else:
print("'h' 또는 's'를 입력해주세요.")
return None
def dealer_turn(self):
print("\n--- 딜러의 카드 ---")
self.dealer_hand.display()
# 딜러는 17 이상이 될 때까지 카드를 계속 뽑음
while self.dealer_hand.calculate_value() < 17:
print("딜러가 카드를 뽑습니다...")
time.sleep(1)
self.dealer_hand.add_card(self.deck.deal())
self.dealer_hand.display()
# 버스트 체크
if self.dealer_hand.calculate_value() > 21:
print("딜러 버스트! 플레이어 승리!")
return True
return None
def determine_winner(self):
player_value = self.player_hand.calculate_value()
dealer_value = self.dealer_hand.calculate_value()
print(f"\n플레이어: {player_value}")
print(f"딜러: {dealer_value}")
if player_value > dealer_value:
print("플레이어 승리!")
return True
elif dealer_value > player_value:
print("딜러 승리!")
return False
else:
print("무승부!")
return None
def update_money(self, result):
if result is True: # 플레이어 승리
self.money += self.bet
elif result is False: # 딜러 승리
self.money -= self.bet
# 무승부는 돈 변동 없음
def display_game_state(self):
print("\n" + "="*50)
print("\n--- 딜러의 카드 ---")
self.dealer_hand.display(show_all_dealer_cards=False)
print("\n--- 플레이어의 카드 ---")
self.player_hand.display()
print("="*50 + "\n")
def play(self):
print("블랙잭 게임에 오신 것을 환영합니다!")
while self.money > 0:
# 게임 초기화
self.player_hand = Hand()
self.dealer_hand = Hand()
# 베팅
self.place_bet()
# 초기 카드 배분
self.deal_initial_cards()
# 게임 상태 표시
self.display_game_state()
# 플레이어 턴
result = self.player_turn()
# 플레이어가 버스트하지 않았다면 딜러 턴
if result is None:
result = self.dealer_turn()
# 누구도 버스트하지 않았다면 승자 결정
if result is None:
result = self.determine_winner()
# 금액 업데이트
self.update_money(result)
print(f"현재 보유금: ${self.money}")
# 게임 계속 여부
if self.money <= 0:
print("모든 돈을 잃었습니다! 게임 오버!")
break
play_again = input("\n다시 플레이하시겠습니까? (y/n) ").lower()
if play_again != 'y':
print("게임을 종료합니다. 이용해주셔서 감사합니다!")
break
if __name__ == "__main__":
game = Blackjack()
game.play()
전체 코드 - 그래픽 기반 💻
import pygame
import random
import os
# 게임 초기화
pygame.init()
# 화면 설정
WIDTH, HEIGHT = 1000, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("파이썬 블랙잭")
# 색상 정의
GREEN = (34, 139, 34)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
# 폰트 설정
font = pygame.font.SysFont('malgungothic', 32)
small_font = pygame.font.SysFont('malgungothic', 24)
# 카드 클래스
class Card:
def __init__(self, suit, value):
self.suit = suit
self.value = value
# 카드 이미지 파일명 형식: {value}_of_{suit}.png
# 이미지 파일이 있는 경우 로드, 없으면 텍스트로 표시
self.image_file = f"{value.lower()}_of_{suit.lower()}.png"
self.image = self.load_image()
def load_image(self):
try:
# 이미지 파일이 있는 경로 (이미지 폴더 경로로 수정 필요)
card_image_path = os.path.join("card_images", self.image_file)
image = pygame.image.load(card_image_path)
return pygame.transform.scale(image, (80, 120))
except:
# 이미지 파일이 없으면 빈 카드 생성
image = pygame.Surface((80, 120))
image.fill(WHITE)
pygame.draw.rect(image, BLACK, (0, 0, 80, 120), 2)
# 카드 값과 모양 텍스트 렌더링
text = small_font.render(f"{self.value}", True, BLACK)
image.blit(text, (10, 10))
# 모양에 따라 색상 변경
if self.suit in ["Hearts", "Diamonds"]:
color = RED
else:
color = BLACK
suit_symbol = {"Hearts": "♥", "Diamonds": "♦", "Clubs": "♣", "Spades": "♠"}
suit_text = font.render(suit_symbol.get(self.suit, "?"), True, color)
image.blit(suit_text, (30, 50))
return image
def __str__(self):
return f"{self.value} of {self.suit}"
def get_numeric_value(self):
if self.value in ['J', 'Q', 'K']:
return 10
elif self.value == 'A':
return 11
else:
return int(self.value)
# 덱 클래스
class Deck:
def __init__(self):
self.cards = []
self.build()
def build(self):
suits = ['Hearts', 'Diamonds', 'Clubs', 'Spades']
values = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
self.cards = [Card(suit, value) for suit in suits for value in values]
def shuffle(self):
random.shuffle(self.cards)
def deal(self):
if len(self.cards) > 0:
return self.cards.pop()
else:
return None
# 핸드(손패) 클래스
class Hand:
def __init__(self):
self.cards = []
self.value = 0
def add_card(self, card):
self.cards.append(card)
def calculate_value(self):
self.value = 0
has_ace = False
for card in self.cards:
self.value += card.get_numeric_value()
if card.value == 'A':
has_ace = True
# 에이스를 11로 계산했을 때 합이 21을 초과하면 에이스를 1로 계산
if has_ace and self.value > 21:
self.value -= 10
return self.value
# 블랙잭 게임 클래스
class BlackjackGame:
def __init__(self):
self.deck = Deck()
self.deck.shuffle()
self.player_hand = Hand()
self.dealer_hand = Hand()
self.game_over = False
self.player_wins = False
self.message = ""
self.money = 1000
self.bet = 100
self.show_dealer_cards = False
self.game_started = False
def deal_initial_cards(self):
self.player_hand = Hand()
self.dealer_hand = Hand()
self.game_over = False
self.player_wins = False
self.message = ""
self.show_dealer_cards = False
# 각자 2장의 카드 배분
for _ in range(2):
self.player_hand.add_card(self.deck.deal())
self.dealer_hand.add_card(self.deck.deal())
self.game_started = True
# 플레이어 블랙잭 체크
if self.player_hand.calculate_value() == 21:
self.message = "블랙잭! 플레이어 승리!"
self.player_wins = True
self.game_over = True
self.show_dealer_cards = True
self.money += self.bet
def hit(self):
if not self.game_over and self.game_started:
# 플레이어가 카드를 한 장 더 받음
self.player_hand.add_card(self.deck.deal())
# 버스트 체크
if self.player_hand.calculate_value() > 21:
self.message = "버스트! 플레이어 패배!"
self.player_wins = False
self.game_over = True
self.show_dealer_cards = True
self.money -= self.bet
def stand(self):
if not self.game_over and self.game_started:
self.show_dealer_cards = True
# 딜러 턴: 17 이상이 될 때까지 카드 뽑기
while self.dealer_hand.calculate_value() < 17:
self.dealer_hand.add_card(self.deck.deal())
# 딜러 버스트 체크
if self.dealer_hand.calculate_value() > 21:
self.message = "딜러 버스트! 플레이어 승리!"
self.player_wins = True
self.game_over = True
self.money += self.bet
else:
# 점수 비교
player_value = self.player_hand.calculate_value()
dealer_value = self.dealer_hand.calculate_value()
if player_value > dealer_value:
self.message = "플레이어 승리!"
self.player_wins = True
self.money += self.bet
elif dealer_value > player_value:
self.message = "딜러 승리!"
self.player_wins = False
self.money -= self.bet
else:
self.message = "무승부!"
self.game_over = True
def change_bet(self, amount):
if not self.game_started:
new_bet = self.bet + amount
if 10 <= new_bet <= self.money:
self.bet = new_bet
def draw(self, screen):
# 배경 그리기
screen.fill(GREEN)
# 제목 표시
title = font.render("Python 블랙잭", True, WHITE)
#screen.blit(title, (WIDTH // 2 - title.get_width() // 2, 20))
screen.blit(title, (20, HEIGHT // 2 - title.get_height() // 2))
# 보유금과 베팅액 표시
money_text = small_font.render(f"보유금: ${self.money}", True, WHITE)
bet_text = small_font.render(f"베팅액: ${self.bet}", True, WHITE)
screen.blit(money_text, (50, 20))
screen.blit(bet_text, (50, 50))
# 베팅 금액 조절 버튼
if not self.game_started:
pygame.draw.rect(screen, WHITE, (200, 45, 30, 30))
pygame.draw.rect(screen, WHITE, (240, 45, 30, 30))
minus = font.render("-", True, BLACK)
plus = font.render("+", True, BLACK)
screen.blit(minus, (210, 45))
screen.blit(plus, (250, 45))
# 게임 버튼 그리기
if not self.game_started:
pygame.draw.rect(screen, WHITE, (WIDTH // 2 - 100, 500, 200, 50))
deal_text = font.render("게임 시작", True, BLACK)
screen.blit(deal_text, (WIDTH // 2 - deal_text.get_width() // 2, 510))
else:
if not self.game_over:
# 히트 버튼
pygame.draw.rect(screen, WHITE, (WIDTH // 2 - 150, 500, 100, 50))
hit_text = font.render("히트", True, BLACK)
screen.blit(hit_text, (WIDTH // 2 - 150 + 50 - hit_text.get_width() // 2, 510))
# 스탠드 버튼
pygame.draw.rect(screen, WHITE, (WIDTH // 2 + 50, 500, 100, 50))
stand_text = font.render("스탠드", True, BLACK)
screen.blit(stand_text, (WIDTH // 2 + 50 + 50 - stand_text.get_width() // 2, 510))
else:
# 다시 시작 버튼
pygame.draw.rect(screen, WHITE, (WIDTH // 2 - 100, 500, 200, 50))
deal_text = font.render("다시 시작", True, BLACK)
screen.blit(deal_text, (WIDTH // 2 - deal_text.get_width() // 2, 510))
# 카드 그리기 - 딜러
dealer_text = font.render("딜러", True, WHITE)
screen.blit(dealer_text, (WIDTH // 2 - dealer_text.get_width() // 2, 100))
# 딜러의 카드 표시
for i, card in enumerate(self.dealer_hand.cards):
if i == 0 and not self.show_dealer_cards:
# 첫 번째 카드는 뒷면으로 표시
card_back = pygame.Surface((80, 120))
card_back.fill(RED)
pygame.draw.rect(card_back, BLACK, (0, 0, 80, 120), 2)
screen.blit(card_back, (WIDTH // 2 - 90 + i * 90, 150))
else:
screen.blit(card.image, (WIDTH // 2 - 90 + i * 90, 150))
# 딜러의 점수 표시
if self.show_dealer_cards:
dealer_value = self.dealer_hand.calculate_value()
dealer_value_text = font.render(f"점수: {dealer_value}", True, WHITE)
#screen.blit(dealer_value_text, (WIDTH // 2 - dealer_value_text.get_width() // 2, 280))
screen.blit(dealer_value_text, (20, 150))
# 카드 그리기 - 플레이어
player_text = font.render("플레이어", True, WHITE)
screen.blit(player_text, (WIDTH // 2 - player_text.get_width() // 2, 320))
# 플레이어의 카드 표시
for i, card in enumerate(self.player_hand.cards):
screen.blit(card.image, (WIDTH // 2 - 90 + i * 90, 370))
# 플레이어의 점수 표시
player_value = self.player_hand.calculate_value()
player_value_text = font.render(f"점수: {player_value}", True, WHITE)
#screen.blit(player_value_text, (WIDTH // 2 - player_value_text.get_width() // 2, 450))
screen.blit(player_value_text, (20, 450))
# 게임 결과 메시지 표시
if self.message:
msg_text = font.render(self.message, True, WHITE)
screen.blit(msg_text, (WIDTH // 2 - msg_text.get_width() // 2, 30))
# 메인 게임 루프
def main():
game = BlackjackGame()
clock = pygame.time.Clock()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
# 게임 시작 전 베팅 금액 조절
if not game.game_started:
# 베팅 감소 버튼
if 200 <= mouse_pos[0] <= 230 and 45 <= mouse_pos[1] <= 75:
game.change_bet(-10)
# 베팅 증가 버튼
elif 240 <= mouse_pos[0] <= 270 and 45 <= mouse_pos[1] <= 75:
game.change_bet(10)
# 게임 시작/다시 시작 버튼
if not game.game_started or game.game_over:
if WIDTH // 2 - 100 <= mouse_pos[0] <= WIDTH // 2 + 100 and 500 <= mouse_pos[1] <= 550:
if game.money > 0:
game.deal_initial_cards()
# 게임 진행 중 - 히트와 스탠드 버튼
if game.game_started and not game.game_over:
# 히트 버튼
if WIDTH // 2 - 150 <= mouse_pos[0] <= WIDTH // 2 - 50 and 500 <= mouse_pos[1] <= 550:
game.hit()
# 스탠드 버튼
elif WIDTH // 2 + 50 <= mouse_pos[0] <= WIDTH // 2 + 150 and 500 <= mouse_pos[1] <= 550:
game.stand()
game.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
if __name__ == "__main__":
main()
게임 실행 방법 🚀
텍스트 기반 버전
- blackjack.py 파일을 만들고 첫 번째 코드를 복사합니다.
- 터미널에서 다음 명령어를 실행합니다: python blackjack.py
- 콘솔 창에서 지시에 따라 게임을 진행하세요.
그래픽 버전
- Pygame 패키지가 필요합니다. 설치되어 있지 않다면 다음 명령어로 설치하세요: pip install pygame
- blackjack_pygame.py 파일을 만들고 두 번째 코드를 복사합니다.
- 카드 이미지를 사용하려면 카드 이미지 세트를 다운로드하거나, 이미지 없이도 실행 가능합니다.
- 터미널에서 다음 명령어를 실행합니다: python blackjack_pygame.py
코드 설명 📝
공통 클래스 구조
두 버전 모두 다음 클래스를 공유합니다:
- Card 클래스: 카드의 모양(suit)과 값(value)을 저장하고, 숫자 값을 계산하는 메서드를 제공합니다.
- Deck 클래스: 52장의 카드를 생성, 섞기, 카드 뽑기 기능을 제공합니다.
- Hand 클래스: 플레이어나 딜러가 가진 카드를 관리하고 점수를 계산합니다.
게임에서 사용된 프로그래밍 개념 📚
- 클래스와 객체지향 프로그래밍:
- Card, Deck, Hand, Game 클래스를 통해 실제 세계의 객체를 모델링
- 캡슐화를 통한 데이터와 기능의 묶음
- 리스트 조작:
- 카드 덱 생성 및 섞기
- 손에 든 카드 관리
- 랜덤화:
- random.shuffle()을 사용한 카드 덱 섞기
- 조건문과 루프:
- 게임 상태 확인 및 게임 흐름 제어
- 플레이어 입력에 따른 분기 처리
- 사용자 입력 처리:
- input() 함수를 통한 사용자 명령 처리
- 입력 유효성 검사
- 함수 구현:
- 각 클래스 내 메소드를 통한 기능 모듈화
- 코드 재사용성 향상
알고리즘 설명 🧮
- 카드 덱 초기화 알고리즘:
- 4개의 슈트(스페이드, 하트, 다이아몬드, 클럽)와 13개의 랭크(A,2~10,J,Q,K)를 조합하여 52장의 카드 생성
- Fisher-Yates 알고리즘을 기반으로 한 random.shuffle()을 사용하여 카드 무작위 배치
- 카드 점수 계산 알고리즘:
- 일반 카드: 숫자 그대로 점수 계산
- 페이스 카드(J,Q,K): 10점으로 계산
- 에이스(A): 합이 21을 넘지 않는 선에서 11점, 그렇지 않으면 1점으로 계산
- 에이스 처리를 위해 먼저 모든 에이스를 11점으로 계산한 후, 합이 21을 초과하면 필요한 만큼 에이스를 1점으로 조정
- 딜러 AI 알고리즘:
- 단순하지만 실제 카지노 규칙을 따르는 알고리즘
- 딜러의 카드 합이 17 미만이면 무조건 카드를 더 받음
- 17 이상이면 더 이상 카드를 받지 않음
- 승패 결정 알고리즘:
- 플레이어 또는 딜러가 버스트(21 초과)하면 즉시 패배
- 둘 다 버스트하지 않았다면, 카드 합이 더 높은 쪽이 승리
- 카드 합이 같으면 무승부(Push)
다음 포스팅 예고 🔮
다음 포스팅에서는 '파이썬으로 만드는 메모리 매칭 게임(Memory Matching Game)'에 대해 알아보겠습니다. Pygame 라이브러리를 활용하여 그래픽 인터페이스를 갖춘 카드 뒤집기 게임을 구현해볼 예정입니다. 게임 로직뿐만 아니라 이미지 처리, 애니메이션 효과, 사운드 추가 등 게임 개발의 다양한 측면을 배워보겠습니다.
여러분의 의견과 질문은 언제나 환영합니다. 행복한 코딩 되세요! 🐍🎴✨
반응형
'파이썬 기초문법 > 파이썬 게임 만들기' 카테고리의 다른 글
파이썬으로 게임 만들기 - 2048 게임 🧠🎲 (0) | 2025.03.15 |
---|---|
파이썬 게임 만들기 - 메모리 매칭 게임 🧠🃏 (0) | 2025.03.11 |
파이썬 게임 만들기 - 전략 시뮬레이션 게임(Strategy Simulation Game) 🏰⚔️ (0) | 2025.03.11 |
파이썬 게임 만들기 - 플랫포머 게임(Platformer Game) 🏃♂️🌟 (1) | 2025.03.09 |
파이썬 게임 만들기 - 간단한 던전 크롤러(Simple Dungeon Crawler) 🏰🗡️ (0) | 2025.03.09 |