본문 바로가기

카테고리 없음

내일배움캠프 2022.04.25 TIL

게임 개발 프로젝트

오늘부터 3일간 게임 개발 프로젝트를 진행한다. 내가 정한 주제는 맞고(2인용 고스톱)다. 이 게임을 선정한 이유는 우선 맞고의 규칙은 생각보다 예외도 많고, 점수 계산도 까다롭기 때문에 이번 교육 때 배운 python의 조건문, 반복문, 예외처리문 등을 연습하기에 적합하다고 생각했기 때문이다. 사실 더 큰 이유는 객체지향적으로 프로그래밍하는 연습을 하기에 적합하다고 생각하였다. 맞고라는 게임을 처음 떠올렸을 때 크게 생각난 클래스로는 User, Card 클래스 정도밖에 없었지만 코드 작성 이전에 클래스 간의 관계도를 그리다 보니 좋은 코드를 작성할 수 있을 것 같아 이 주제를 선택하게 되었다.

 

 

Card 클래스

name, month, card_type을 속성으로 가지는 클래스이다. name은 말 그대로 카드의 이름, month는 해당 카드가 몇 월에 해당하는 카드인지, card_type은 해당 카드가 광 카드인지 띠 카드인지 피 카드인지 등을 파악할 수 있도록 해주는 필드이다. Card 클래스를 통해 모든 화투패를 객체로 생성하였다. 객체로 생성한 코드는 다음과 같다.

JanGwang = Card("JanGwang", Month.Jan, CardType.Gwang)
JanTtee = Card("JanTtee", Month.Jan, CardType.RedTtee)
JanPee1 = Card("JanPee1", Month.Jan, CardType.Pee)
JanPee2 = Card("JanPee2", Month.Jan, CardType.Pee)

FebGodori = Card("FebGodori", Month.Feb, CardType.Godori)
FebTtee = Card("FebTtee", Month.Feb, CardType.RedTtee)
FebPee1 = Card("FebPee1", Month.Feb, CardType.Pee)
FebPee2 = Card("FebPee2", Month.Feb, CardType.Pee)

MarGwang = Card("MarGwang", Month.Mar, CardType.Gwang)
MarTtee = Card("MarTtee", Month.Mar, CardType.RedTtee)
MarPee1 = Card("MarPee1", Month.Mar, CardType.Pee)
MarPee2 = Card("MarPee2", Month.Mar, CardType.Pee)

AprGodori = Card("AprGodori", Month.Apr, CardType.Godori)
AprTtee = Card("AprTtee", Month.Apr, CardType.Ttee)
AprPee1 = Card("AprPee1", Month.Apr, CardType.Pee)
AprPee2 = Card("AprPee2", Month.Apr, CardType.Pee)

MayMeong = Card("MayMeong", Month.May, CardType.Meong)
MayTtee = Card("MayTtee", Month.May, CardType.Ttee)
MayPee1 = Card("MayPee1", Month.May, CardType.Pee)
MayPee2 = Card("MayPee2", Month.May, CardType.Pee)

JunMeong = Card("JunMeong", Month.Jun, CardType.Meong)
JunTtee = Card("JunTtee", Month.Jun, CardType.BlueTtee)
JunPee1 = Card("JunPee1", Month.Jun, CardType.Pee)
JunPee2 = Card("JunPee2", Month.Jun, CardType.Pee)

JulMeong = Card("JulMeong", Month.Jul, CardType.Meong)
JulTtee = Card("JulTtee", Month.Jul, CardType.Ttee)
JulPee1 = Card("JulPee1", Month.Jul, CardType.Pee)
JulPee2 = Card("JulPee2", Month.Jul, CardType.Pee)

AugGwang = Card("AugGwang", Month.Aug, CardType.Gwang)
AugGodori = Card("AugTtee", Month.Aug, CardType.Godori)
AugPee1 = Card("AugPee1", Month.Aug, CardType.Pee)
AugPee2 = Card("AugPee2", Month.Aug, CardType.Pee)

SepGukjin = Card("SepGukjin", Month.Sep, [CardType.Meong, CardType.SsangPee])  # 이 카드의 처리가 힘들지 않을까..?
SepTtee = Card("SepTtee", Month.Sep, CardType.BlueTtee)
SepPee1 = Card("SepPee1", Month.Sep, CardType.Pee)
SepPee2 = Card("SepPee2", Month.Sep, CardType.Pee)

OctMeong = Card("OctMeong", Month.Oct, CardType.Meong)
OctTtee = Card("OctTtee", Month.Oct, CardType.BlueTtee)
OctPee1 = Card("OctPee1", Month.Oct, CardType.Pee)
OctPee2 = Card("OctPee2", Month.Oct, CardType.Pee)

NovGwang = Card("NovGwang", Month.Nov, CardType.Gwang)
NovPee1 = Card("NovPee1", Month.Nov, CardType.Pee)
NovPee2 = Card("NovPee2", Month.Nov, CardType.Pee)
NovSsangPee = Card("NovSsangPee", Month.Nov, CardType.SsangPee)

DecBiGwang = Card("DecBiGwang", Month.Dec, CardType.BiGwang)
DecMeong = Card("DecMeong", Month.Dec, CardType.Meong)
DecBiTtee = Card("DecBiTtee", Month.Dec, CardType.BiTtee)
DecSsangPee = Card("DecSsangPee", Month.Dec, CardType.Pee)

Bonus1 = Card("Bonus1", Month.Bonus, CardType.SsangPee)
Bonus2 = Card("Bonus2", Month.Bonus, CardType.SsangPee)

cards = [JanGwang, JanTtee, JanPee1, JanPee2, FebGodori, FebTtee, FebPee1, FebPee2, MarGwang, MarTtee, MarPee1, MarPee2,
         AprGodori, AprTtee, AprPee1, AprPee2, MayMeong, MayTtee, MayPee1, MayPee2, JunMeong, JunTtee, JunPee1, JunPee2,
         JulMeong, JulTtee, JulPee1, JulPee2, AugGwang, AugGodori, AugPee1, AugPee2, SepGukjin, SepTtee, SepPee1,
         SepPee2, OctMeong, OctTtee, OctPee1, OctPee2, NovGwang, NovPee1, NovPee2, NovSsangPee, DecBiGwang, DecMeong,
         DecBiTtee, DecSsangPee, Bonus1, Bonus2]

약간의 Tip이라면 month나 card_type과 같은 필드도 새로운 class로 정의함으로써 좀 더 어떤 카드가 어떤 속성을 가지고 있는지 한 눈에 알아볼 수 있도록 정의하였다.

 

 

Cards 클래스

점수 계산을 어떻게 하면 좋을지 굉장히 많은 고민을 했다. 맞고는 내가 딴 패만을 보고 점수를 계산하는 것이 아니라, 현재 내가 Go를 몇 번 외쳤었는지, 혹은 내 손에 든 카드에 폭탄 패가 있지는 않은지, 아니면 운이 너무 안 좋아서 첫 패부터 총통이라 그대로 게임을 끝내버릴 수도 있는 게임이다. 

 

그래서 생각해낸 것이 Cards 클래스를 생성하고 그 클래스를 상속받아 CardsMatched(내가 딴 패), CardsOnTable(현재 바닥에 있는 보이는 패), CardsOnHand(현재 내가 손에 쥐고 있는 패) 클래스들을 생성하는 것이었다...!!!!! 다른 사람들은 어떻게 이런 생각을 바로바로 해내는지.. 아무튼! 이로써 문제를 해결해낼 수 있었다. 점수 계산 함수들은 대부분 CardsMatched 클래스에, 맞는 짝인지 확인해주는 함수는 CardsOnTable 클래스에, 총통이나 폭탄 여부는 CardsOnHand 클래스에 넣어두었다.

 

다음은 CardsMatched 클래스이다.

import Cards
import Card


class CardsMatched(Cards):
    def calcScore(self):
        self.score = 0
        self.type_cards = self.cardTypeArrange()
        self.peeScore()  # 멍보다 피를 먼저 계산해서 국진 패를 처리해주자!
        self.meongScore()
        self.gwangScore()
        self.tteeScore()
        return self.score

    def gwangScore(self):  # 광 계산
        gwang_cards = self.type_cards[Card.CardType.Gwang] + self.type_cards[Card.CardType.BiGwang]
        if len(gwang_cards) == 5:  # 광 패가 5장이면
            self.score += 15  # 15점 증가
        if len(gwang_cards) == 4:  # 광 패가 4장이면
            self.score += 4  # 4점 증가
        if len(gwang_cards) == 3:  # 광 패가 3장인데
            if self.type_cards[Card.CardType.BiGwang]:  # 비광이 존재한다면
                self.score += 2  # 2점 증가
            else:  # 그렇지 않다면
                self.score += 3  # 3점 증가

    def meongScore(self):  # 멍 계산
        meong_cards = self.type_cards[Card.CardType.Meong] + self.type_cards[Card.CardType.Godori]  # 멍 패 전부 (멍+고도리)
        godori_cards = self.type_cards[Card.CardType.Godori]
        if len(meong_cards) >= 5:  # 멍 패가 5장 이상이면
            self.score += len(meong_cards) - 4  # 5장부터 1점이니까, (멍 패 개수 - 4)점 증가
        if len(godori_cards) == 3:  # 고도리가 3장 다 모였다면
            self.score += 5  # 5점 증가

    def tteeScore(self): # 띠 계산
        ttee_cards = self.type_cards[Card.CardType.RedTtee] + self.type_cards[Card.CardType.BlueTtee] + self.type_cards[
            Card.CardType.Ttee] + self.type_cards[Card.CardType.BiTtee]  # 띠 패 전부 (홍단+청단+초단+비초단)
        redTtee_cards = self.type_cards[Card.CardType.RedTtee]  # 홍단
        blueTtee_cards = self.type_cards[Card.CardType.BlueTtee]  # 청단
        choTtee_cards = self.type_cards[Card.CardType.Ttee]  # 초단
        if len(ttee_cards) >= 5:  # 띠 패가 5장 이상이면
            self.score += len(ttee_cards) - 4  # 5장부터 1점이니까, (띠 패 개수 - 4)점 증가
        if len(redTtee_cards) == 3:  # 홍단이 3장 다 모였다면
            self.score += 3  # 3점 증가
        if len(blueTtee_cards) == 3:  # 청단이 3장 다 모였다면
            self.score += 3  # 3점 증가
        if len(choTtee_cards) == 3:  # 초단이 3장 다 모였다면
            self.score += 3  # 3점 증가

    def peeScore(self): # 피 계산
        pee_cards = self.type_cards[Card.CardType.Pee]  # 피
        ssangpee_cards = self.type_cards[Card.CardType.SsangPee]  # 쌍피
        add_score = len(pee_cards) + 2 * len(ssangpee_cards)
        if add_score >= 10:  # 10장부터 1점이니까
            if Card.SepGukjin in self.type_cards[Card.CardType.Meong]:  # 그런데 이때, 국진 패를 가지고 있다면
                self.type_cards[Card.CardType.Meong].remove(Card.SepGukjin)  # 국진을 멍 패에서 없애주고
                self.score += add_score + 2 - 9  # (피 패 개수 - 9)점 증가 인데, 2점을 더 더함
            else:  # 국진 패가 없다면
                self.score += add_score - 9  # (피 패 개수 - 9)점 증가

 

다음은 CardsOnTable 클래스이다.

import Cards


class CardsOnTable(Cards):
    def getPair(self, card): # 짝 맞는 것들 찾아주는 함수
        pair = []
        for match_card in self.cards:
            if match_card.month == card.month:
                pair.append(match_card)
        return pair

 

다음은 CardsOnHand 클래스이다.

import Cards


class CardsOnHand(Cards):
    def calcScore(self):
        self.score = 0
        self.month_cards = self.monthArrange()
        self.month_cnt = self.monthCount()

    def chongTong(self):  # 총통 여부
        for cnt in self.month_cnt:
            if cnt == 4:
                return True
        return False

    def getChongTongMonth(self):
        if not self.chongTong():
            return
        chongtong_month = 0
        for month in range(13):
            if self.month_cnt[month] == 4:
                chongtong_month.append(month)
        return chongtong_month[-1] # 제일 큰 총통의 달 수 리턴


    # 폭탄 처리 함수 만들기...?

 

 

Deck 클래스

나도 상대방도 볼 수 없는 카드들에 대한 클래스이다. random.shuffle() 함수를 이용하여 카드를 섞는 함수를 넣어두었다. 다음은 Deck 클래스이다.

import random
import Card # cards 변수를 사용하기 위함


class Deck(list):
    # 카드를 섞기 전 상태의 디폴트는 cards 상태 1~12월 순
    def __init__(self, before_shuffle=Card.cards):
        super(Deck, self).__init__(before_shuffle)

    # 카드를 섞는 함수
    def cardShuffle(self):
        random.shuffle(self)

 

 

Game 클래스

사실상 모든 게임은 여기서 이루어진다고 볼 수 있다. 현재 게임의 상태, 승자는 누구인지, player 1과 player 2의 손에 있는 카드는 각각 무엇인지 등 모든 정보는 이 클래스에서 사실 확인이 가능하다. 그래서 아직 많이 짜지는 못했지만, startGame() 함수를 생성하여 패를 섞고 2명의 player에게 패를 나눠주고 바닥에 패를 까는 것은 완성하였고, getWinner() 함수를 현재 짜고 있는데 아직은 총통일 경우 winner를 지정해버리는 정도밖에 진행하지 못하였다. 다음은 Game 클래스이다.

import Deck
import CardsMatched
import CardsOnTable
import CardsOnHand


class Game(object):
    def __init__(self):
        self.winner = None
        self.deck = Deck.Deck()
        self.cards_on_table = CardsOnTable.CardsOnTable()
        self.player1_hand = CardsOnHand.CardsOnHand()
        self.player2_hand = CardsOnHand.CardsOnHand()
        self.player1_matched = CardsMatched.CardsMatched()
        self.player2_matched = CardsMatched.CardsMatched()

    def startGame(self):
        self.deck.cardShuffle()  # 패 섞기
        for _ in range(2):  # 2번 반복
            for _ in range(5):  # player1에게 5장
                self.player1_hand += self.deck.pop()
            for _ in range(5):  # player2에게 5장
                self.player2_hand += self.deck.pop()
            for _ in range(4):  # 바닥에 4장
                self.cards_on_table += self.deck.pop()
        return self

    def getWinner(self):
        # 총통은 바로 승리 처리
        if self.player1_hand.chongTong() and self.player2_hand.chongTong():
            player1_chongtong_month = self.player1_hand.getChongTongMonth()
            player2_chongtong_month = self.player2_hand.getChongTongMonth()
            if player1_chongtong_month > player2_chongtong_month:
                self.winner = "player 1"
            else:
                self.winner = "player 2"
        elif self.player1_hand.chongTong():
            self.winner = "player 1"
        elif self.player2_hand.chongTong():
            self.winner = "player 2"
        else: # 여기부터 다시 처리해주기
            self.winner = None

 

 

User 클래스

user_name, ID, PW 그리고 맞고에서 가장 가장 중요한 seed를 속성으로 가지는 클래스이다. 아마도 MongoDB나 txt파일을 이용하여 회원 정보 및 시드머니를 저장해둘 것 같다. 너무 어려워지면 지울 의향도 있는 클래스..!