삼각형 배열을 1차원 배열로 펼친 뒤, 달팽이가 이동하는 세 방향(왼쪽 아래, 오른쪽, 왼쪽 위)을 반복하며 값을 채우는 방식으로 구현했습니다.
초기에 반복 횟수를 ceil(n/4)로 설정했는데, 이는 달팽이가 방향을 꺾으며 안으로 파고드는 껍질(Layer)의 수가 약 n/4일 것이라는 추측했기 때문입니다. 하지만 실제로 Layer의 수는 약 n/3에 가깝기 때문에, n/4로 설정하면 n이 커질수록 달팽이가 안쪽에 도달하기 전에 반복문이 종료된다고 합니다.
반복 횟수를 n으로 넉넉하게 설정하자 정답을 받을 수 있었습니다. 범위를 벗어나거나 이미 채워진 인덱스에는 덮어씌우지 않는 방어 로직을 작성해 두었기에 오답을 피할 수 있었습니다.
vector solution(int n) {
vector answer;
int answerSize = 0;
for(int i = 1; i <= n; i++) answerSize += i;
answer.resize(answerSize, 0);
int currentIdx = 1;
int leftDown = 0, bottomRight = 0, leftUp = 0;
for(int i = 0; i < n; i++)
{
leftDown = leftUp;
for(int j = i; j < n - i; j++)
{
if(leftDown + j < answerSize && answer[leftDown + j] == 0)
{ leftDown += j; answer[leftDown] = currentIdx++; }
}
bottomRight = leftDown;
for(int j = 0; j < n - i * 2; j++)
{
if(bottomRight + 1 < answerSize && answer[bottomRight + 1] == 0)
{ bottomRight++; answer[bottomRight] = currentIdx++; }
}
leftUp = bottomRight;
for(int j = n - i; j > i; j--)
{
if(leftUp - j > 0 && answer[leftUp - j] == 0)
{ leftUp -= j; answer[leftUp] = currentIdx++; }
}
}
return answer;
}
테스트 1 〉 통과 (0.01ms, 4.14MB)
테스트 2 〉 통과 (0.01ms, 3.67MB)
테스트 3 〉 통과 (0.01ms, 4.2MB)
테스트 4 〉 통과 (1.00ms, 4.67MB)
테스트 5 〉 통과 (1.08ms, 4.68MB)
테스트 6 〉 통과 (1.11ms, 4.79MB)
테스트 7 〉 통과 (180.38ms, 107MB)
테스트 8 〉 통과 (156.96ms, 107MB)
테스트 9 〉 통과 (156.20ms, 107MB)
n이 커질수록 시간과 메모리가 급격히 늘어나는 것으로 보입니다. 개선의 여지가 있어 보입니다.
VGEquipmentComponent::Interact()는 클라이언트에서 실행되어 레이캐스트로 기믹 액터를 탐지한 뒤, Execute_OnInteract()를 클라이언트에서 직접 호출하게 되어있습니다.
문제는 기믹의 실제 상태 변경 로직이 HasAuthority() 가드로 보호되어 있다는 점입니다.
[Client] Interact()
→ SweepSingleByChannel로 기믹 탐지
→ Execute_OnInteract() ← 클라이언트에서 직접 호출
→ AVGMissionGimmickBase::OnInteractWith()
→ if (!HasAuthority()) return; ← Authority 없음 → 즉시 리턴
VGMissionGimmickBase에 Server RPC를 추가해 클라이언트가 서버에 요청을 위임하도록 수정했습니다. VGEquipmentComponent는 건드리지 않고 Mission 파일 범위 내에서 해결할 수 있는 방법이었지만 아직 상호작용이 정상적으로 동작하고 있지 않습니다.
void AVGMissionGimmickBase::OnInteractWith(AVGCharacterBase* Interactor)
{
if (!HasAuthority())
{
Server_Interact(Interactor); // 클라이언트는 서버에 위임
return;
}
if (!CanInteractWith(Interactor)) return;
OnGimmickInteracted.Broadcast(this, Interactor);
}
void AVGMissionGimmickBase::Server_Interact_Implementation(AVGCharacterBase* Interactor)
{
if (!CanInteractWith(Interactor)) return;
OnGimmickInteracted.Broadcast(this, Interactor);
}
자식 기믹 클래스(Lever, Chest, Altar 등)는 RPC 함수를 재정의해서 사용합니다.
후보로 검토한 방법들은 다음과 같습니다.
OnGimmickStateChanged 델리게이트에 Interactor 인자 추가 → 영향 범위가 너무 넓어 배제보상 로직의 위치는 MissionSubsystem이 아닌 MissionBase 로 결정했습니다. 미션마다 보상 조건이 다를 수 있고, 서브시스템은 조율자 역할에 집중하는 것이 SRP 원칙에 맞기 때문입니다.
OnRep_CurrentStateTag 역할 정리기존 코드는 OnRep_ 안에서 HasAuthority()를 체크하며 서버 측 연쇄 처리까지 담당하고 있었습니다. OnRep_은 클라이언트 피드백 콜백이므로 서버 로직이 섞이면 실행 경로가 불명확해집니다.
// 변경 후
void AVGMissionBase::CompleteMission()
{
...
SetMissionState(VigilantMissionTags::MissionCompleted);
for (AVGMissionGimmickBase* Gimmick : MissionGimmicks)
Gimmick->SetStateTag(VigilantMissionTags::GimmickCompleted); // 서버 연쇄 처리
SpawnRewardItems(Contributors);
}
void AVGMissionBase::OnRep_CurrentStateTag()
{
OnMissionStateChanged.Broadcast(GetMissionID(), CurrentStateTag); // 클라이언트 피드백만
}
FVGAltarPlacementSlot 배열 도입)UVGMissionDataAsset으로 분리 (MissionID, MissionDescription, MissionTypeTag, RewardItemClass, TimeLimit)OnRep_MissionID 방식에서 BeginPlay 방식으로 변경OnRep_CurrentStateTag 역할 정리 및 서버/클라이언트 로직 분리 설계 확정MissionBase)