https://www.youtube.com/watch?v=VMbpVmJfBk8&embeds_referring_euri=https%3A%2F%2Fcontest.nypc.co.kr%2F&source_ve_path=MjM4NTE

NYPC - 코드배틀 온라인 본선 문제이다. 야추라는 게임을 변형한 게임으로, 꽤 복잡한 규칙을 가지고 있어서 어렵기도 했고 재밌기도 했던 문제이다. 본선은 팀이 협력하여 진행하였고, 바빠서 참여하지 못한 팀원 1명을 제외한 3명(미디어 23 황준서, 소웨 24 박정우, 소웨 24 황교선)이 약 1주일 동안 열심히 진행하였다.

시작

팀원 박정우가 문제에서 주어지는 뼈대 코드를 발전시켜 기본적인 코드를 작성해왔다.

image.png

결과는 샘플 Ai 기준 1승이지만, 기본적인 뼈대를 구성했다는것에 의의가 있는 제출이었다.

해당 코드의 기본적인 전략은, 입찰 단계에서는 상대와 나의 각 주사위에 대한 가치를 평가한 후, 그에 맞는 배팅금을 거는 것, 득점단계에서는 지금 당장 얻을 수 있는 가장 큰 점수의 족보를 선택하는 전략이었다. 아주 기본적인 전략이지만 앞으로 팀이 나아갈 방향성을 제시해주었다.

코드:

#include <algorithm>
#include <cassert>
#include <iostream>
#include <numeric>
#include <sstream>
#include <string>
#include <vector>
#include <queue>

using namespace std;

// 가능한 주사위 규칙들을 나타내는 enum
enum DiceRule
{
    ONE,
    TWO,
    THREE,
    FOUR,
    FIVE,
    SIX,
    CHOICE,
    FOUR_OF_A_KIND,
    FULL_HOUSE,
    SMALL_STRAIGHT,
    LARGE_STRAIGHT,
    YACHT
};

// 입찰 방법을 나타내는 구조체
struct Bid
{
    char group; // 입찰 그룹 ('A' 또는 'B')
    int amount; // 입찰 금액
};

// 주사위 배치 방법을 나타내는 구조체
struct DicePut
{
    DiceRule rule;    // 배치 규칙
    vector<int> dice; // 배치할 주사위 목록
};

// 팀의 현재 상태를 관리하는 구조체
struct GameState
{
    vector<int> dice;      // 현재 보유한 주사위 목록
    vector<int> ruleScore; // 각 규칙별 획득 점수 (사용하지 않았다면 -1)
    int bidScore;          // 입찰로 얻거나 잃은 총 점수

    // 처음에 사용하지 않은 상태로 score를 초기화
    GameState() : ruleScore(12, -1), bidScore(0) {}

    int getTotalScore() const;               // 현재까지 획득한 총 점수 계산 (상단/하단 점수 + 보너스 + 입찰 점수)
    void bid(bool isSuccessful, int amount); // 입찰 결과에 따른 점수 반영
    void addDice(vector<int> newDice);       // 새로운 주사위들을 보유 목록에 추가
    void useDice(DicePut put);               // 주사위를 사용하여 특정 규칙에 배치
    static int calculateScore(DicePut put);  // 주어진 규칙과 주사위에 대한 점수 계산
};
// 게임 상태를 관리하는 클래스
double calculateMaxScore(DicePut put);
double calculateMaxScoreWithWeight(DicePut put, vector<vector<double>> weight);
vector<int> usedDicesWhenCalc(DicePut put);
class Game
{
public:
    GameState myState;  // 내 팀의 현재 상태
    GameState oppState; // 상대 팀의 현재 상태

    // ================================ [필수 구현] ================================
    // ============================================================================
    // 주사위가 주어졌을 때, 어디에 얼마만큼 베팅할지 정하는 함수
    // 입찰할 그룹과 베팅 금액을 pair로 묶어서 반환
    // ============================================================================

    // 가치 계산 함수
    pair<double, double> valueCalc_3(vector<int> diceA, vector<int> diceB, bool isMe)
    {
        // 가치계산함수. 리턴: A를 포함한 Full Hand 가치, B" " " 가치
        double valA = 0, valB = 0;

        if (isMe)
            for (auto d : myState.dice)
            {
                diceA.push_back(d);
                diceB.push_back(d);
            }
        else
            for (auto d : oppState.dice)
            {
                diceA.push_back(d);
                diceB.push_back(d);
            }
        int rule = -1;
        for (int i = 0; i < 12; i++)
            if (isMe)
            {
                if (myState.ruleScore[i] == -1)
                {
                    DicePut tempDicePut = DicePut{DiceRule(i), diceA};
                    valA = max(valA, calculateMaxScore(tempDicePut));
                    valB = max(valB, calculateMaxScore(tempDicePut));
                }
            }
            else
            {
                if (oppState.ruleScore[i] == -1)
                {
                    DicePut tempDicePut = DicePut{DiceRule(i), diceA};
                    valA = max(valA, calculateMaxScore(tempDicePut));
                    valB = max(valB, calculateMaxScore(tempDicePut));
                }
            }
        return {valA, valB};
    }

    // 베팅 함수
    Bid calculateBid(vector<int> diceA, vector<int> diceB)
    {
        sort(diceA.begin(), diceA.end());
        sort(diceB.begin(), diceB.end());

        auto [sumA, sumB] = valueCalc_3(diceA, diceB, 1);
        char group = (sumA > sumB) ? 'A' : 'B';

        int amount = min(40000.0, max(sumA, sumB) / 3);
        return Bid{group, amount};
    }

    // ============================================================================
    // 주어진 주사위에 대해 사용할 규칙과 주사위를 정하는 함수
    // 사용할 규칙과 사용할 주사위의 목록을 pair로 묶어서 반환
    // ============================================================================
    // [득점 주사위를 고르는 함수]
    DicePut calculatePut()
    {
        int remain_turn = 0;
        // 가치가 가장 높은 규칙 찾기
        pair<double, int> ruleValue = {0, -1}; // 가치, 룰
        for (int rule = 0; rule < 12; rule++)
        {
            if (myState.ruleScore[rule] == -1)
            {
                remain_turn++;
                double newValue = calculateMaxScore({DiceRule(rule), myState.dice});
                if (ruleValue.first <= newValue)
                    ruleValue = {newValue, rule};
            }
        }
        vector<int> usedDices;
        if (remain_turn == 1)
        {
            const string str = to_string(remain_turn);
            for (auto d : myState.dice)
            {
                if (1 <= d && d <= 6)
                    usedDices.push_back(d);
            }
            return DicePut{DiceRule(ruleValue.second), usedDices};
        }

        usedDices = usedDicesWhenCalc(DicePut{DiceRule(ruleValue.second), myState.dice});
        return DicePut{DiceRule(ruleValue.second), usedDices};
    }
    // ============================== [필수 구현 끝] ==============================

    // 입찰 결과를 받아서 상태 업데이트
    void updateGet(vector<int> diceA, vector<int> diceB, Bid myBid, Bid oppBid, char myGroup)
    {
        // 그룹에 따라 주사위 분배
        if (myGroup == 'A')
            myState.addDice(diceA), oppState.addDice(diceB);
        else
            myState.addDice(diceB), oppState.addDice(diceA);

        // 입찰 결과에 따른 점수 반영
        bool myBidOk = myBid.group == myGroup;
        myState.bid(myBidOk, myBid.amount);

        char oppGroup = myGroup == 'A' ? 'B' : 'A';
        bool oppBidOk = oppBid.group == oppGroup;
        oppState.bid(oppBidOk, oppBid.amount);
    }

    // 내가 주사위를 배치한 결과 반영
    void updatePut(DicePut put) { myState.useDice(put); }

    // 상대가 주사위를 배치한 결과 반영
    void updateSet(DicePut put) { oppState.useDice(put); }
};

// 현재까지 획득한 총 점수 계산 (상단/하단 점수 + 보너스 + 입찰 점수)
int GameState::getTotalScore() const
{
    int basic = 0, combination = 0, bonus = 0;

    // 기본 점수 규칙 계산 (ONE ~ SIX)
    for (int i = 0; i < 6; i++)
        if (ruleScore[i] != -1)
            basic += ruleScore[i];
    // 보너스 점수 계산 (기본 규칙 63000점 이상시 35000점 보너스)
    if (basic >= 63000)
        bonus += 35000;
    // 조합 점수 규칙 계산 (CHOICE ~ YACHT)
    for (int i = 6; i < 12; i++)
        if (ruleScore[i] != -1)
            combination += ruleScore[i];

    return basic + bonus + combination + bidScore;
}

// 입찰 결과에 따른 점수 반영
void GameState::bid(bool isSuccessful, int amount)
{
    if (isSuccessful)
        bidScore -= amount; // 성공시 베팅 금액만큼 점수 차감
    else
        bidScore += amount; // 실패시 베팅 금액만큼 점수 획득
}

// 새로운 주사위들을 보유 목록에 추가
void GameState::addDice(vector<int> newDice)
{
    for (int d : newDice)
        dice.push_back(d);
}

// 주사위를 사용하여 특정 규칙에 배치
void GameState::useDice(DicePut put)
{
    // 이미 사용한 규칙인지 확인
    assert(ruleScore[put.rule] == -1 && "Rule already used");

    for (int d : put.dice)
    {
        // 주사위 목록에 없는 주사위가 있는지 확인하고 주사위 제거
        auto it = find(dice.begin(), dice.end(), d);
        assert(it != dice.end() && "Invalid dice");
        dice.erase(it);
    }

    // 해당 규칙의 점수 계산 및 저장
    ruleScore[put.rule] = calculateScore(put);
}

// 주어진 규칙과 주사위에 대한 점수 계산
int GameState::calculateScore(DicePut put)
{
    DiceRule rule = put.rule;
    vector<int> dice = put.dice;

    switch (rule)
    {
    // 기본 규칙 점수 계산 (해당 숫자의 개수 × 숫자 × 1000점)
    case ONE:
        return count(dice.begin(), dice.end(), 1) * 1 * 1000;
    case TWO:
        return count(dice.begin(), dice.end(), 2) * 2 * 1000;
    case THREE:
        return count(dice.begin(), dice.end(), 3) * 3 * 1000;
    case FOUR:
        return count(dice.begin(), dice.end(), 4) * 4 * 1000;
    case FIVE:
        return count(dice.begin(), dice.end(), 5) * 5 * 1000;
    case SIX:
        return count(dice.begin(), dice.end(), 6) * 6 * 1000;

    case CHOICE: // 주사위에 적힌 모든 수의 합 × 1000점
        return accumulate(dice.begin(), dice.end(), 0) * 1000;
    case FOUR_OF_A_KIND:
    { // 같은 수가 적힌 주사위가 4개 있다면, 주사위에 적힌 모든 수의 합 × 1000점, 아니면 0
        bool ok = false;
        for (int i = 1; i <= 6; i++)
            if (count(dice.begin(), dice.end(), i) >= 4)
                ok = true;
        return ok ? accumulate(dice.begin(), dice.end(), 0) * 1000 : 0;
    }
    case FULL_HOUSE:
    { // 3개의 주사위에 적힌 수가 서로 같고, 다른 2개의 주사위에 적힌 수도 서로 같으면 주사위에 적힌 모든 수의 합 × 1000점, 아닐 경우 0점
        bool pair = false, triple = false;
        for (int i = 1; i <= 6; i++)
        {
            int cnt = count(dice.begin(), dice.end(), i);
            // 5개 모두 같은 숫자일 때도 인정
            if (cnt == 2 || cnt == 5)
                pair = true;
            if (cnt == 3 || cnt == 5)
                triple = true;
        }
        return (pair && triple) ? accumulate(dice.begin(), dice.end(), 0) * 1000 : 0;
    }
    case SMALL_STRAIGHT:
    { // 4개의 주사위에 적힌 수가 1234, 2345, 3456중 하나로 연속되어 있을 때, 15000점, 아닐 경우 0점
        bool e1 = count(dice.begin(), dice.end(), 1) > 0;
        bool e2 = count(dice.begin(), dice.end(), 2) > 0;
        bool e3 = count(dice.begin(), dice.end(), 3) > 0;
        bool e4 = count(dice.begin(), dice.end(), 4) > 0;
        bool e5 = count(dice.begin(), dice.end(), 5) > 0;
        bool e6 = count(dice.begin(), dice.end(), 6) > 0;
        bool ok = (e1 && e2 && e3 && e4) || (e2 && e3 && e4 && e5) ||
                  (e3 && e4 && e5 && e6);
        return ok ? 15000 : 0;
    }
    case LARGE_STRAIGHT:
    { // 5개의 주사위에 적힌 수가 12345, 23456중 하나로 연속되어 있을 때, 30000점, 아닐 경우 0점
        bool e1 = count(dice.begin(), dice.end(), 1) > 0;
        bool e2 = count(dice.begin(), dice.end(), 2) > 0;
        bool e3 = count(dice.begin(), dice.end(), 3) > 0;
        bool e4 = count(dice.begin(), dice.end(), 4) > 0;
        bool e5 = count(dice.begin(), dice.end(), 5) > 0;
        bool e6 = count(dice.begin(), dice.end(), 6) > 0;
        bool ok = (e1 && e2 && e3 && e4 && e5) || (e2 && e3 && e4 && e5 && e6);
        return ok ? 30000 : 0;
    }
    case YACHT:
    { // 5개의 주사위에 적힌 수가 모두 같을 때 50000점, 아닐 경우 0점
        bool ok = false;
        for (int i = 1; i <= 6; i++)
            if (count(dice.begin(), dice.end(), i) == 5)
                ok = true;
        return ok ? 50000 : 0;
    }
    }
    assert(false);
}

double calculateMaxScore(DicePut put)
{
    DiceRule rule = put.rule;
    vector<int> dice = put.dice;

    sort(dice.begin(), dice.end(), greater<>());
    switch (rule)
    {
    // 기본 규칙 점수 계산 (해당 숫자의 개수 × 숫자 × 1000점)
    case ONE:
        return min(5, (int)count(dice.begin(), dice.end(), 1)) * 1 * 1000;
    case TWO:
        return min(5, (int)count(dice.begin(), dice.end(), 2)) * 2 * 1000;
    case THREE:
        return min(5, (int)count(dice.begin(), dice.end(), 3)) * 3 * 1000;
    case FOUR:
        return min(5, (int)count(dice.begin(), dice.end(), 4)) * 4 * 1000;
    case FIVE:
        return min(5, (int)count(dice.begin(), dice.end(), 5)) * 5 * 1000;
    case SIX:
        return min(5, (int)count(dice.begin(), dice.end(), 6)) * 6 * 1000;

    case CHOICE:
    { // 주사위에 적힌 모든 수의 합 × 1000점
        int cnt = 0;
        for (int i = 0; i < 5; i++)
        {
            cnt += dice[i];
        }
        return cnt * 1000;
    }
    case FOUR_OF_A_KIND:
    { // 같은 수가 적힌 주사위가 4개 있다면, 주사위에 적힌 모든 수의 합 × 1000점, 아니면 0
        int ret = 0;
        for (int i = 0; i < dice.size() - 3; i++)
        {
            if (dice[i] == dice[i + 3])
                ret = max(ret, ((i == 0) ? dice[0] * 4 + dice[4] : dice[i] * 4 + dice[0]));
        }
        return ret * 1000;
    }
    case FULL_HOUSE:
    { // 3개의 주사위에 적힌 수가 서로 같고, 다른 2개의 주사위에 적힌 수도 서로 같으면 주사위에 적힌 모든 수의 합 × 1000점, 아닐 경우 0점
        int ret = 0;
        for (int i = 0; i < dice.size() - 4; i++)
        {
            if (dice[i] == dice[i + 4])
                ret = max(ret, dice[i] * 5);
        }
        // pair중 최선과, triple중 최선을 찾으면 될까?
        // triple은 pair가 될 수 있다.
        bool tripleValid[6] = {false, false, false, false, false, false};
        bool pairValid[6] = {false, false, false, false, false, false};
        for (int i = 0; i < dice.size() - 2; i++)
        {
            if (dice[i] == dice[i + 2])
            {
                tripleValid[dice[i] - 1] = 1;
                i = i + 2;
            }
        }
        for (int i = 0; i < dice.size() - 1; i++)
        {
            if (dice[i] == dice[i + 1])
            {
                pairValid[dice[i] - 1] = 1;
                i = i + 1;
            }
        }
        for (int i = 0; i < 6; i++)
        {
            for (int j = 0; j < 6; j++)
            {
                if (i == j)
                    continue; // 맨 위에서 이미 고려함.
                if (tripleValid[i] + pairValid[j] == 2)
                    ret = max((i + 1) * 3 + (j + 1) * 2, ret);
            }
        }
        return ret;
    }
    case SMALL_STRAIGHT:
    {
        bool count[6] = {false, false, false, false, false, false};
        for (int i = 0; i < dice.size(); i++)
        {
            count[dice[i] - 1] = 1;
        }
        for (int i = 1; i <= 3; i++)
        {
            int cnt = 0;
            for (int j = 0; j < 4; j++)
            {
                cnt += count[i + j - 1];
            }
            if (cnt == 4)
                return 15000;
        }
        return 0;
    }
    case LARGE_STRAIGHT:
    {
        bool count[6] = {false, false, false, false, false, false};
        for (int i = 0; i < dice.size(); i++)
        {
            count[dice[i] - 1] = 1;
        }
        for (int i = 1; i <= 2; i++)
        {
            int cnt = 0;
            for (int j = 0; j < 5; j++)
            {
                cnt += count[i + j - 1];
            }
            if (cnt == 5)
                return 30000;
        }
        return 0;
    }
    case YACHT:
    {
        for (int i = 0; i < dice.size() - 4; i++)
        {
            if (dice[i] == dice[i + 4])
                return 50000;
        }
        return 0;
    }
    }
    assert(false);
}

vector<int> usedDicesWhenCalc(DicePut put)
{
    DiceRule rule = put.rule;
    vector<int> dice = put.dice;

    sort(dice.begin(), dice.end(), greater<>());

    switch (rule)
    {
    case ONE:
    {
        vector<int> retDice;
        int cnt = 0;
        for (auto d : dice)
        {
            if (d == 1)
            {
                cnt++;
                retDice.push_back(d);
            }
            if (cnt == 5)
            {
                return retDice;
            }
        }
        reverse(dice.begin(), dice.end());
        for (auto d : dice)
        {
            if (retDice.size() == 5)
                break;
            if (d == 1)
                continue;
            retDice.push_back(d);
        }
        return retDice;
    }
    case TWO:
    {
        vector<int> retDice;
        int cnt = 0;
        for (auto d : dice)
        {
            if (d == 2)
            {
                cnt++;
                retDice.push_back(d);
            }
            if (cnt == 5)
            {
                return retDice;
            }
        }
        reverse(dice.begin(), dice.end());
        for (auto d : dice)
        {
            if (retDice.size() == 5)
                break;
            if (d == 2)
                continue;
            retDice.push_back(d);
        }
        return retDice;
    }
    case THREE:
    {
        vector<int> retDice;
        int cnt = 0;
        for (auto d : dice)
        {
            if (d == 3)
            {
                cnt++;
                retDice.push_back(d);
            }
            if (cnt == 5)
            {
                return retDice;
            }
        }
        reverse(dice.begin(), dice.end());
        for (auto d : dice)
        {
            if (retDice.size() == 5)
                break;
            if (d == 3)
                continue;
            retDice.push_back(d);
        }
        return retDice;
    }
    case FOUR:
    {
        vector<int> retDice;
        int cnt = 0;
        for (auto d : dice)
        {
            if (d == 4)
            {
                cnt++;
                retDice.push_back(d);
            }
            if (cnt == 5)
            {
                return retDice;
            }
        }
        reverse(dice.begin(), dice.end());
        for (auto d : dice)
        {
            if (retDice.size() == 5)
                break;
            if (d == 4)
                continue;
            retDice.push_back(d);
        }
        return retDice;
    }
    case FIVE:
    {
        vector<int> retDice;
        int cnt = 0;
        for (auto d : dice)
        {
            if (d == 5)
            {
                cnt++;
                retDice.push_back(d);
            }
            if (cnt == 5)
            {
                return retDice;
            }
        }
        reverse(dice.begin(), dice.end());
        for (auto d : dice)
        {
            if (retDice.size() == 5)
                break;
            if (d == 5)
                continue;
            retDice.push_back(d);
        }
        return retDice;
    }
    case SIX:
    {
        vector<int> retDice;
        int cnt = 0;
        for (auto d : dice)
        {
            if (d == 6)
            {
                cnt++;
                retDice.push_back(d);
            }
            if (cnt == 5)
            {
                return retDice;
            }
        }
        reverse(dice.begin(), dice.end());
        for (auto d : dice)
        {
            if (retDice.size() == 5)
                break;
            if (d == 6)
                continue;
            retDice.push_back(d);
        }
        return retDice;
    }

    case CHOICE:
    {
        vector<int> ret(dice.begin(), dice.begin() + 5);
        return ret;
    }
    case FOUR_OF_A_KIND:
    { // 같은 수가 적힌 주사위가 4개 있다면, 주사위에 적힌 모든 수의 합 × 1000점, 아니면 0
        int ret = 0;
        int temp = -1; // 포오카의 시작 인덱스를 저장
        for (int i = 0; i < dice.size() - 3; i++)
        {
            if (dice[i] == dice[i + 3])
            {
                if (ret < ((i == 0) ? dice[0] * 4 + dice[4] : dice[i] * 4 + dice[0]))
                {
                    ret = ((i == 0) ? dice[0] * 4 + dice[4] : dice[i] * 4 + dice[0]);
                    temp = i;
                }
            }
        }
        if (temp == 0)
            return {dice[0], dice[0], dice[0], dice[0], dice[4]};
        else if (temp == -1)
        {
            reverse(dice.begin(), dice.end());
            vector<int> retDice(dice.begin(), dice.begin() + 5);
            return retDice;
        }
        else
            return {dice[0], dice[temp], dice[temp], dice[temp], dice[temp]};
    }
    case FULL_HOUSE:
    {
        int ret = -1;
        reverse(dice.begin(), dice.end());
        vector<int> retDice(dice.begin(), dice.begin() + 5);

        for (int i = 0; i < dice.size() - 4; i++)
        {
            if (dice[i] == dice[i + 4])
                if (ret > dice[i] * 5)
                {
                    ret = dice[i] * 5;
                    retDice = {dice[i], dice[i], dice[i], dice[i], dice[i]};
                }
        }
        // pair중 최선과, triple중 최선을 찾으면 될까?
        // triple은 pair가 될 수 있다.
        bool tripleValid[6] = {false, false, false, false, false, false};
        bool pairValid[6] = {false, false, false, false, false, false};
        for (int i = 0; i < dice.size() - 2; i++)
        {
            if (dice[i] == dice[i + 2])
            {
                tripleValid[dice[i] - 1] = 1;
                i = i + 2;
            }
        }
        for (int i = 0; i < dice.size() - 1; i++)
        {
            if (dice[i] == dice[i + 1])
            {
                pairValid[dice[i] - 1] = 1;
                i = i + 1;
            }
        }
        // 가능한 쌍을 모두 배열에 저장한다.
        int pair = -1, triple = -1;
        for (int i = 5; i != -1; i--)
        {
            for (int j = 5; j != -1; j--)
            {
                if (i == j)
                    continue;
                if (tripleValid[i] + pairValid[j] == 2)
                    if (ret < ((i + 1) * 3 + (j + 1) * 2))
                    {
                        ret = (i + 1) * 3 + (j + 1) * 2;
                        triple = i + 1, pair = j + 1;
                        retDice = {triple, triple, triple, pair, pair};
                    }
            }
        }

        return retDice;
    }
    case SMALL_STRAIGHT:
    {
        bool count[6] = {false, false, false, false, false, false};
        reverse(dice.begin(), dice.end());
        vector<int> retDice(dice.begin(), dice.begin() + 5);
        for (int i = 0; i < dice.size(); i++)
        {
            count[dice[i] - 1] = 1;
        }
        for (int i = 1; i <= 3; i++)
        {
            int cnt = 0;
            for (int j = 0; j < 4; j++)
            {
                cnt += count[i + j - 1];
            }
            if (cnt == 4)
            {
                bool visited[4] = {false, false, false, false};
                int straight = i;
                for (auto d : dice)
                {
                    if (d == straight && !visited[straight - i])
                    {
                        straight++;
                        visited[straight - i] = true;
                    }
                    retDice = {i, i + 1, i + 2, i + 3};
                    retDice.push_back(d);
                    return retDice;
                }
            }
        }
        return retDice;
    }
    case LARGE_STRAIGHT:
    {
        bool count[6] = {false, false, false, false, false, false};
        for (int i = 0; i < dice.size(); i++)
        {
            count[dice[i] - 1] = 1;
        }
        for (int i = 1; i <= 2; i++)
        {
            int cnt = 0;
            for (int j = 0; j < 5; j++)
            {
                cnt += count[i + j - 1];
            }
            if (cnt == 5)
                return {i, i + 1, i + 2, i + 3, i + 4};
        }
        reverse(dice.begin(), dice.end());
        vector<int> retDice(dice.begin(), dice.begin() + 5);
        return retDice;
    }
    case YACHT:
    {
        reverse(dice.begin(), dice.end());
        for (int i = 0; i < dice.size() - 4; i++)
        {
            if (dice[i] == dice[i + 4])
                return {dice[i], dice[i], dice[i], dice[i], dice[i]};
        }
        reverse(dice.begin(), dice.end());
        vector<int> retDice(dice.begin(), dice.begin() + 5);
        return retDice;
    }
    }
    assert(false);
}

// 입출력을 위해 규칙 enum을 문자열로 변환
string toString(DiceRule rule)
{
    switch (rule)
    {
    case ONE:
        return "ONE";
    case TWO:
        return "TWO";
    case THREE:
        return "THREE";
    case FOUR:
        return "FOUR";
    case FIVE:
        return "FIVE";
    case SIX:
        return "SIX";
    case CHOICE:
        return "CHOICE";
    case FOUR_OF_A_KIND:
        return "FOUR_OF_A_KIND";
    case FULL_HOUSE:
        return "FULL_HOUSE";
    case SMALL_STRAIGHT:
        return "SMALL_STRAIGHT";
    case LARGE_STRAIGHT:
        return "LARGE_STRAIGHT";
    case YACHT:
        return "YACHT";
    }
    assert(!"Invalid Dice Rule"); // 올바르지 않은 주사위 규칙
}

DiceRule fromString(const string &s)
{
    if (s == "ONE")
        return ONE;
    if (s == "TWO")
        return TWO;
    if (s == "THREE")
        return THREE;
    if (s == "FOUR")
        return FOUR;
    if (s == "FIVE")
        return FIVE;
    if (s == "SIX")
        return SIX;
    if (s == "CHOICE")
        return CHOICE;
    if (s == "FOUR_OF_A_KIND")
        return FOUR_OF_A_KIND;
    if (s == "FULL_HOUSE")
        return FULL_HOUSE;
    if (s == "SMALL_STRAIGHT")
        return SMALL_STRAIGHT;
    if (s == "LARGE_STRAIGHT")
        return LARGE_STRAIGHT;
    if (s == "YACHT")
        return YACHT;
    assert(!"Invalid Dice Rule"); // 올바르지 않은 주사위 규칙
}

// 표준 입력을 통해 명령어를 처리하는 메인 함수
int main()
{
    Game game;

    // 입찰 라운드에서 나온 주사위들
    vector<int> diceA, diceB;
    // 내가 마지막으로 한 입찰 정보
    Bid myBid;

    while (true)
    {
        string line;
        getline(cin, line);

        istringstream iss(line);
        string command;
        if (!(iss >> command))
            continue;

        if (command == "READY")
        {
            // 게임 시작
            cout << "OK" << endl;
            continue;
        }

        if (command == "ROLL")
        {
            // 주사위 굴리기 결과 받기
            string strA, strB;
            iss >> strA >> strB;
            diceA.clear();
            diceB.clear();
            for (char c : strA)
                diceA.push_back(c - '0'); // 문자를 숫자로 변환
            for (char c : strB)
                diceB.push_back(c - '0'); // 문자를 숫자로 변환
            myBid = game.calculateBid(diceA, diceB);
            // 뭘 얼마에 살지
            cout << "BID " << myBid.group << " " << myBid.amount << endl;
            continue;
        }

        if (command == "GET")
        {
            // 주사위 받기
            char getGroup;
            char oppGroup;
            int oppScore;
            iss >> getGroup >> oppGroup >> oppScore;
            game.updateGet(diceA, diceB, myBid, Bid{oppGroup, oppScore}, getGroup);
            continue;
        }

        if (command == "SCORE")
        {
            // 주사위 골라서 배치하기(득점)
            DicePut put = game.calculatePut();
            game.updatePut(put);
            cout << "PUT " << toString(put.rule) << " ";
            for (int d : put.dice)
                cout << d;
            cout << endl;
            continue;
        }

        if (command == "SET")
        {
            // 상대의 주사위 배치
            string rule, str;
            vector<int> dice;
            iss >> rule >> str;
            for (char c : str)
                dice.push_back(c - '0'); // 문자를 숫자로 변환
            game.updateSet(DicePut{fromString(rule), dice});
            continue;
        }

        if (command == "FINISH")
        {
            // 게임 종료
            break;
        }

        // 알 수 없는 명령어 처리
        cerr << "Invalid command: " << command << endl;
        return 1;
    }

    return 0;
}

가중치 조정

위의 코드는 입찰 단계에서 돈을 너무 많이 쓰는 단점이 있었다. 그래서 내가 가중치를 조정해서 돈을 조금 더 적게 사용하게 만들었다.

image.png

결과는 샘플 Ai기준 4승. 어느정도의 발전이 있었다.

전략 추가

득점 단계에서 단순 점수로만 판단하는 로직은 좋지 않다고 판단, 팀원 박정우가 각 주사위 눈금 / 그 주사위의 개수에 대한 가중치를 따로 분리하여 득점단계의 로직을 발전시켰다. 그리고 팀원 황준서가 가중치를 조정하는 작업을 하였다.