한 수보다 큰 수들 중 비트가 1~2개 다른 것들, 그 중 가장 작은 수를 찾는 문제입니다.
xor 연산으로 비트가 다른 곳들로 새로운 수를 만들고 그것을 이진수 변환해서 1의 갯수를 새면 다른 비트의 수를 알 수 있을 것 같습니다.
반복문이 삼중이 되는 코드가 되어버렸습니다.
#include <string>
#include <vector>
#include <limits.h>
using namespace std;
vector<long long> solution(vector<long long> numbers) {
vector<long long> answer;
for(long long number : numbers)
{
for(long long num = number + 1; num < LLONG_MAX; num ++)
{
long long xorResult = num ^ number;
int oneCnt = 0;
while(xorResult != 0)
{
oneCnt += (xorResult % 2);
xorResult /= 2;
if(oneCnt > 2) break;
}
if(oneCnt == 1 || oneCnt == 2)
{
answer.push_back(num);
break;
}
}
}
return answer;
}
결국 테스트 케이스 10, 11에서 시간초과를 받았습니다.
bitset이라는 구조체를 사용해 봤습니다. 여전히 시간 초과를 받았습니다.
코드가 좀 더 간단해진 것 같아서 좋긴 합니다.
#include <string>
#include <vector>
#include <limits.h>
#include <bitset>
using namespace std;
vector<long long> solution(vector<long long> numbers) {
vector<long long> answer;
for(long long number : numbers)
{
for(long long num = number + 1; num < LLONG_MAX; num ++)
{
int oneCnt = bitset<128>(num^number).count();
if(oneCnt == 1 || oneCnt == 2)
{
answer.push_back(num);
break;
}
}
}
return answer;
}
std::bitset<N>은 C++ 표준 라이브러리(<bitset>)에서 제공하는 고정 길이 N비트짜리 비트 컨테이너입니다.
컴파일 타임에 크기가 N으로 고정됩니다.
생성/대입 시 정수나 문자열 등을 받을 수 있고, 이때 값은 N비트 범위로 표현되어 저장됩니다.
정수를 넣으면 그 정수값의 비트 표현을 N비트로 맞춰 담는다고 생각하면 됩니다.
cout << bitset으로 출력하면 operator<<가 비트열을 ‘0’/‘1’ 문자로 출력해 줘서 문자열처럼 보입니다.
실제로 타입이 string으로 바뀌는 것이 아니고 출력 형식이 문자열 형태인 것입니다.
멤버 함수
다른 사람의 풀이에서 힌트를 얻었습니다.
수가 커질 수록 비교해야할 수의 범위도 커져서 이 방법으로는 매우 비효율적이라 합니다.
확실히 짝수의 경우 마지막 비트가 무조건 0일 테니 그것만 수정하는 것으로 빠르게 정답을 구할 수 있고,
문제는 홀수여서 비트가 잔뜩 변경된다는 건데...
음.. 마지막 비트 부터 쭉 1이 이어지는 지점의 끝까지이동 한뒤 2개까지만 차이가 나도록 변경 그러니가 0인 비트를 1 바로 그 다음 비트는 0으로 만들어야 할 것 같은데..
1111 -> 10000 -> 10111 같이
이걸 수식으로 어떻게 만들 수 있을까?
그냥 bitset 상에서 변경한뒤에 정수로 빼내면 될 것을 어렵게 고민하고 있었네요
#include <string>
#include <vector>
#include <limits.h>
#include <bitset>
using namespace std;
vector<long long> solution(vector<long long> numbers) {
vector<long long> answer;
for(long long number : numbers)
{
if (number % 2 == 0) {
answer.push_back(number + 1);
continue;
}
bitset<64> bits(number);
for(int i = 0; i < 64; i++)
{
if(!bits.test(i))
{
bits.set(i);
if(i > 0)
{
bits.flip(i - 1);
}
answer.push_back(bits.to_ullong());
break;
}
}
}
return answer;
}
Claude에게 코드 흐름 분석을 부탁해 흐름이 어긋나는 지점을 찾았습니다.
버그 1 — IncreaseGuessCount 안에서 ChangeTurn이 선행 실행됨
void ABaseballGameModeBase::IncreaseGuessCount(ABaseballPlayerController* InBaseballPlayerController)
{
BaseballPS->CurrentGuessCount++;
ChangeTurn(); // ← 여기서 턴이 먼저 바뀜
}
IncreaseGuessCount가 호출되는 순간 턴이 교체됩니다. 그런데 이후 PrintChatMessageString 안의 for 루프에서 JudgeGame이 호출되므로, Draw 체크 시점에는 이미 다음 플레이어 턴으로 넘어간 상태입니다. JudgeGame의 Draw 판정이 틀어지고, else 분기에서 ChangeTurn을 한 번 더 호출해 턴이 꼬입니다.
버그 2 — JudgeGame이 for 루프 안에서 플레이어 수만큼 반복 호출됨
for (TActorIterator<ABaseballPlayerController> It(GetWorld()); It; ++It)
{
BaseballPlayerController->ClientRPCPrintChatMessageString(...); // 채팅 전송 (OK)
JudgeGame(InBaseballPlayerController, StrikeCount); // ← 매 반복마다 호출!
}
2인 플레이 시 JudgeGame → ReserveResetGame → ChangeTurn이 2번씩 중복 실행됩니다. ClientRPC 전송만 루프에서 하고, JudgeGame은 루프 밖으로 빼야 합니다.
버그 3 — Draw 판정 조건이 Count >= Max여야 하는데 타이밍이 어긋남
버그 1의 결과로 Draw 체크 시점에 현재 플레이어의 카운트는 3 (= Max)이지만 상대 플레이어는 아직 0이므로 bIsDraw = false가 되고, else → ChangeTurn()이 한 번 더 실행됩니다. 이 중복 ChangeTurn이 MaxGuessCount를 초과한 입력을 허용하는 원인입니다.
PrintChatMessageString에서 JudgeGame을 루프 밖으로 이동, IncreaseGuessCount에서 ChangeTurn 제거, JudgeGame 안에서 ChangeTurn을 통합 관리하도록 변경합니다.
// PrintChatMessageString — JudgeGame을 루프 밖으로
if (IsGuessNumberString(...))
{
FString JudgeResultString = JudgeResult(SecretNumberString, InChatMessageString);
IncreaseGuessCount(InBaseballPlayerController); // Count++만, ChangeTurn 없이
ABaseballPlayerState* BaseballPS = InBaseballPlayerController->GetPlayerState<ABaseballPlayerState>();
FString CurrentPlayerInfoString = IsValid(BaseballPS) ? BaseballPS->GetPlayerInfoString() : TEXT("");
// 채팅 브로드캐스트만 루프에서
for (TActorIterator<ABaseballPlayerController> It(GetWorld()); It; ++It)
{
if (IsValid(*It))
{
FString Msg = CurrentPlayerInfoString + TEXT(" : ") + InChatMessageString + TEXT(" -> ") + JudgeResultString;
(*It)->ClientRPCPrintChatMessageString(Msg);
}
}
// 판정은 루프 밖에서 1회만
int32 StrikeCount = FCString::Atoi(*JudgeResultString.Left(1));
JudgeGame(InBaseballPlayerController, StrikeCount);
}
// IncreaseGuessCount — ChangeTurn 제거
void ABaseballGameModeBase::IncreaseGuessCount(ABaseballPlayerController* InBaseballPlayerController)
{
ABaseballPlayerState* BaseballPS = InBaseballPlayerController->GetPlayerState<ABaseballPlayerState>();
if (IsValid(BaseballPS))
{
BaseballPS->CurrentGuessCount++;
}
// ChangeTurn() 제거 — JudgeGame에서 통합 처리
}
// JudgeGame — ChangeTurn은 여기서만 호출
void ABaseballGameModeBase::JudgeGame(ABaseballPlayerController* InBaseballPlayerController, int InStrikeCount)
{
if (InStrikeCount == 3)
{
ABaseballPlayerState* BaseballPS = InBaseballPlayerController->GetPlayerState<ABaseballPlayerState>();
FString WinMsg = IsValid(BaseballPS) ? BaseballPS->PlayerNameString + TEXT(" has won the game.") : TEXT("Winner!");
for (const auto& PC : AllPlayerControllers)
{
PC->NotificationText = FText::FromString(WinMsg);
}
ReserveRsetGame(); // 1회만
return; // ← 이후 코드 실행 방지
}
bool bIsDraw = true;
for (const auto& PC : AllPlayerControllers)
{
ABaseballPlayerState* PS = PC->GetPlayerState<ABaseballPlayerState>();
if (IsValid(PS) && PS->CurrentGuessCount < PS->MaxGuessCount)
{
bIsDraw = false;
break;
}
}
if (bIsDraw)
{
for (const auto& PC : AllPlayerControllers)
{
PC->NotificationText = FText::FromString(TEXT("Draw..."));
}
ReserveRsetGame(); // 1회만
}
else
{
ChangeTurn(); // 게임 계속 → 턴 교체
}
}
이제 Draw도 정상적으로 출력되고 팀 전환 기능도 동작이 됩니다. 3번의 입력제한도 지켜지네요.
문제를 해결하니 다른 문제가 보입니다. 제한시간이 지나도 턴이 변경되지 않는 현상이 발생했습니다.
Transact
에디터 유틸리티에서 함수를 만들 때 시작 부에 BeginTransaction을 끝 부분에는 EndTransaction을 붙이기도 하지만 잘 동작하지 않을 때도 있고, 필수는 아니라서 안해도 문제 없습니다.
BP에서 함수의 매개변수로 지정된 값은 Get을 통해 가져올 수 있다.
에디터 유틸리티 함수에 매개변수를 설정하면 나중에 해당 스크립팅된 함수를 사용할 때 팝업 창으로 나와서 선택하게 합니다.
에셋 액션 - 콘텐츠 브라우저 상에서의 액션
액터 액션 - 월드에서 선택된 액터에서의 액션
둘 다 사용법은 동일합니다.
실행 방법
위젯 블루프린트를 우클릭하고 실행 누르기 or 위젯 블루프린트 창에서 Run Utility widget 클릭
EditorUtilityToolMenuEntry
툴 바에 등록 가능한 유틸리티 블루프린트?
키 값이 콘솔 변수로 숨겨져 있다고 합니다. ToolMenus.Edit
1로 하면 숨겨진 키 값이 보인다.
기본값은 0입니다.
ToolMenu에 포함되 Get 노드를 사용해 ToolMenuObjectReference를 가져올 수 있고,
거기에 Tool 위젯을 추가할 수 있습니다.
반드시 커스텀 이벤트 이름을 Run으로 해서 이벤트 생성
Run으로 해야 에디터에서 이 유틸리티가 활성화 될 때 호출이 됩니다.
거기에서 Get 노드로 ToolMenuObjectReference를 가져오고 Add MenuEntryObject로 정해진 메뉴 툴바에 등록합니다.
메뉴 툴바 지정은 details의 data에서 진행합니다.
등록을 해두어도 바로 적용되지 않고 껏다 켜야합니다.
그냥 껏다키는 걸로는 안되고 클래스 디폴트에서 Run on Start-Up을 체크하고 껏다켜야 등록됩니다.

Execute 이벤트 생성(커스텀 아님)
Execute에서 EditorUtilitySubsystem의 Spawn and Register Tab을 호출하는 것으로 툴바 버튼 실행 시의 이벤트를 정의할 수 있습니다.
마찬가지로 Run on Start-Up을 체크한 상태로 엔진을 껏다켜야 합니다.
