[C#] 은는이가 타파!! 자연스러운 문장 만들기!
안녕하십니까 밤말팅입니다!
오늘은 가벼운 걸로 한 번 가져와보았습니다!
게임에 보면 꼭 이런 게 있곤 하죠
밤말팅(은)는 정의의 망치(을)를 얻었다!
한국어에 있는 조사는 "앞에 오는 종성"에 영향을 받는 것들이 있습니다.
종성이 있는 지만 판단하면 되는 아주 간단한 일이지만,
외국에서 수입된 프로그램인 경우에는 그냥 그대로 사용되는 것도 있구요
국산 프로그램 중에서도 제대로 커버가 되어있지 않은 경우도 가끔 보입니다~
오늘은 간단하게 조사를 처리해보도록 합시다!
완성본을 미리 시험해볼 수 있는 시제품입니다.
[이 클래스는 별도의 네임스페이스 참조가 필요하지 않습니다]
<목표>
[종성이 있을 때 / 종성이 없을 때]로 나누어서 입력을 하면
자동으로 앞 글자에 받침이 있는지 없는지 확인해서 입력해주는 함수를 만들어봅시다!
기왕 만드는 김에 대괄호를 사용하는 [커맨드]도 같이 넣어보도록 합시다.
<구상>
구상에 앞서 유니코드(UTF-8)로 되어있는 문자열을 사용한다는 것을 밝힙니다.
C#은 유니코드를 기본적으로 지원하기 때문에 char가 2바이트입니다.
다른 언어로 사용하실 땐, 종성 확인에 별도의 작업이 필요합니다.
EUC-KR이나 CP949의 경우 종성이 없는 글자의 규칙성이 없어 작업이 힘듭니다.
한글은 유니코드 번호 44032(가) ~ 55203(힣)에 등록되어있습니다.
매 28번째마다 종성이 들어있지 않은 문자가 나오는데요,
이걸 이용하면 쉽게 찾을 수 있겠죠!
일단 문자열을 돌면서 '['와 ']'를 확인하고
가운데에 있는 내용을 확인할 겁니다.
가운데에 '/'이 있으면 조사로 넣고
없으면 커맨드로 쓰도록 하겠습니다!
<구현>
지금부터 필요한 부분은 딱 세 개 입니다!
커맨드를 확인하고 정보를 주기
받은 커맨드를 실행하기
종성을 확인하기
간단하죠?
커맨드가 있는지 확인해주는 애부터 시작해보도록 합시다!
public static string CommandText(string targetText)
{
//반환해 줄 문자열을 저장해둡시다.
string returnText = targetText;
//커맨드가 시작하는 위치를 저장합니다.
int commandStartIndex = 0;
//커맨드가 시작되었다고 표시합니다.
bool commandReading = false;
//해당 문자열을 돌면서 확인합니다.
for(int index = 0; index < returnText.Length; ++index)
{
//'['가 나오면 커맨드가 시작되는 것으로 판단합니다.
if(returnText[index] == '[')
{
//커맨드 시작 위치가 현재 위치라고 알립니다.
commandStartIndex = index;
//커맨드가 시작되었다고 알립니다.
commandReading = true;
}
//커맨드가 시작되어 있는 상태에서, ']'가 나오면 커맨드가 끝났다고 판단합니다.
else if(commandReading && returnText[index] == ']')
{
//커맨드 시작 위치부터 현재 위치까지의 문자열을 저장합니다.
string checkedCommand = returnText.Substring(commandStartIndex, index - commandStartIndex + 1);
//커맨드 부분을 문자열에서 지웁니다.
returnText = returnText.Remove(commandStartIndex, index - commandStartIndex + 1);
//확인 중인 문자열 위치를 조정합니다.
index = commandStartIndex;
//커맨드 실행 후에 나온 결과물을 위치에 넣습니다.
returnText = returnText.Insert(index, CommandChecker(returnText[index - 1], checkedCommand));
//결과물을 넣은 후에, 한 칸 앞으로 이동해서 변환한 부분부터 읽게 합니다.
--index;
//커맨드 읽기가 끝났다고 알립니다.
commandReading = false;
};
};
//조정이 끝난 문자열을 반환합니다.
return returnText;
}
간단합니다!
문자열 돌면서 확인하는 거구요
'['가 나오면 위치를 저장, 커맨드 블록에 들어왔다고 알려놓은 뒤
']'가 나오면 저장된 위치부터 지금 위치까지를 떼오면 됩니다!
떼오면서 잠깐 확인하는 위치를 조정하는 것이 끝이네요~
public static string CommandChecker(char preText, string targetCommand)
{
//대상이 2글자보다 작다면, 내용이 없는 것으로 공백을 반환합니다.
if(targetCommand.Length <= 2)
{
return "";
}
//2글자보다 크다면, 내용이 있으므로 맨 앞 뒤에 있는 대괄호를 자릅시다.
else
{
targetCommand = targetCommand.Substring(1, targetCommand.Length - 2);
};
//대괄호를 자르고 넘어온 내용물을 확인합니다.
switch (targetCommand)
{
//저는 Info라는 클래스에서 내용물을 받는 걸로 만들어놓았습니다.
//여기서 원하는 내용을 실행하셔도 되고
//다른 곳에서 내용을 받아오셔도 됩니다!
//그냥 커맨드 구분이기 때문에 마음대로 쓰시면 되겠습니다~
case "PlayerName":
return Info.playerName;
case "PlayerClass":
return Info.playerClass;
case "TargetLocation":
return Info.targetLocation;
//여기가 핵심적인 부분입니다.
//확인되지 않은 커맨드인 경우에
//가운데에 '/'이 있는지 확인해서 나누어주도록 합시다.
default:
//나뉘어지는 위치를 저장하는 곳입니다.
int divideIndex = 0;
//나뉘어지는지 확인하는 곳입니다.
bool isDivide = false;
//문자열을 돌면서 '/'를 찾습니다.
for(int index = 0; index < targetCommand.Length; ++index)
{
//'/'를 찾은 경우에 나뉘어진다고 알린 후, 위치를 저장합니다.
if (targetCommand[index] == '/')
{
isDivide = true;
divideIndex = index;
break;
};
};
//나뉘어지는 것이 확인된 경우
if (isDivide)
{
//직전 문자에 종성이 있는지 확인합니다.
if (FinalConsonant(preText))
{
//종성이 있으면 왼쪽 문자열을 반환합니다.
return targetCommand.Substring(0, divideIndex);
}
else
{
//종성이 없으면 오른쪽 문자열을 반환합니다.
return targetCommand.Substring(divideIndex + 1, targetCommand.Length - divideIndex - 1);
};
}
//나뉘어지지 않으면 확인되지 않은 커맨드이므로 공백을 반환합니다.
else
{
return "";
};
};
}
이제 커맨드를 실행해보도록 합시다!
커맨드를 실행하는 함수는 커맨드 들어오기 직전 문자와 커맨드 블럭을 받아옵니다.
[커맨드]의 형태로 받아오기 때문에 앞 뒤를 자를 건데요!
[]같은 걸로 들어오면 바로 돌려보내주도록 합시다.
자른 뒤에는 switch문을 사용해서 커맨드를 걸러내고
등록된 커맨드의 경우 그냥 실행을 해줍니다~
아닌 경우만 조금 생각해보죠!
[아무렇게나 쓰인 커맨드]가 들어왔을 때에는 당연히 아무 것도 하지 않아야 하기 때문에
[이렇게/저렇게]같은 형태로 되어있는지 확인을 해주도록 합니다.
'/'의 위치를 확인하고 저장한 후에, 있다고 알려주기만 하면 되겠죠?
종성이 있으면 왼쪽 / 오른쪽 애를 가져다 주면 되는데!
종성이 있는지 체크만 하면 되겠네요~
가봅시다!
public static bool FinalConsonant(char wantCharacter)
{
//한글의 범위를 넘어서면 종성이 없다고 알립니다.
if(wantCharacter < 44032 || wantCharacter > 55215)
{
return false;
};
//대상 문자를 조정하기 위해 저장해둡니다.
int checker = wantCharacter;
//한글의 시작 위치인 44032를 빼서 0으로 맞춥시다.
checker -= 44032;
//28로 나누어서 나머지가 있으면 종성이 있는 것으로 칩니다.
return (checker % 28) > 0;
}
핵심 부품인데 생각보다 뭐가 없네요?
유니코드는 간격이 일정해서 생각보다 구현이 쉽습니다!
44032를 기준점인 0으로 잡고
28번째마다 종성이 없는 글자가 나오기 때문에
28로 나눠서 나머지가 0인 애들만 false로 주면 되겠죠!
한글이 아닌 경우에는 무조건 false를 반환하도록 하고 있는데요,
만약 다른 언어도 원하는 경우 추가하시면 됩니다.
예를 들어 일본어 っ(12387), ん(12435), ッ(12483), ン(12531)는 종성의 역할을 하기 때문에
이 문자를 예외처리하면 일본어도 구현할 수 있겠죠!
그래도 일단은 이 세 개의 함수만으로 기본적인 구현은 끝입니다!
모두 static함수이기 때문에 아무 데에나 넣고 사용하시면 되겠네요~
<마치며>
이번 포스팅은 꽤나 작은 기능을 만들어봤습니다!
물론 여기서 끝낼 것은 아니구요, 이런 것들을 쌓아서 대화 기능을 만들 예정입니다.
대화를 넘기고, 선택지를 골라 다음 단계로 넘어가는 기능 등을 만들 것이기 때문에
관심이 있으시면 다음 포스팅을 확인해주러 오시면 감사드리겠습니다!
그럼 여기까지 쓰고 글을 마치겠습니다~