I'm FanJae.

[EA FC Pro club Draft Bot] 5. Draft Logic 구현 본문

Toy Project/EA FC Pro club Draft Bot [Python]

[EA FC Pro club Draft Bot] 5. Draft Logic 구현

FanJae 2024. 9. 4. 22:14

1. logging 설정

import logging

logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

- logging을 하도록 기록해두면,  오류를 잡는데 있어서 상당히 유용하다.

- 본인은 우선, 개발을 진행중이였기에 logging level을 DEBUG로 처리하였다.


2. 초기 셋팅

2-1. 기본 전제

- 프로클럽 모드에서 가장 많이 쓰이는 4-3-3 포메이션(DM)을 예시로 하였다.

가장 일반적으로 많이 하는 포메이션이고, 여러 인원이 처음 접하기 무난한 포메이션이다.

- 기본적인 드래프트 구현을 우선으로 했다. 남은 포지션 출력 및 충돌 처리는 고려하지 않았다.

- 충돌 처리가 정말 중요한 문제이므로, 별도의 포스트로 다룬다.

- 커스텀 이미지를 사용해야 한다.

- 한번 초기화되면 일반적으로 잘 바뀌지 않는 경우 상수처럼 대문자로 처리하였다.

 

2-2. 초기값 셋팅 (이모지 가져오기 및 ID값 셋팅)

# 비교적 상수처럼 쓰이는 값들 (EMOJI_MAPPING도 한번 값을 초기화 하면 변하지 않는다)
EMOJI_ID_TARGET = ['ST','LW','RW','CM','CDM','LB','CB','RB','GK']
EMOJI_MAPPING = {
    'ST': None,
    'LW': None,
    'RW': None,
    'CM': None,
    'CDM': None,
    'LB': None,
    'CB': None,
    'RB': None,
    'GK': None,
}
COUNT_TIME = 5
POSITION_COUNT = None
KEYS_LIST = []

async def setup_emojis(guild):
    if guild:
        emojis = await guild.fetch_emojis()
        global KEYS_LIST
        global POSITION_COUNT

        for emoji_key in EMOJI_MAPPING.keys():
            emoji = discord.utils.get(emojis, name=emoji_key)
            if emojis:
                EMOJI_MAPPING[emoji_key] = str(emoji.id)

        KEYS_LIST = list(EMOJI_MAPPING.keys())
        POSITION_COUNT = len(KEYS_LIST)

    else:
        print(f'Guild with ID {SERVER_ID} not found.')

- 이 코드는 이모지를 가져와서 ID값을 셋팅한다.

- 본인의 경우 봇이 실행될 때 이 코드를 실행하게 만들었으며, 추후에는 이 코드 실행 전까지 다른 어떤 명령어도 사용하지 못하게 설정하고자한다

emojis = await guild.fetch_emojis()

- await guild.fetch_emojis()를 호출해서 서버의 이모지 목록을 가지고 온다. 서버의 모든 이모지를 가져온다.

emoji = discord.utils.get(emojis, name=emoji_key)

- 이모지 목록에서 emoji_key 값과 일치하는 이름을 가진 이모지를  찾아 저장한다.

KEYS_LIST = list(EMOJI_MAPPING.keys())
POSITION_COUNT = len(KEYS_LIST)

- 구현 상의 이유로 키값을 미리 리스트화 하여 따로 꺼내놨다.


3. Draft Logic

- 핵심 코드만 별도 기록하며, 순서는 다음과 같이 진행된다.

 드래프트를 참여하는 인원을 담아줄 배열을 이 타이밍에 초기화한다.

async def 드래프트(ctx):
    global position_member
        position_member = {i: [] for i in range(POSITION_COUNT)}

※ 이 변수가 드래프트 명령어를 여럿이 사용할때 충돌이 나게 만드는 가장 큰 원인이다.

※ 이것이 문제가 되는 이유는 추후 별도 포스트로 다룬다.

 

② 투표 이모지 추가

for emoji_key in EMOJI_MAPPING.keys():
    emoji_id = EMOJI_MAPPING[emoji_key]
    if emoji_id:
       emoji = discord.PartialEmoji(name=emoji_key, id=int(emoji_id))
       await message.add_reaction(emoji)

- 미리 저장해놨던 이모지 이름을 순회하면서, 이모지를 표현하는 객체를 생성한다.

 

③ 이모지 반응을 처리하는 이모지 핸들러 구현

@bot.event
async def on_reaction_add(reaction, user):
    # logging.debug("on_reaction_add called")  # 디버깅 로그 추가
    # logging.info(f"Reaction by: {user}, Reaction: {reaction.emoji}")  # 정보 로그

    # 봇 반응 제외
    if user.bot:
        return

    emoji_name = str(reaction.emoji).split(':')[1]

    if emoji_name in EMOJI_MAPPING:
        position = KEYS_LIST.index(emoji_name)

        # 현재 포지션에 사용자 ID 추가
        if user.id not in position_member[position]:
            position_member[position].append(user.id)

- 실제 실행시에는 Logging을 상당히 빡빡하게 잡았었다.

- 이 핸들러는 이모지를 클릭했을때 반응에 사용된 이모지를 추출한다.

- 이모지 이름에 해당하는 위치를 찾아서 사용자의 ID를 추가해 둔다.

- on_reaction_add는 Discord.py 라이브러리에서 제공하는 이벤트 핸들러의 표준 형태다.

 

※ Discord.py는 비동기식 이벤트 기반 라이브러리로, Discord 서버와의 상호작용 처리를 위해 다양한 이벤트를 제공한다.

- 사용자가 메시지에 반응을 추가하면 on_reaction_add가 호출되는 방식이다.

 

④ 랜덤 인원 뽑기 로직

# async def 드래프트(ctx) 부분의 연장선이다.

for i in range(len(position_member)):
    key_value = KEYS_LIST[i]

    if key_value != 'CM' and key_value != 'CB':
       choose_player = pick_and_remove(position_member[i], 1)
         if not choose_player:
            draft_message += f"{key_value} : 없음.\n"
         else:
            draft_message += f"{key_value} : <@{choose_player[0]}>\n"
    else:
       choose_player = pick_and_remove(position_member[i], 2)
       # 출력 로직 생략
await ctx.send(content=f"{draft_message}")
def pick_and_remove(player_list, count):
    logging.debug(f"{player_list}")  # 디버깅 로그 추가
    if len(player_list) < count:
        count = len(player_list)
    choose_player = random.sample(player_list,count)

    for player in choose_player:
        player_list.remove(player)

    return choose_player

- player_list를 인자로 받고 뽑아야 하는 인원수를 받아 해당 인원 만큼 뽑아온다. 

- 만약 누른 인원이 부족한 경우(Ex. CM은 2명을 뽑지만 1명만 누른 경우)엔 뽑아야 하는 인원수를 조정한다.

- 4-3-3 포메이션에서 CM과 CB를 제외한 경우는 모두 1명이 나와야 하기 때문에 1명만 뽑는다.

- else의 출력 로직은 생략했다.

 

⑤ 실행결과

- 이전 서버에서 운영하던 방식과 다르게 구현했지만, 여러번 테스트 끝에 정상 작동을 확인하였다.

 

⑥ git 반영

- 기본적으로 기능을 구현할 때는 특정 기능이 독립적인 성격을 띄고있다면, 가급적 branch를 나눠서 작업한다.

- 이후에 병합을 하더라도 그렇게 작업을 해주는 것이 유용하다.

- 나의 경우 draft_preset이라는 branch를 만들어 그곳에서 작업했다.

git branch "생성할 브랜치 명"
git checkout "이동할 브랜치 명"

git add 파일명.py
git commit -m "커밋할 문자 내용"
git push origin "branch명"

 

- 다음과 같이 반영되는것을 확인할 수 있다.

 

⑦ 변경 사항 통합

- 본인의 경우는 어느정도 테스트를 해놓고 난 이후에 문제없이 작동한다고 판단하여, 병합을 진행하였다.

- 필요시 master branch외에 별도로 develop branch 등을 만들어서 반영하고, 그 이후 안정화되면 master를 넣어도 된다.

Comments