강의

멘토링

로드맵

인프런 커뮤니티 질문&답변

윤현준님의 프로필 이미지
윤현준

작성한 질문수

코드 질문 있어요!

작성

·

121

0

#Bingo Game
import random
class BingoBoard:
    def __init__(self,name):
        self.name=name
    
        self.board=[]
    def create(self, pool):
        self.board= random.sample(pool,k=25)
    def __repr__(self):
        out=self.name+'\n'
        for i,k in enumerate(self.board):
            out += f"{k:3} "
            if i % 5 == 4:
                out += '\n'
        return out
    def update(self,num):
        self.num=num
        if self.num in self.board:
            self.board[self.board.index(self.num)] = 0
    def getLinesCompleted(self):
        self.result=[]
        for i in range(5):
            self.result.append(self.board[6*i:5*(i+1)].count(0))#가로
            self.result.append(self.board[i::5].count(0))#세로
        self.result.append(self.board[0::6].count(0))#대각선 우하향
        self.result.append(self.board[4:-1:4].count(0))#대각선 좌상향
        return self.result #길이12 #0을 카운트 한 숫자,,? 각 가로줄에 0이 몇 개 (총 5줄)... 총 12개의 숫자 반환. 5가 반환되면 줄 하나 완성
    def next(self):
        #지우고 싶은 숫자를 선택
        pass

class HumanBingoBoard(BingoBoard):
    def next(self):
        a=int(input('지우고 싶은 숫자 입력: '))
        return a
    
class ComputerBingoBoard(BingoBoard):
    def next(self):
        cand = [x for x in self.board if x > 0] #0이 아닌 숫자만 고르게 됨.
        return random.sample(cand, k=1)[0] #리스트 형 숫자 반환이라 0번째 숫자를 인덱싱 해줌



class MyBingoBoard(BingoBoard):
    def next(self):
        # 빙고판 중앙 선택
        if self.board[12] != 0:
            return self.board[12]
        # 각 라인에서 0의 개수를 계산, 각 라인을 리스트에 담아둠.
        
        
        lines = [
            self.board[0::6].count(0),  # 대각선1
            self.board[4:-1:4].count(0)  # 대각선2
            ]
        
        dia_lines=[
            self.board[0::6],  # 대각선1
            self.board[4:-1:4]  # 대각선2
            ]
        
        lines2 = [
            self.board[0:5].count(0),
            self.board[5:10].count(0),
            self.board[10:15].count(0),
            self.board[15:20].count(0),
            self.board[20:25].count(0)
            ]
        
        hor_lines=[
            self.board[0:5],
            self.board[5:10],
            self.board[10:15],
            self.board[15:20],
            self.board[20:25]
        ]

        lines3 = [
            self.board[0::5].count(0),
            self.board[1::5].count(0),
            self.board[2::5].count(0),
            self.board[3::5].count(0),
            self.board[4::5].count(0)
        ]
        
        ver_lines = [
            self.board[0::5],
            self.board[1::5],
            self.board[2::5],
            self.board[3::5],
            self.board[4::5]
        ]    
            
        if max(lines) !=5:
            a=max(lines)
        elif min(lines) !=5:
            a=min(lines)
        else:
            a=-1 #게임이 끝나기 전 두 대각선이 먼저 완성되었을 경우 대각선이 게임에 방해를 주지 않게 하기 위해서이다.
        if max(lines2) !=5:
            b=max(lines2)
        else:
            for i in range(5):
                if lines2[i]==5:
                    lines2[i]=-1
            b=max(lines2)
        if max(lines3) !=5:
            c=max(lines3)
        else:
            for i in range(5):
                if lines3[i]==5:
                    lines3[i]=-1
            c=max(lines3)

        def dia():
            selected_index = lines.index(max(lines)) #대각선에서 숫자고르기
            selected_line = dia_lines[selected_index]
            for i in range(5):
                if selected_line[i] !=0:
                    return selected_line[i]
            
        def hor(): #가로
            selected_index = lines2.index(max(lines2))
            selected_line = hor_lines[selected_index]
            for i in range(5): 
                if selected_line[i] != 0:
                    return selected_line[i]
            
        def ver(): #세로
            selected_index = lines3.index(max(lines3))
            selected_line = ver_lines[selected_index]
            for i in range(5):
                if selected_line[i] !=0:
                    return selected_line[i]
            
        
        if a==b and b==c:
            return dia()
        elif a==b and b>c:
            dia()
        elif a==b and b<c:
            ver()
        elif a==c and c>b:
            dia()
        elif a==c and c<b:
            hor()
        elif b==c and c>a:
            ver()
        elif b==c and c<a:
            dia()
        elif b>c:
            if a>b:
                dia()
            else:#가로
                hor()         
        elif c>b:
            if a>c:
                dia()
            else:#세로
                ver()
        else:
        # 특별한 무언가가 없는 경우
            cand = [x for x in self.board if x > 0] #0이 아닌 숫자만 고르게 됨.
            return random.sample(cand, k=1)[0]


class BingoGame:

    def __init__(self):
        self.pool = list(range(1, 26))
        self.user = MyBingoBoard('player1')
        self.com = ComputerBingoBoard('player2')
        self.user.create(self.pool)
        self.com.create(self.pool)

    # Refactoring: 동작은 유지, 코드만 개선

    def run_by(self, bot):
        bot_input = bot.next()
        self.user.update(bot_input)
        self.com.update(bot_input)
        ucl = self.user.getLinesCompleted().count(5)
        ccl = self.com.getLinesCompleted().count(5)
        if ucl >= 4 and ccl < 4:
            #print(self.user.name, 'wins')
            return 0
        elif ccl >= 4 and ucl < 4:
            #print(self.com.name, 'wins')
            return 1
        elif ccl >= 4 and ucl >= 4:
            #print('draw')
            return 2
        else:
            return -1

    def run(self):

        while True:

            out = self.run_by(self.user)
            if out >= 0:
                break
            out = self.run_by(self.com)
            if out >= 0:
                break

        return out

stat = [0, 0, 0]
for k in range(1000):
    game = BingoGame()
    stat[game.run()] += 1

print(stat)

빙고 게임 만드는 코드입니다.
빙고 게임을 실행 시키면 생성된 두 빙고판이 MyBingoBoard와 ComputerBingoBoard를 활용해서 게임을 하게 되고, 약 1000판에서 승리, 패배, 무승부 횟수를 출력하게 끔 해뒀습니다.
제 목표는 MyBingoBoard를 활용하는 빙고판이 게임을 압도적으로 이기는 것을 목적으로 만들긴 했는데, 파이썬을 접한 지 얼마 안 되어서 최적화하는 것도 어렵고 나중에 헷갈릴까봐 하나하나 대입한 감이 없지 않아 있습니다. 이런 건 무시해 주시고 MyBingoBoard 클래스만 건드려서 좀 더 압도적으로 이기게 끔 할 수 없을까요? 들은 걸론 900회 이상을 이기게 끔 할 수 있다고 했는데, 아무리해도 550회를 넘기는 게 힘드네요..
처음 이 사이트를 사용해서 실수한 게 있다면 지적해주셔도 감사하겠습니다.

답변 1

0

안녕하세요, 인프런 AI 인턴입니다. 윤현준님의 질문에 답변드리겠습니다.

빙고 게임에서 MyBingoBoard 클래스를 통해 더 높은 승률을 달성하기 위해선 매 턴마다 보다 전략적으로 숫자를 선택하는 논리를 구현해야 합니다. 현재의 로직에서는 빙고판의 어떤 위치에서 숫자를 지울지 다양한 조건을 확인하고 있지만, 이를 좀 더 효율적이고 전략적으로 개선할 수 있을 여지가 있습니다. 예를 들어, 아직 완성되지 않은 줄 중에서 가장 많은 숫자를 지워가는 줄에 집중하여 숫자를 지우거나, 상대방이 승리할 가능성이 높은 줄에 있는 숫자를 방해하는 식으로 할 수 있습니다.

전략을 개선하기 위한 몇 가지 아이디어는 다음과 같습니다:

  1. getLinesCompleted 메소드의 반환값을 활용해 각 줄의 완성도를 참고하여 현재 가장 빙고가 완성될 가능성이 높은 줄에 있는 숫자를 우선적으로 지우는 로직으로 변경합니다. 이때, ‘5’가 반환된 줄은 이미 완성된 줄로 간주하고 무시해야 합니다.

  2. next 메소드에서는 각 가로줄, 세로줄, 대각선별로 몇 개의 숫자가 남아있는지를 체크하고, 가장 숫자가 적게 남은 줄(가장 승리 가능성이 높은 줄)에 있는 숫자를 선택합니다.

  3. 상대방이 쉽게 빙고를 완성할 수 있는 숫자가 있으면 방해하도록 로직을 추가할 수 있으며, 승리에 필요한 개수보다 하나 적은 숫자가 있는 줄에 대해서만 이러한 방해 전략을 적용합니다.

  4. 중앙 칸을 활용하는 전략은 중요하지만, 승리 조건에 더 가까운 선택이 있다면 우선순위를 낮출 수 있습니다.

  5. 무작위 선택을 하는 부분(random.sample)은 마지막에 선택이 어렵거나 전략적으로 중요도가 낮은 경우에만 사용하도록 필터링합니다.

위의 전략들을 코드에 반영하여 MyBingoBoard 클래스의 next 메소드를 수정해 보세요. 대부분의 경우에 승리하기 위한 가장 좋은 수(숫자)를 선택하도록 논리 개선을 하다 보면 승률을 높일 수 있을 것입니다. 그 과정에서 반복문, 조건문을 효율적으로 활용하는 연습을 하게 될 겁니다. 파이썬을 접한 지 얼마 되지 않았다면 실전 문제를 해결하면서 많은 학습이 이루어질 수 있을 거라 생각합니다. 화이팅하세요!

추가 질문이 있으시면 언제든지 문의해 주시길 바랍니다.

윤현준님의 프로필 이미지
윤현준

작성한 질문수

질문하기