레이블이 나랏말인 게시물을 표시합니다. 모든 게시물 표시
레이블이 나랏말인 게시물을 표시합니다. 모든 게시물 표시

2017년 5월 19일 금요일

오랜만입니다!

AdChat에 관한 공지는 어떻게 말씀드려야 할지 모르겠지만...

현재 1.10은 개발 중단되었고, 1.11은 개발 계획이 없는 상태입니다.
학과 내에서 개발팀을 구성해서 좀 더 좋은 질의 모드를 제공하려고 계획하다 보니
개발은 뒷전으로 밀렸네요... 다만 1.12의 개발 계획이 있고, 이를 위한 모듈을 구성하고 있습니다.

혁신적일 것이라고 생각하고 있는인 탈출맵을 제작하고 있습니다.
현재 테스터가 턱없이 부족한 상황이라 테스터를 구할 수 있었다면 좋겠습니다.
혹시 테스터가 되고 싶으신 분은 adchat.kipa00@gmail.com으로 메일 보내 주세요.
이 탈출맵의 아이콘을 구하고 있습니다. 아이콘을 그려주실 분은 맵 전체를 클리어한 분이었으면 좋겠습니다.
역시 같은 곳으로 메일 보내주세요.

감사합니다.

2016년 3월 1일 화요일

AdChat 1.9!

KIPA00이 릴리즈 4시간 만에 AdChat을 만들어 냈습니다!

대단히 죄송합니다!


이번 버전을 MUM으로 만들었는데, 이 프로그램이 아무래도 베타 버전에 있다 보니까
클래스 파일을 수정하는지 제대로 확인하고 썼어야 하는데 가짜 field가 남아 있었나 보네요.
Tab을 누르면 크래시가 나는 것을 수정했습니다.

주저리주저리


릴리즈를 학수고대하다가 나오자마자 만들기 시작해서 4시간 만에 완성했습니다.
아무래도 공부만 하다 보니 이쪽으로 두뇌 회전이 잘 안 돼서, 기능이 줄었는데도 만드는 데 힘은 더 든 것 같네요.

+ 한글 입력 기능을 추가했습니다.
+ 업데이트 기능을 추가했습니다. AdChat 1.9 버전을 계속해서 사용하실 경우, 업데이트가 있을 때 알림을 받게 됩니다.
+ 한자 입력 기능을 추가했습니다. 입력키는 Ctrl + Space입니다(이것은 맥에 RCONTROL이 없기 때문이며 저 자신을 위한 배려이기도 합니다.).
+ 자잘한 세팅이 추가되었습니다.
+ 이스터 에그 다섯 개가 추가되었습니다. 세 개는 1.8.8 때, 두 개는 이번에 새로 추가된 것.
o 글꼴 오류(뀕이 꿝 등으로 보이는 오류)를 수정했습니다.
- 글꼴 너비 오류와 출력 오류가 1.9에 와서 드디어 수정되었습니다. 제가 수정할 필요가 없었습니다.
- 한/영 상태 반투명화 표시를 없앴습니다.

이번에는 파일의 링크를 일부 카페에 게시할 생각입니다.
아무래도 한국 마인크래프트 유저분들은 네이버를 훨씬 많이 이용하시니까...
사실 지금 시각이 새벽 4시쯤 돼서(스웨덴 시각으로 저녁 8시) 많이 피곤해요.
그래서 1.8.8 틀 가져와서 그대로 복사 붙여넣기했습니다. 넓은 아량으로 이해해 주세요.
이 모드가 아마 1.9 최초의 모드이지 않을까 싶습니다!

한자 입력에 관해


배우고 때때로 그것을 익히니 또한 기쁘지 아니한가!

Ctrl + Space를 눌러 한자를 입력할 수 있습니다.
이 키 조합은 어쩔 수 없는 선택이었습니다. 맥에서는 한자 입력이 Alt + Enter입니다.
이중 Alt는 Gui 단계에서 받을 수는 있으나 캐릭터 코드로 분류가 안 되어 이용할 수 없고,
Enter를 이용하려면 Gui의 근본적인 구조를 싹 뜯어고쳐야 합니다.
Shift + Space는 이미 슆스 기능에 이용되고 있기 때문에 Ctrl + Space를 단축키로 정했습니다.
빈도 정리가 잘못됐는지 한자음이 빠진 것이 더러 있더군요. 발견한 것은 수정하였습니다.
혹시 더 발견하게 된다면 이 글에 댓글로 빠진 한자음을 달아주세요.

파아일 파아아일


이번 버전은 natives가 없습니다. 이유는 귀찮아서(...)입니다. 죄송합니다.
인간이 고3이다 보니 귀차니즘이 장난이 아니네요. (?)
딱히 잠금을 걸거나 그런 것은 아니니 일반적인 분해기로 natives를 얻으려면 얻어질 겁니다.
파일 링크는 아래에 있습니다.
AdChat 1.9
AdChat 1.9 natives

2차 수정/미수정 배포는 제 허락을 받고 해 주시기 바랍니다.
이는 추후 문제가 발생했을 때 추적할 범위를 줄이기 위함입니다.
적용법을 물으시는 분들이 많아서 해당 링크의 파일 설명(정보 란)에 적어 두었습니다.

항상 한글패치와 AdChat을 이용해 주시는 분들께 감사한 마음 드립니다.

2016년 2월 13일 토요일

한자 전체를 빈도순 정렬했습니다.

네이버를 이용해 먹었습니다. 한자사전 DB가 아주 잘 돼 있고 긁기도 쉬워서...
한자 입력 기능은 1.9부터 지원될 예정입니다. 한자 데이터베이스가 110KB 정도 되니까
효율적으로 압축하여 파일 용량을 줄이고, 무결성 검사를 하여 어떤 음에도 잘못된 한자가 배당되는 일이 없도록 처리할 것입니다.

가능하다면 말이지요...

2015년 9월 28일 월요일

AdChat 1.8.8!

KIPA00이 8시간 만에 AdChat을 만들어 냈습니다!

주저리주저리


시험 때문에 빠르게 업데이트하지 못해 죄송합니다.
그만큼 확실히, 열심히 만들었습니다. 업데이트한 것:
+ 한글 입력 기능을 추가했습니다(당연한 소리!).
+ 업데이트 기능을 추가했습니다. AdChat 1.8.8 버전을 계속해서 사용하실 경우, 업데이트가 있을 때 알림을 받게 됩니다.
+ 자잘한 세팅이 추가되었습니다.
+ 이스터 에그 세 개가 추가되었습니다. 각각 1.8.1, 1.8.3, 1.8.8 때 추가된 것.
+ 슆스 기능을 추가했습니다. 아래에서 더 설명하겠습니다.
o 글꼴 너비 오류(마우스 클릭 위치가 한글 위치와 맞지 않는 오류)를 수정했습니다.
o 글꼴 오류(뀕이 꿝 등으로 보이는 오류)를 수정했습니다.
o 출력 오류(ㅏ, ㅑ, ㅣ 모음 등이 보이지 않는 오류)를 수정했습니다.
이전 버전(AdChat 1.8.3 업데이트)과의 차이점:
  • 슆스 기능을 추가했습니다.

슆스 기능에 관해


이러니까 ShiftStudios 홍보하는 거 같은데 사실 맞습니다.
Shift + Space를 눌러 다양한 이모티콘 등을 넣을 수 있는 기능입니다.
이를테면 "하트" + Shift + Space, "현좌표" + Shift + Space 등. 이용해 보세요!

앞의 것을 열쇠나름대로 Key 순화해 보려 한 겁니다, 뒤의 것을 값이라고 합니다.
동봉된 shortener.txt의 문법을 보시면 아시겠지만, (열쇠)=(열쇠)=...=(열쇠)=(값)으로 쓰시면 적용됩니다.
값에 "=" 문자를 쓰실 필요가 있을 경우 "\equals"를 입력해 주세요. 슆스 기능을 이용하실 때는 열쇠를 정확하게 입력 후 Shift + Space를 누르시면, 설정해 놓은 값으로 바뀝니다.
열쇠에 영문자, 숫자나 특수문자를 사용하지 마세요.
"현좌표"의 경우는 조금 특별한데, 실제 x, y, z 좌표가 들어갈 자리에 "%x", "%y", "%z"를 입력해 주시면 잘 작동합니다.

저랑 내기할래요?


베타 개발 기간을 합치면 저는 여태까지 4년간 한글패치/AdChat을 개발해 왔습니다.
여러분께 파일을 배포한 기간만 2년이 되었고요.
그런데 제가 지금 고등학생입니다. 조금 있으면 바빠질 텐데요.
그래서 AdChat을 공동으로 개발할 개발자를 찾습니다.

당연히(?) 제 조건에 부합하는 사람만 개발하실 수 있겠죠.
간단합니다. 이번 AdChat 1.8.8 파일은 모든 클래스를 잠갔습니다. 웬만한 프로그램으로는 쉽게 제가 수정한 파일을 볼 수 없습니다.
어떤 방법을 쓰든지, 제가 수정한 파일을 알아내서 제 메일, k2pa00@gmail.com으로 수정한 모든 파일의 리스트를 정확히 보내시면 당신은 AdChat을 개발할 조건에 부합하게 됩니다.

일반 사용자나 통합패치, 자동설치기 개발자 분들에겐 죄송하지만 그래서 이번 한글패치는 natives가 없습니다.
완벽한 수준의 AdChat을 제공하려고 노력하지만, 이번 버전은 개발 기간이 짧은 만큼 여러분의 넓은 아량으로, 베타 버전이라고 생각해 주세요.

기다리고 기다리던 파일


AdChat 공동 개발 같은 거 필요없고, 파일만 먹으면 되는 분들을 위해 링크를 준비했습니다.
AdChat 1.8.8
AdChat 1.8.8 natives

내기 때문에도 그렇고, 항상 그랬듯이 2차 배포 엄격히 금지합니다.
적용법은 안 바뀌었는데, 적용 위치가 약간 바뀌었습니다(매우 중요합니다!).
Windows 유저 분들은 %appdata%\.minecraft에, Mac 유저 분들은 ~/Library/Application Support/minecraft에 넣어 주세요.

AdChat 1.8.3 사용 횟수는 15-09-28 02:38 (KST) 기준 36,008회입니다.
이렇게나 많은 분들이 사용해 주셔서 정말 감사합니다!

2015년 9월 27일 일요일

추석입니다. / AdChat에 관해

AdChat에 관해서 문의해 주시는 분들이 있는데
얼마 전에 제게 1.8.3 버전 한글패치 버그를 지적해 주셨던 분의 서버 구성원 중 한 분이
IME input checking을 고쳐서 임시방편으로 만들어 놓은 1.8.8 한글패치가 있더군요.
1.8.8 버전은 일단 그걸로 대체하는 걸로 알고 있어 주시면 감사하겠습니다.
그분 한글패치의 링크는 여기입니다(모든 형태의 2차 배포 금지).

제 AdChat의 개발이 늦어지는 이유는 시험 때문입니다. 다른 거 없어요 고딩 바쁩니다ㅠㅠ
저와 룻트님 둘 다 고2라서 내년에 한글패치를 개발할 사람이 없을지도?!
추석을 맞아 1.8.8 AdChat을 개발하려고 봤더니 모듈이 엉망진창이어서...
일단 되는 대로 힘써 보고 안 되면 스킵하는 걸로 하겠습니다.

AdChat 다운로드 링크도 없는 긴 글 읽어주셔서 감사합니다.
오늘 추석이네요. 모두 즐거운 추석 되시기 바랍니다!

2013년 3월 26일 화요일

마인크래프트 한글패치 1.5.1

여기에 있다구요오오

상위 트래픽 소스가 아니면 거들떠보지도 않는 검색엔진이 미워 죽겠음.
그래서 트래픽 올리기 위한 검색어 입력!

제발 무시해 주세요.
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치
마인크래프트 한글패치 1.5.1
마인크래프트 1.5.1 한글패치

2013년 2월 22일 금요일

마인크래프트 키파의 한글패치

룻트님 한글패치 오류 고쳐주려 하다가 아까워서 배포합니다.
제 한글패치는 한글패치 본연의 기능에만 신경썼습니다. 그래서 한글을 입력하는 부분은 룻트님 것보다 좋을지도 모르나, 기능은 정말 허접합니다. 다양한 기능을 원하시면 룻트님 한글패치를 사용하세요.

다운로드 (1.4.7)
다운로드 (1.5.1)

링크를 누르면 낯선 화면이 보입니다. 당황하지 마시고 파일(F)의 다운로드(S)를 눌러주세요. 다운로드가 바로 시작됩니다. rar 파일입니다. 절대로 알집으로 풀지 마세요. 제 윈컴(윈7) 기준으로 안 됩니다. WinRAR나 다른 좋은 압축 소프트웨어를 사용하세요.

수동 설치입니다. 저는 자동 설치같은 거 안 하구요, 자동 설치기 만들어주시려고 하시는 분들 정말 감사합니다. 그러나 이런 모드같은 거 배포할 때는 출처를 명확하게 알려주세요. 프로그래머에게 버그 리포트는 생명입니다. 버그가 엉뚱하게 설치기를 만든 사람에게 보고되는 어이없는 사례가 적지 않으니, 배포할 때는 꼭 출처를 밝혀주세요. 저뿐만 아니라 다른 프로그래머들이 써도 된다고 한 거 전부요. 출처는 kipa00.blogspot.com(이곳)이나 정 여의치 않으면 네이버 블로그(kipa00.blog.me)로 남기셔도 됩니다. 아! 네이버의 폐해란!

국민 대다수가 네이버를 쓰고 있다는 사실이 한심하군요. 물론 저도 씁니다만... 저도 한심하군요. 참... 아직까지 네이버를 끊지 못한다니...

2013년 2월 12일 화요일

세종대왕님이 정말로 존경스럽습니다.

우리는 그냥 평소에 이렇게 말하고 쓰고 읽고 (한글을) 듣고 하는데, 제가 실용적인 한글 압축 알고리즘을 만드려고 연구를 하던 도중에 놀라운 사실을 발견했습니다.

웬만한 일상한국어에서는 받침이 없는 글자가 전체의 60% 이상을 차지합니다.

덕분에 '받침 플래그를 추가해도 괜찮을 것 같다'는 생각이 들었습니다. 받침 플래그 1비트를 추가하면 오히려 압축이 더 될 것 같습니다.
그리고 메신저에서는 통신어를 잘 쓰니까, 모음 있나 없나 플래그도 추가하면 안... 되겠지요? 그건 좀 그렇네요. 왜냐하면 'ㅋㅋ'나 'ㄴㄴ', 'ㅌㅌ'등을 위해서 나머지 98%가 1비트씩 희생하면 안 되니까요.

근데 경어체를 실험 안 해 봤네요;; 반어체에서는 확실히 60% 이상이더라구요.

추신: 이 글귀를 포함해서, 여기 있는 글자들 중 받침이 없는 글자는 전체의 53.3%입니다. 경어체에서는 약간 줄어드는군요. 그래도 50% 이상이니 뭐...

2013년 1월 20일 일요일

한글 입력 이론 (2)

지난번에 한글을 바꾸는 원리에 대해 제대로 설명하지 못한 것 같아서
개요를 짚고 넘어갑니다. 여기에는 버퍼도 포함합니다.

1. 주어진 스트링을 input라 하자.
2. 버퍼 스트링 buffer와 결과물 ret를 빈 스트링으로 선언
3. for i = 0부터 input.length()-1까지 반복
4. buffer에 input[i]를 붙인다
5. if buffer에 분할이 필요하면(아래 버퍼스트링이 변환 후 2글자 이상이 된다면)
(분할된 문자열 앞부분을 buffer.front 뒷부분을 buffer.back이라고 하면)
6. buffer.front를 한글로 변환해서 ret에 붙인다
7. buffer = buffer.back(버퍼에 남은 뒷부분을 저장한다)
8. endif
9. endfor
10. 버퍼에 남은 문자열을 한글로 변환해서 ret에 붙인다.
11. 결과물: ret

이해가 안 되시나요? 일반적인 한글 입력과 완전히 똑같은 의미입니다.
해설을 하자면,
5에서 '분할이 필요하면'이란 것은 지난번에 봤듯이 'rksk' 같은 걸 입력했을 때
'k'와 's' 사이에서 끊어야 ('가|나') 한다는 겁니다. 끊어야 할 필요가 없을 때는
버퍼에 데이터를 계속 추가(4번)하고, 끊어야 할 때는 끊어서 앞부분을 한글로 변환('가')
해서 붙이고(6번) 남은 'sk'를 buf에 저장(7번)한다는 겁니다.

분할이 필요 역할을 하는 함수가 needSeperating입니다.
이 함수는 분할이 필요할 경우 분할할 곳의 위치를,
아닐 경우 0을 리턴합니다.
한글로 변환 역할을 하는 함수가 assemble입니다.
전체적인 역할을 하는 함수를 translate라고 하면, 우리는 이제
translate의 코드를 짤 수 있습니다. 여러분이 한 번 해 보세요.


.

.

.

코드는 다음과 같습니다.

아, 참고로 저는 UTF-8에서 코드를 썼기 때문에 wstring 대신 string을 쓴 겁니다. wstring의 코드가 필요하시면 직접 변환(1분도 안 걸립니다.)하시거나 저한테 달라고 해 보세요.

1:  string translate(string input) {                        // 1
2:     int i, len = (int)input.length(), temp;
3:     string buffer, ret;                                 // 2
4:      for (i=0; i<len; ++i) {                             // 3
5:          buffer += input[i];                             // 4
6:          if ((temp = needSeperating(buffer))) {          // 5
7:              ret += assemble(buffer.substr(0, temp));    // 6
8: 
            buffer = buffer.substr(temp);               // 7
9: 
        }                                               // 8
10: 
    }                                                   // 9
11: 
    ret += assemble(buffer);                            // 10
12: 
    return ret;                                         // 11
13: 
}
14: 


#include <string>과 using namespace std;는 당연히 되어 있을 거라 생각하고
짠 코드입니다. 제가 저 위에서 얼마나 친절하게 설명을 했는지
보이시죠? 그냥 그대~로 코드를 짜면 됩니다.

이제 needSeperating의 코드를 봅시다. needSeperating은 특수문자가 보이면
'어, 끊어야 할 곳이네?'하고 끊어버립니다. 입력 원리를 그대로 적용하기 때문에 그렇습니다.
이건 지난 시간에 아~주 철저하게 설명을 했으니, 넘어가고 코드만 보겠습니다.

#define INT char
int needSeperating(string input) {
    int i, len = (int)input.length();
    int last = -1, temp;
    INT *arr;   // 0 : 자음(초성/종성 불확실) 1 : 모음

                // 2 : 확실한 종성 (다른 것과 조합 가능)
                // 3 : 자음(종성)과 조합 가능한 모음
                // 4 : 조합 불가능한 종성(포화상태)
    arr = (INT *)malloc(sizeof(INT) * len);
    for (i=0; i<len; ++i) {
        if (isJaum(input[i])) {
            *(arr + i) = 0;
        } else if (isMoum(input[i])) {
            *(arr + i) = 1;
        } else {                //특수문자가 보이면,
            if (i == 0) {       //첫번째에 특수문자가 있을 경우
                free(arr);      //메모리 관리
                return 1;       //1을 리턴한다. [1]
            } else {            //아닐 경우
                free(arr);      //메모리 관리
                return i;       //끊어야 할 곳을 리턴한다.
            }
        }
    }
    //여기서부터 i를 거치고 난 value 0은 확실한 초성이 된다.
    //검사를 하다 확실히 끊어야 할 상황이 되면 return
    for (i=0; i<len; ++i) {
        temp = *(arr + i);
        if (last == -1) {
            last = temp;
        } else {           //이전 거, 다음 거, if 문 처리가 막 보이죠?
            if (last == 0 && temp == 0) {
                free(arr);
                return i;
            } else if (last == 0 && temp == 1) {
                *(arr + i) = 3;
            } else if (last == 1 && temp == 0) {
                free(arr);
                return i;
            } else if (last == 1 && temp == 1) {
                if (!isAssemblableMoum(input.substr(i - 1, 2))) {
                    free(arr);
                    return i;
                }
            } else if (last == 2 && temp == 0) {
                if (!isAssemblableJaum(input.substr(i - 1, 2))) {
                    free(arr);
                    return i;
                } else {
                    *(arr + i) = 4;
                }
            } else if (last == 2 && temp == 1) {
                free(arr);
                return i - 1;
            } else if (last == 3 && temp == 0) {
                *(arr + i) = 2;
            } else if (last == 3 && temp == 1) {
                if (!isAssemblableMoum(input.substr(i - 1, 2))) {
                    free(arr);
                    return i;
                } else {
                    *(arr + i) = 3;
                }
            } else if (last == 4 && temp == 0) {
                free(arr);
                return i;
            } else if (last == 4 && temp == 1) {
                free(arr);
                return i - 1;
            }
            last = *(arr + i);
        }
    }
    free(arr);
    //검사를 해도 끊을 게 없다면 return 0
    return 0;
}


[1] 왜 1을 리턴할까요? 0을 리턴하면 '끊을 곳이 없다'라는 사인이 될 뿐만 아니라, 이미 끊어져 있는 부분을 또 끊어요? 말이 안 되죠. 그리고 특수문자 다음엔 어떤 것이 오든 그대로 2글자가 됩니다. (ex ' g' -> ' ㅎ')

컬러링 힘들었음 ㅠㅠ
isJaum, isMoum, isAssemblableJaum, isAssemblableMoum
(Assembleable인지 Assemblable인지 모르겠음)이라는 4개의 함수가 선언 없이 사용되었는데요, 이 함수들의 코드는 다음과 같습니다. 저 긴 코드에 비하면 진짜 간단해요 :D
bool isJaum(char input) {
    return (strchr("rRseEfaqQtTdwWczxvg", input) != NULL);
}
bool isMoum(char input) {
    return (strchr("koiOjpuPhynbml", input) != NULL);
}

bool isAssemblableJaum(string input) {
    string ok[] = {"rt", "sw", "sg", "fr", "fa", "fq", "ft", "fx", "fv", "fg", "qt"};
    int i;
    for (i=0; i<11; ++i) {
        if (input == ok[i]) {
            return true;
        }
    }
    return false;
}
bool isAssemblableMoum(string input) {

    string ok[] = {"hk", "hl", "ho", "nj", "nl", "np", "ml"};
    int i;
    for (i=0; i<7; ++i) {
        if (input == ok[i]) {
            return true;
        }
    }
    return false;
}

네. 이거에요. 그냥 레퍼런스 관리가 귀찮아서 함수화한 겁니다. 물론
저 Assemblable 시리즈는 약간 코드가 길지만 _로 분리해서 find로
훨씬 더 쉽게 할 수 있었어요. 다만 처리 속도가 길어지므로 pass

assemble은 합치는 건데요, 단일 한글(ex 'ㄱ', 'ㄴ', ..., 'ㅏ', 'ㅑ', ... 'ㅚ')은 받아서
바로 처리해주고, 완성 한글이라면 (ex '가', '한', '수')
맨 앞 한글자는 무조건 초성으로 하고
이후에 나오는 자음은 종성이,
이후에 나오는 모음은 중성이 되는 거죠. 두말 않고 코드를 보겠습니다.

string assemble(string input) {
    if (input.length() == 1) {   //길이가 1일 경우 무조건 단일
        string origin = "rRseEfaqQtTdwWczxvgkijuhynbmloOpP";
        string change

        = "ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎㅏㅑㅓㅕㅗㅛㅜㅠㅡㅣㅐㅒㅔㅖ";
        int i, len = (int)origin.length();
        for (i=0; i<len; ++i) {
            if (origin[i] == input[0]) {
                return change.substr(i * 3, 3);     //UTF-8은 한글이 3칸이에요.
            }
        }
        return input;
    } else if (input.length() == 2 && isAssemblableMoum(input)) { // [1]

        string origin = "hkhlhonjnlnpml";
        string change = "ㅘㅚㅙㅝㅟㅞㅢ";
        int i, len = (int)origin.length() / 2;
        for (i=0; i<len; ++i) {
            if (input == origin.substr(i * 2, 2)) {
                return change.substr(i * 3, 3);       //UTF-8은 한글이 3칸이에요.
            }
        }
    } else {
        int cho = JaMoValue(input.substr(0, 1), 0), jung, jong;
        int unicode = 0xAC00;
        string ret;
        char UTF8Value[4];
        string temp = input.substr(1);
        int i;

        // 종성을 찾을 때까지 돌려서 중성/종성을 끊을 위치를 찾는 겁니다.
        for (i=0; i<input.length(); ++i) {
            if (isJaum(temp[i])) {
                break;
            }
        }
        jung = JaMoValue(temp.substr(0, i), 1);
        jong = JaMoValue(temp.substr(i), 2);
        temp = "";
        unicode += (cho * 588 + jung * 28 + jong);       //UTF16BE 형태
        UTF8Value[0] = 0xE0 + (unicode >> 12);
        UTF8Value[1] = 0x80 + ((unicode % 4096) >> 6);
        UTF8Value[2] = 0x80 + (unicode % 64);
        UTF8Value[3] = 0x00;
        ret.assign(UTF8Value);                           //UTF-8 형태
        return ret;
    }
    return input + "_";     //변환하지 못할 것을 대비해 끊을 곳만이라도 표시
}


[1] 종성, 중성 없이 초성만으로 ㄳ, ㄶ 만드는 건 잘못된 알고리즘입니다. 실제로 이렇게 쓰이는 경우가 거의 없어서 표준에서 뺐습니다. 저건 사실이지만 구현할 수도 있고 구현하기 귀찮다고는 말 못 해

간단하죠?
여기서 아무 선언 없이 쓰인 JaMoValue는 string과 int를 받습니다.
int가 0이면 초성 순서를, 1이면 중성 순서를, 2이면 종성 순서를 리턴합니다.
순서는 다음과 같습니다.


초성

01 23 45 67 89 1011 1213 1415 1617 18

중성

01 23 45 67 89 10
11 1213 1415 1617 1819 20

종성

X
0 1 2 3 4 5 6 7 8 9 10 11 12 13
14 15 16 17 18 19 20 21 22 23 24 25 26 27


표가 뒤죽박죽인 건 귀차니즘 때문에 소수점 안 쓴 거니 이해해주세요 :D
네. 보시다시피 한글 순서 그대로에요.
완성자의 조합 규칙은 이렇습니다.
44032 + (초성순서) * 588 + (중성순서) * 28 + (종성순서)
저 위에서 사용된 게 보이죠. 다시 얘기하지만
int형이 0이면 초성 순서, 1이면 중성 순서, 2면 종성 순서를
리턴합니다. 그렇다면 코드가 어떻게 짜여질지 눈에 선하게 보이죠?
물론 여기서는 한글로 했지만 내부적으로는 영어를 썼으므로
전부 영어로 (초성의 경우 'r=0 R=1 s=2 e=3 E=4'...) 해야겠죠.
코드는 다음과 같습니다.

int JaMoValue(string input, int value) {
    if (input.length() == 1) {
        string origin;
        if (value == 0) {
            origin = "rRseEfaqQtTdwWczxvg";
        } else if (value == 1) {
            origin = "koiOjpuPh///yn///bm/l";
        } else if (value == 2) {
            origin = "/rR/s//ef///////aq/tTdwczxvg";
        }
        int i, len = (int)origin.length();
        for (i=0; i<len; ++i) {
            if (origin[i] == input[0]) {
                return i;
            }
        }
    } else if (input.length() == 2) {

        string origin;
        //초성은 무조건 한 글자이므로 value 0 나오는 게 이상하죠.
        if (value == 1) {
            origin = "//////////////////hkhohl////njnpnl////ml";
        } else if (value == 2) {
            origin = "//////rt//swsg////frfafqftfxfvfg////qt";
        }
        int i, len = (int)origin.length();
        for (i=0; i<len; ++i) {
            if (origin.substr(i * 2, 2) == input) {
                return i;
            }
        }
    }
    return 0;
}


이해하셨나요? 여기까지 코드를 작성하고, 모든 함수에 대한 Prototype을 쓰신 다음,
main을 알아서들 쓰시고 컴파일해 보세요.
wstring으로 코드 바꾸는 건 메일하면 해 드립니다. :D
저는 메인을 이렇게 썼습니다: 이건 예제니 굳이 복붙하실 필요 없어요.

int main(int argc, char* argv[]) {
    string temp;
    while (1) {
        getline(cin, temp);
        cout << translate(temp) << endl;
    }
}


오늘은 어떻게... 강의 좀 잘 쓴 것 같나요?
너무 어렵게 설명한 건 아닌지... 약간 걱정이 됩니다.
다음 시간에는 진짜로 한글 입력 이론을 하겠습니다.
이건 string에서 data를 읽어서 buffer 처리를 한 것에 불과해요 :D

컬러링 잘못된 거 있으면 댓 달아주세요. 이게 댓 달면 메일이 오네 ㄷㄷ

2012년 12월 23일 일요일

한글 입력 이론 (1)

제가 이번에 한글 변환기(예를 들면 dkssud안녕으로 바꿔주는 기계)에 해당하는 함수를 만들 필요성을 느껴서 만들기로 했는데,
예제가 전부 말이 아니고 어떤 건 유료인 것까지 있어서 제가 그냥 강좌를 쓰기로 했습니다. (한글 오토마타라고 하나요?)
초기에 만든 것(주의! 예전 블로그인 네이버로 갑니다.)은 haansoft 뿐만 아니라 모든 자음분할, 모음분할자에 문제가 있어서 새로 만든 겁니다.

예전 알고리즘은 뭐였냐면 모든 완성자는 초성이 반드시 한 번을 입력한다.의 원리를 이용한 겁니다.
예를 들어 dkssud이 있으면, 뒤에서부터 읽어가며, 자음을 봅니다.
(편의상 자음을 파란색, 모음을 빨간색으로 표시하겠습니다.)
dkssud에서 두 번째부터 첫 자음을 초성으로 처리합니다. (초성은 검은색)
dkssud
이제 분리가 되었죠. 이게 바꾸는 원리였는데
원리를 보니 왜 자음분할, 모음분할자에 문제가 있는지 알겠죠.
그래서 이번 알고리즘은 일정한 문자를 보고 입력원리를 그대로 적용합니다!

자음분할자로 해 보겠습니다. 으-음... 예제는
rsefkiju로 해 보겠습니다. 끊는 위치를 찾는 겁니다.
끊는 위치만 찾으면 그 다음부터는 쉬우니까요.
1) 먼저 자음 = 0, 모음 = 1을 넣습니다.
특수문자가 보이면 바로 끊습니다.
r
s
e
f
k
i
j
u
0
0
0
0
1
1
1
1
어이구 표 한번 그리기 진짜 힘드네
2) 이제부터
초성 = 0
앞에 초성이 없는 중성 = 1
합성 가능한 종성 = 2
초성이 있는 중성 = 3
포화상태의 종성 = 4
이라고 합시다.
다음 진리표에 따라 값을 채웁니다.
전 값이 없으면 그대로 둡니다.
전 값
현 값
행동 요령(값)
0
0
그 곳에서 끊음
0
1
3
1
0
그 곳에서 끊음
1
1
합쳐지지 않으면 끊음
2
0
합쳐지면 4 아니면 끊음
2
1
그 전 곳에서 끊음
3
0
2
3
1
합쳐지면 3 아니면 끊음
4
0
그 곳에서 끊음
4
1
그 전 곳에서 끊음
이렇게 하면 되겠죠. 옛한글이라면 입력기가 어떻게 되었을까 생각도 드네요.
이 예제는 채우다 중간에서 끊기네요. r과 s 사이에서 끊기네요.

조금 더 좋은 예제로 해봅시다.
qnpfrz로 해 봅시다.
q
n
p
f
r
z
0
1
1
0
0
0
이렇게 됩니다. 그 다음에 시도해 보면, (시도 중인 건 빨간색으로 표시합니다)
q
n
p
f
r
z
0
3
1
0
0
0
첫 번째 n입니다. 0-1이므로 3으로 값을 바꿉니다.
q
n
p
f
r
z
0
3
3
0
0
0
두 번째 p인데, 3-1 조합에서 n과 p는 합쳐지므로(ㅞ로 합쳐짐) 3으로 바꿉니다.
q
n
p
f
r
z
0
3
3
2
0
0
세 번째 문자 조합에서 3-0이므로 2로 바뀝니다.
q
n
p
f
r
z
0
3
3
2
4
0
네 번째 문자 조합에서, 2-0 조합인데 합쳐지므로(ㄺ) 값을 4로 바꿉니다.
q
n
p
f
r
z
0
3
3
2
4
0
다섯 번째의 문자 조합 4-0은 그 곳에서 끊습니다. 즉 'qnpfr' 'z'로 끊기는 겁니다.
이 문자열을 변환하여 합치는 알고리즘은 유니코드를 조금만 해 보셨다면 금방 알 수 있습니다.
바꾸면 '뷁' 'ㅋ'입니다.
즉 주어진 문자열은 '뷁ㅋ'가 되는 것입니다.

(이번 강의는 잘 썼나요?)

다음에는 이것을 구현하는 코드를 같이 보도록 하겠습니다. 이 알고리즘을 구현하기 위해서는 버퍼라는 게 필요한데, 그것도 설명하도록 하겠습니다.