파일 연결이 무엇인지 먼저 살펴보면,


(윈도우 XP 에서의 파일 연결 설정 창)

 
이놈 얘기다.

사실 여기서 그냥 설정하면 된다.

그런데 왜 굳이 레지스트리를 뜯어야하는고 하니,


윈도우7에서는 고급 세부 설정이 불가능하더란 것이랬다.


(위와 같은 고급 속성 창 얘기다. 고급 속성은 존재하는 확장자도 있고 아닌 녀석도 있다)


윈도우7에서 하고 싶으면 외부 프로그램을 쓰라니, 아오 빡쳐 같은 저질스런 상황이?

고급 설정을 해야하는 이유가 몇 가지 있는데, 대표적으로 스크립트 파일 같은 것을 열 때(더블클릭 또는 엔터 키 등)

그냥 파일 형식을 devenv.exe 같은 것에 연결해두면 기존에 IDE가 열려 있더라도 새 인스턴스로 열리게 된다.

그냥 그대로 쓰자니 왠지 소금물을 사발로 들이켜는 기분이지 않은가?
(뭐, 갈증을 참을 수 있다면 그대로 써도 상관 없겠다)



그래서 그냥 레지스트리 수정 방법을 공유하고자 한다.
(이하 스샷은 모두 윈도우 XP 에서 가져왔지만 윈도우7 에서도 다르지 않다)


일단 기본적인 파일 연결은 해두도록 한다. 생짜로 모두 레지스트리를 만들려면 귀찮기 때문!





(현재 글을 쓰고 있는 시스템에는 익스프레스 버전 뿐이다. 보통은 등록되어 있지 않으니 찾아보기로 경로를 잘 찾아들어가자)

 

저렇게 해서 추가하고 나서 아래 레지스트리 편집 프로그램(regedit.exe)를 이용하여 레지스트리 경로로 가보자

HKEY_CURRENT_USER  \  Software  \  Microsoft  \  Windows  \  CurrentVersion  \  Explorer  \  FileExts  \

그럼 이곳에 .sp 라는 키 이름을 찾을 수 있고 하위에 OpenWithList 라는 키와 OpenWithProgids 라는 키를 갖고 있는 것을 확인할 수 있다.


중요한 것은 OpenWithProgids 키다. 여기서 sp_auto_file 이라는 이진 데이터가 있음을 확인하고서, 다음과 같은 레지스트리 경로로 옮겨가자

HKEY_CLASSES_ROOT  \  sp_auto_file  \
 

알기 쉽게 되어 있다. shell 키 밑에 edit 와 open 이 있는데 edit는 마우스 우클릭했을 때 편집 메뉴로 연결되는 녀석이고

open은 물론 열기 메뉴에 연결되는 녀석이다.

편집 메뉴에 보면 ddeexec 라는 녀석이 있고 여기에 몇 가지 인자들이 적혀 있는데,

그리 어렵지 않으므로 구성에 대한 분석은 건너뛰고 open 이란 녀석을 edit 와 동일하게 만들어주면 된다는 사실만 강조하고 넘어가자.

똑같이 만드는 것이 귀찮은 사람은 open 을 지우고 edit 의 키 이름을 open 으로 바꿔도 좋다.

Posted by OOJJRS
,
SomeHeader.h

#ifndef _SOME_HEADER_
#define _SOME_HEADER_

// 헤더 내용들이 많이 있다.

#endif


OtherHeader.h

#pragma once

// ... 뭐 있겠지



두 가지 방식의 차이점이라든지 비교우위 등을 알고 싶어하는 이들이 많다.

짤막하게 짚고 넘어가자면 전자(SomeHeader)는 언어 차원의 표준이며 컴파일러가 항상 지원해야하는 믿을 수 있는 기능이고

후자(OtherHeader)는 일부 성능 좋은(?) 컴파일러만 지원하는 변종 기능이다.

변종인 만큼 후자의 방식은 전처리기가 아예 파일도 열어보지 않고 컴파일을 건너뛰고,

전자는 일단 파일을 열어보고 건너뛴다는 차이점을 가지며, 변종이 조금 더 속도에서 우세하다고 한다.



맞는 얘기들이었지만 사실 이젠 좀 철 지난 이야기들이다.

실제로 #ifndef 든 #pragma 든 둘 다 전처리기(Preprocessor) 지시자이며 컴파일을 하기 전에 먼저 처리되는 것들이다.

#ifndef 와 #endif (#else 라든지 #elif 도 좋겠다) 사이에 수백만줄 정도가 있다면 #pragma once 와 차이가 좀 벌어질 수 있으나

일반적인 기준에서 잘 짜여진 헤더파일(그 자체로 문서로 여겨질 수 있는 요약본 형식의)의 크기 안에서는

그럭저럭 무난하게 무시해줄 수 있다.
(물론 그렇다고 해서 그 헤더 파일이 수천번 include 되어도 무난하다는 소리는 절대 아니다)


중요한 것은 정말로 파일 전체가(꼭 헤더에 한정되지 않는다) 동일 obj 에서 한 번만 로딩된다면 #pragma once 도 괜찮겠지만

다른 조건들이 포함되어 있다면 #ifndef 를 활용하는 것이다. #ifndef 의 원래 용도를 잘 생각해보자.
(혹자는 #pragma once 가 표준이 아님을 들어 #ifndef 를 권하고 있으나 사실 가장 널리 사용되는 상위 컴파일러들은 모두 지원하고 있으므로 딱히 문제가 되진 않는다)

만약 헤더 파일이 수천줄 쯤 된다면, 그리고 그 헤더 파일을 많은 곳에서 include 한다면 #ifndef 보다는 #pragma once 를

쓰는 것이 확실히 전처리 속도에 도움이 된다.


감이 잘 안 오는 사람들을 위해 아래와 같은 스샷을 마련했다


 

대충 파일 구조는 위의 것을 보면 쉽게 알 수 있을 것이고

컴파일 결과를 그냥 IDE에서 컴파일해버리면 알기 어려우니 직접 명령어를 조작하여 컴파일을 시도해보는 것이 좋다.

원래 영어 IDE 를 사용하지만 스샷을 위해 한글을 마련했다. 왕친절한 것 같다.


 

 
<아 이렇게 친절할 수가.jpg> 



result.cpp 의 내용을 보자


 

드문드문 보이는 빈 공백들은 바로 전처리기가 #ifndef ~ #endif 같은 조건에 걸려 삭제해버린 줄들이다.
(물론 쓸모 없는 전처리기 구문 자체도 지우기 때문에 원래 3번 라인 밑에 나와야 하는 #ifndef _IFNDEF_ 같은 구문이 모두 빈줄로 대체되어 있다)


other.cpp 의 경우를 보아야 #ifndef 와 #pragma once 의 차이점을 확실히 알 수 있다.

other.cpp 는 22줄에서 보다시피 처음으로 ifndef.h 파일을 포함하였는데 이후 middle.h 에서 다시 한 번 포함한다(36줄)

그러나 앞서 이미 포함되어 있었으므로 모두 공백으로 대체되었다.

그러나 30줄에서 보다시피 other.cpp 는 pragmaonce.h 파일도 먼저 포함하였는데 이후 44번째 줄에서 보다시피

middle.h 가 선언한 #include "pragmaonce.h" 명령은 공백으로 대체되어 아예 포함조차 시도하지 않았다.

이것을 통해 #ifndef 와 #pragma once 의 실제 움직임을 조금은 따라가 볼 수 있는가?



#line 지시문에 대해서는 관심있는 사람들에 한해 찾아보도록 남겨두겠다.



그런데 주의할 점이 있다. 여기서 1회 포함 및 생성이란 얘기는 "파일 단위"에서의 이야기다.

그리고 이것이야말로 Unity Build 라는 기법이 왜 필요한가의 이유가 된다.


내가 심심해서 main.cpp 와 other.cpp 두 개를 만들지는 않았을 것이다.

우리는 보통 #ifndef (또는 #pragma once) 를 사용하면 헤더 파일을 전체 프로젝트에서 한 번만 include 할 것이라고

착각할 수 있는데(또는 별 신경 안 쓰고 넘어갈 수 있는데) 위에서 보다시피, 사실이 아니다.

한 번만 include 하는 것은 모두 오브젝트 단위에서의 이야기다.

오브젝트가 다르면 제 아무리 이전에 8만줄짜리 파일을 포함하여 컴파일을 이미 진행했다 하더라도

컴파일러는 다시 컴파일을 진행해버린다는 이야기다.

이런 짓을 하지 않기 위해 Unity Build 라는 스킬이 등장한 것이고 이와 관련해서는 서툰 내 글솜씨보다

훨씬 잘 된 자료가 있으므로 그것을 공유하며 이 포스팅을 마칠까 한다


http://www.slideshare.net/devcatpublications/ndc2010-unity-build




 


Posted by OOJJRS
,



-,.- 이거 한 지 꽤 된건데(이미지 날짜로 보니 8월 13일이네)

왜 여태 안 올리고 까먹고 잇엇징~ 
Posted by OOJJRS
,



이제 고수라고 자칭해도 될랑가잉~

1212점부터는 왠지 중급 난이도의 그것과 비슷한 수준의 요행을 바라야 할 것 같다.

내가 기록했던 중급 난이도 최고는 119x점인가가 인데 요거나 뚫어봐야할랑가~?
Posted by OOJJRS
,



앗사방구날라리~

처음이다!!! 계속하면이상태에서도늘긴하는구나이것도!!
Posted by OOJJRS
,



3월 17일 기록이니 한 4개월 됐구나


근데 그때 스샷을 왜 저따위로 찍었을까 ㅡ ㅡ
Posted by OOJJRS
,

사실 구글링 하면 금방 답이 나오지만 (how steam retail install)

한글로는 정보가 안 뜬다는 점이 슬프므로 블로깅해본다.
(정말 스팀은 정이 안 가는 플랫폼이다)



1. 만약 스팀의 게임 목록에서 내가 CD로 가지고 있는 게임이 다운로드되고 있다면, 오른 버튼을 눌러 delete local content 해준다.
(이후 등장하는 확인창도 delete 버튼 클릭)





2. 스팀을 끈다.


3. 내가 가진 스팀 게임 시디를 넣는다.


4. 윈도우키 + R 또는 시작 메뉴 - 실행을 눌러 다음과 같이 쳐넣는다

스팀경로\steam.exe -install 시디경로:
(ex : c:\program files\steam\steam.exe -install D:)


즐거운 게임 되시길

ps : 스팀이 실행 안되는 문제에 대해서는 나름대로 국내에도 많은 정보가 있어 블로깅을 생략한다
Posted by OOJJRS
,



책의 소개는 그림과 같다. 임상 심리학의 분야로 사람을 연구함에 있어 더 위를 찾기 어려울 만큼 장기적으로 진행된 프로젝트인데 그만큼 파장도 컸고, 권위도 있었다고 하겠다.



이 책을 한 마디로 요약해 보자면, 보고서다. 재밌는 글귀나 긴장감 넘치는 진행을 바라는 사람은 최대한 피해야할 책이다.

물론 연구 결과 자체가 충분히 신선하므로 이쪽 동네에 흥미를 가진 사람이라면 재밌게 볼 만하지만 잘 팔리는(?) 심리학 서적들보다는

훨씬 무미건조하다. 게다가 많은 내용을 담고 있기 때문에 최대한 글을 요약하여 쓰다보니 결론 도출 과정이나 근거 자료들이

생략된 부분이 많다. 그래서 자칫 잘못하면 허공에 붕 뜬 말처럼 보일 위험이 있는 내용들도 더러 있었다.
(그런 내용들은 대개 "...연구해 보니 결론은 이러했다." 라거나 "...했더니 이렇게 되었다." 식으로 요약되어 있었다)

읽으면서 과정을 더듬어보려면 많은 기반 심리학 지식을 요구하는 책이고, 그렇지 않다면 그냥 이 연구 결과를 <되도록 믿는다>는 심정으로 읽는 것을 추천한다.
 
심리학자 및 전문가들이 20년에 걸쳐 분석한 자료이므로(그것 자체가 또 위험을 내포하고 있긴 하지만) 그렇게 틀린 말을

하진 않았을 것이다. 만약 당신이 근거가 뭉뚱그려진 말을 잘 못 믿는 편이라면, 그냥 읽지 마라. 심신이 나빠질지도 모른다.





책의 내용으로 들어가서, 보고서 같은 내용이라 요약하기가 쉽지 않지만, 그래도 핵심만 짚어 얘기해보자면.

일단 먼저 생각해야할 것은 오래 사는 것이 과연 중요한 것인가, 그리고 오래 산다는 것은 무엇을 의미하는가이다.

어떤 사람은 오래 살 필요 없이 주어진 삶을 행복하게 살아야 한다고 말하고, 또 어떤 사람은 일단 건강해야 한다고 말한다.

단순히 오래 산다고 말하면 굉장한 오해의 소지가 있기 때문에 미리 밝히는데 이 책에서 이야기하는 긴 삶이란,

<행복하고 건강한 삶을 오래오래> 라는 의미로 사용한다. 완전하지 않은가? 저런 삶을 거부하는 사람은 염세증 환자를 제외하면

거의 없을 것으로 생각된다.



본론으로 들어가서, 책의 내용 전반에 걸쳐 가장 중요한 사실을 하나 이야기하는데, 오래 사는 사람들은 대부분 성실한 사람이라는 것이다.

우리는 흔히 일을 성실하게 하고, 작은 일 하나도 매사에 최선을 다하며 너무 일에 매달려 사는 사람은 스트레스를 많이 받아 오래 못 산다고들 이야기한다.

하지만 연구 결과는 그 내용이 사실이 아님을 밝혀내고 실제로는 성실한 사람일수록 오래 살 가능성이 있다고 한다.

근거는 그럴싸하다. 성실한 사람일수록 자신의 건강도 성실하게 챙기고 일이든 가족이든 성실하기 때문에 성공할 가능성이 높다는 것이다.

그리고 인생의 태반이라고 할 수 있는 일과 가족에서 성공하면, 그래도 실패한 것보다는 좀 더 행복하지 않을까?

그리고 그런 긍정적인 제반 상황이 오래 살 수 있는 기반이 된다는 것이다. 물론 아플 때 약을 챙겨먹는 것도 성실한 사람이

좀 더 잘 챙겨먹을 것은 분명하다.

이 책이 좀 더 참신한 이유는 오래 사는 이유로 습관이나 운동, 건강식 등의 조건 외에 위와 같이 성격이라는 요소를 고려했기 때문이다.



사교성이 좋은 사람이 더 오래 살 것인가, 건강과 행복은 무슨 관계인가, 사랑/결혼/이혼은 어떤 영향을 미치는가,

어릴 적 외향적인(그리고 많이 뛰어노는) 아이가 좀 더 공부에 매진한 아이보다 과연 건강한가, 종교는 어떤 영향을 끼치는가,

스트레스는 과연 수명에 악영향을 주는가 등의, 하나 같이 흥미를 자아내는 내용들로 구성되어 있다.

이 외에도 여러 가지 논제가 거론되어 있으며 그 연구 결과는 위의 성격 예시처럼 꽤 흥미진진하다.



개인적으로 가장 깨달음을 얻었던 부분은 바로 "낙관론자의 태도가 항상 삶을 건강하게 해주진 않는다" 였다.

낙관적인 태도는 자칫 자신의 현재 상태가 건강하지 않음에도 불구하고 모든 것이 잘 될 것이라는 생각을 갖게 하여

건강 대비를 소홀히 하거나 악화되도록 만들어버린다. 과유불급이란 단어가 역시 고금의 진리라는 생각도 떠오른다.




종교와 수명의 관계도 꽤 인상 깊었다. 이 책에서는 종교가 마음의 안정을 준다는 심리적인 관점이 논란의 여지가 많음을

인정하고 그것보다는 종교를 가진 자의 행동이 어떠한 건강한 습관을 이루는가에 대해 초점을 맞추어 이야기했다.

적당한 종교 활동은 좀 더 건강한 인생 활동(외부 활동, 담배/술과 같은 악영향의 차단, 커뮤니케이션의 형성 등)에

도움을 줄 수 있다. 역시 그럴 듯한 근거라고 여겨졌다.

종교와 관련된 이야기는 대부분 원치 않는 논쟁 속에 휘말릴 수 있기 때문에 자세한 것은 책을 참조하라고 이야기하고 싶다.




안타깝지만 그렇게 성실하지도, 부지런하지도 않은 본인은 이쯤에서 짧은 리뷰를 마쳐볼까 한다.
Posted by OOJJRS
,


해당 방식은 내가 만든 것이 아니라 이미 숙련된 프로그래머라면 대부분 쓰고 있을(또 만들 수 있는) 방법이다.

그 동안 이 방식보다는 다른 방법을 이용했었는데, 이번에 라이브러리화시키면서 끝내 만들게 되었으므로 하는 김에

조금 더 시간을 투자하여 많은 분들(특히 프로그래머 지망생들)에게 소개하고자 한다.

이 글을 읽기 위해서는 꽤 폭넓은 기반 지식이 필요하다(깊을 필요는 없다).

가능한한 모든 연관된 것을 설명하겠지만 링크나 검색 등으로 때우는 경우도 있을 것이다.




switch(sz)
{
  case "Name":
    ...
    break;
  case "Age":
    ...
    break;
}


위와 같은 구문이 C나 C++ 상에서 돌아가지 않는다는 것은 이미 잘들 알고 있는 사실이다.
(당연하죠! ... 때문에 컴파일 오류가 나잖아요? 라고 반문하는 사람은 없길 바란다)


switch 명령은 case가 많을수록 이론상 if문과의 성능 차이가 기하급수로 벌어지게 되어 있다.

이론상인 이유는 if - else if 열거식의 경우에도 만약 비교하는 대상 중 어느 한쪽이 상수라면 현대의 똑똑한 컴파일러가

자동으로 점핑 테이블을 만들어 마치 switch - case처럼 동작하기 때문이다.

상수 값(예제) 호출될 주소 (상대값)
2 0x0000
4 0x0016
6 0x0048
8 0x0052

※ 상수 값에 따라 IP 는 마치 함수호출처럼 자신의 주소를 향해 펄쩍 뛰어간다. break 구문을 만나면 switch 가 끝나는 블럭으로 또 한 번 점프한다. break 나 continue 는 goto 구문의 다른 (특수한 용도로 한정된) 이름이다.




그런데 상수가 아닐 경우는 점핑 테이블 생성이 불가능해진다. 아니, 불가능하다기보다는 우리가 원하는 그 성능이 나올 수가 없다.

점핑 테이블을 쓰는 것은 시간이 오래 걸리는 테이블 생성 작업을 1회 한 후(2011. 05. 31. 수정되었음) 한 번의 비교로 원하는 곳으로 찾아가 작업하겠다는 성능을 기대하는 것인데

변수가 case에 들어가게 되면 일반적인 if - else if 로밖에 구현이 안되는 것이다.

EIP = EIP + jmptable[nIndex];


하지만 변수가 case 에 가능하다고 하면 대체 jmptable 은 얼마나 커져야할까. 게다가 변수가 상수가 아닐 경우에는

nIndex 값 자체가 산출되지 않아 저런 식으로는 불가능하고 연결리스트 따위를 동원해야하니 속도 성능 같은 건 나락으로

떨어지는 것이다.




그래서 보통 빨리 짜야하거나 몇 가지 안되는 케이스를 가진 문자열 비교는 고전적으로 if - else if 로 행해진다

if(strcmp(sz, "Name") == 0)
{
}
else if(strcmp(sz, "Age") == 0)
{
}
else ...

이런 연쇄 else if 구문을 볼 때마다 속이 메스껍고 피로함을 느낀다면, 반갑다. 나도 그렇다.

게다가 더 큰 문제는 else if 중첩에는 한계가 있다는 것이다(error code C1061 참고)
(물론 정확히는 else if 중첩만이 문제가 아니지만, 어쨌든 많이 쓸 수가 없다)


그래서 이것을 개선한다면, 전통적으로 map이나 hash_map 을 사용해왔다.


static map< string, int > m;
void Init()
{
  m["Name"] = ENUM_NAME;
  m["Age"] = ENUM_AGE;
}

void func()
{
  switch(m[sz])
  {
    case ENUM_NAME:
      ...
      break;
    case ENUM_AGE:
      ...
      break;
  }
}



오, 오오... 깔끔하게 스위치 전환에도 성공했고 성능상의 이점도 가져왔다.

아 그런데... 뭐 좀 부족함이 느껴지지 않는가?

저런 문자열 비교 루틴이 수십 군데서 사용된다고 생각해보자(물론 다들 문자열 목록이 다른).

그런 경우가 얼마나 있겠냐고? 단순히 스크립트 데이터를 해석하는 코드만 해도 저런 것들이 부지기수다.


그래, 난 근성가이야. 하면서 그 수십 군데의 루틴을 모두 저렇게 열거형 상수를 선언하고 맵으로 고친다...

못할 건 없다. 초기화 함수 하나 만들고 변수 하나 선언하고 열거형 쯤 만들면 되지.

하지만 저런 부분들은 노가다 작업이다. 아무나 해도 될 뿐더러 저런 작업을 하고 있으면 굉장한 손해를 본다.

성능상으로야 가장 퍼펙트하지만(map 대신 hash_map을 쓴다면, 비록 표준은 아니지만 더욱 성능으로는 우위에 설 수 있을 것이다)

코드의 유지보수 관리에는 개떡이 되는 것이다.

매번 문자열 토큰(대개 태그라고도 하는데) 하나 추가될 때마다 상수도 추가해야하고 초기화에 라인도 추가해야하고

뭔가 작업할 곳이 최소한 세 군데가 생기는 것이다(열거형 상수쪽, 초기화쪽, switch 안)



그래서 생각하기를, 그냥 case 하나 달랑 추가하면 그 모든 걸 자동으로 해주는 신통방통한 녀석 없을까? 다.

완전 베스트 아닌가? 소개한다.


아래 소개된 코드는 앞서 이야기한대로, 내가 최초 사용한 기법은 아니지만, 작성은 스스로 했다(내 입맛에 맞게).

하지만 이해하기 쉽도록 몇 부분을 고쳤으며 이대로 활용하기엔 조금 불안한 부분이 있음을 미리 고백한다.
(뭐, 실무용으론 조금 부족하지만 예제용으론 그냥 써도 된다)


#define STR_SWITCH_BEGIN(key) \
{ \
  static stdext::hash_map< string, int > s_hm; \
  static bool s_bInit = false; \
  bool bLoop = true; \
  while(bLoop) \
  { \
    int nKey = -1; \
    if(s_bInit) { nKey=s_hm[key]; bLoop = false; } \
    switch(nKey) \
    { \
      case -1: {

#define CASE(token)  } case __LINE__: if(!s_bInit) s_hm[token] = __LINE__; else {
#define DEFAULT()    } case 0: default: if(s_bInit) {

#define STR_SWITCH_END()   \
      }      \
    }       \
    if(!s_bInit) s_bInit=true; \
  }        \
}



실제 사용은 다음과 같이 한다

STR_SWITCH_BEGIN(sz)
{
  CASE("Name")
    ...
    break;
  CASE("Age")
    ...
    break;
}
STR_SWITCH_END()


자, 이제 우리 입맛에 맞는 것들이 준비되었다. 앞으로 case가 추가된다 하더라도 단지 저곳에만 작업해주면 된다.

그럼 나머지 부분들은 모두 자동화되어 처리된다.

그런데 여기서 그냥 납득하지 않고 의문을 갖는 분들이 있다. 현명한 자세다. 세상에 공짜는 없는고로, 저런 게 그냥 됐으면

진작에 C++ 에서 표준으로 제공했을 것이다.

map(또는 hash_map)을 사용한 switch 보다 손해보는 점은 case 구문마다 if가 들어간다는 점과 위에서 미리 사용한

변수들이 자칫 기존 코드와 겹칠 수 있다는 점, 마지막에 STR_SWITCH_END 가 들어가 혼동된다는 점 등이다.

하지만 이 정도는 감수할 수 있을 정도에 불과하며, 사후 코드 관리 측면에서는 월등한 이점을 가져다준다.



마지막으로, 코드만 봐도 쉽게 이해할 수 있겠지만 위의 것을 사용할 때 놓치기 쉬운 몇 가지 주의점에 대해 언급하고자 한다.




1. 최초 1회에 한하여 초기화 과정을 수행하는데, 따라서 처음 1번의 비교는 제법 시간이 걸린다(모든 요소에 대해 일단 다 돌고 나서 원래 비교하려고 했던 값을 비교하므로). 그러니 만약 1회 비교하고 끝나는 루틴 같은 곳에는 저거 쓰지 말기 바란다.


2. hash_map 은 현재 C++ 표준이 아니다. 물론 그럴 확률은 지극히 낮지만, 언제 가서 코드가 메롱해질지 장담할 수 없다. 불안하다면 그냥 map 을 써도 좋다. map 은 표준인 만큼 견고함에 있어서는 해시보다 월등하다. 물론 비교 성능은 해시보다 좀 떨어진다.
(그래서 나도 해시맵을 탐탁찮게 생각하는 사람 중 하나이며, 해싱이라는 스킬 자체가 과연 키 해싱에 대해 무결성이 가능한지에 대해서도 회의적이므로 늘 불안불안하다)


3. VS 상에서 컴파일할 경우 릴리즈모드는 상관없으나 디버그모드에서는 컴파일 옵션을 바꿔주지 않으면 컴파일이 되지 않는다. 저 부분이 무슨 역할을 하는지는 검색하면 금방 나오니 활용하길 바란다.


원래 디버그 모드는 Program Database for Edit & Continue 옵션으로 되어 있는데

이것을 Program Database 로 바꿔주어야 한다(릴리즈 모드 옵션이다)


__LINE__ 이라는 매크로 때문인데 이것이 /ZI 옵션 상에서는 상수가 아니라 변수로 제어되기 때문에

상수로 코드에 박히는 /Zi 옵션이 필요한 것이다.
(case 에는 상수만 쓸 수 있다는 것을 다시 한 번 상기하자)


4. 실제 돌려보면 알겠지만, case 부분에 변수가 사용 가능하다! 하지만 컴파일만 되는 것일 뿐, 수행 결과는 첫 1회 초기화 때의 값에 따라 switching 되어 나온다. 너무 들뜨지 않길 바란다. 왜 그런지는 소스를 보면 잘 알 수 있을 거라 생각된다.



5. 혹시나 해서 첨언한다. 위의 예제 코드와 내가 만든 코드 사이의 차이는 언어 코드(MBCS / UNICODE) 분석 부분과 여러가지 예외 / 에러 처리 등이다.




많은 이들에게 도움이 되었으면 한다.


Posted by OOJJRS
,

wowfull.exe




결국 수천 덩이의 풀을 손수 쪼갤 수 없어 만들게 되었다.

라지만 좀 늦은 감이 있다. 예전에 한창 할 때 만들었어야 했는데 이놈의 구차니즘은...



자동쥐 셋팅 같은게 귀찮아서 나만의 프로그램을 만들어 사용하는 편이다. 코딩은 취미니까!

누군가에게는 도움이 될지도 몰라서 올려본다.


그러고보니 참 간만에 블로그에 글 싸지른 것 같다
Posted by OOJJRS
,
말은 거창하지만 실상은 단순하다.
 
많은 선배 프로그래머들이 충고하고 MEC++ 책(※ More Effective C++ 스캇 마이어스 저, 항목 5)에서 강조한 부분이 있기도 하지만 후배 프로그래머들에게 잘 와닿지도 않는 모호한 경고를 앵무새처럼 늘어놓고 싶은 생각은 없다.
 
나는 대원칙만 제대로 세워두었다면 암시적 변환의 실보다는 득을 더 많이 얻을 수 있다고 생각하는 편이다.


다음 코드를 보자.

struct SB_TYPE_RELS    // 인간관계를 나타내는 타입
{
private:
unsigned char value;
public:
operator char*()
{
static char* s_sz[] =
{
"일반",
"아군",
"적군",
};

return s_sz[value];
}
};

위의 타입은 실제로 어떤 ENUM 타입의 값을 갖고 있지만 그것을 텍스트로 출력할 필요가 있기 때문에

다음과 같이 사용된다.

SB_TYPE_RELS Rels = SB_RELS_일반;
cout << Rels << endl;

이 경우 Rels는 암시적으로 char* 로 변환이 가능하기 때문에 operator <<  함수를 따로 정의하지 않아도 컴파일러가 처리해주게 된다.


그런데 주의할 점이 있다.

다음과 같은 가변 인자에서
printf("관계 : %s", Rels);

우리 생각엔 Rels가 "일반"으로 바뀌어서 %s 자리에 쏙 들어가야할 것 같은데 실제 컴파일을 하면 에러가 난다.

이유는 printf 가 내부에서 만들어질 때 %s를 만나면 인자에서 주소를 대입하게 되는데 그 모습이 우리 상상과는 많이 다르기 때문이다.
 
요점만 얘기하자면 컴파일러는 Rels가 char* 로 변환되어야한다는 사실을 모른다. 그래서 끔찍한 메모리 포인터 에러를 뱉는다.



두번째 주의점은 암시적 변환의 경우 미리 정의만 되어 있다면 얼마든지 몇 번에 걸쳐 변환을 해주지만,

변환 생성자의 경우는 외부 타입 -> 현재 클래스의 타입으로 만들어내는 방식이기 때문에

컴파일러가 두 번 이상 암시적으로 변환을 해주진 않는다는 것이다.


꿍얼꿍얼... 뭔 소린지 모르겠으니 역시 예제 코드를 보자


class CString
{
public:
CString(const std::string& sz);          // STL 의 string 타입을 통해 CString 인스턴스를 생성한다

bool operator == (const CString& s);  // 두 문자열이 서로 같은지 비교해본다
};

void main()
{
    CString  s("Hello World");      // 문제 없다. "" 문자열은 string 으로 암시적으로 한차례 변환되어 들어간다
    if( s == "Hello" )                   // 에러! "Hello" 를 CString 으로 변환할 수가 없다
}


오우, 안되는게 당연하지! 라면서 명확해 보인다고? 당신은 앞으로 크게 될 가능성이 있다.

그럼 다음과 같은 경우는 어떨까?


class CString
{
public:
CString(const std::string& sz);          // STL 의 string 타입을 통해 CString 인스턴스를 생성한다
CString(const CString& o);               // 익숙한 복사 생성자가 추가되었다
};

void main()
{
CString k = "Hello";   // 초기화를 했는데
k = "World";             // 바꾸고 싶다. 근데 에러가 났어 읭!?
}


"헐, 이거 뭐야." 라는 반응이 나오는가 아니면 "당연히 안되지!" 라는 반응이 나오는가? "이게 뭥미" 라는 반응이라면, 당신 조금 곤란하다.

조금만 생각해보면 "World" 는 CString 타입으로 암시적 변환이 가능하므로("World" => string 타입 => CString)

CString("World") 로 변환한 다음 복사 생성자를 호출하면 될 것 같다.

하지만 컴파일러는 저런 연산 과정을 할 수 없다. 우리 눈에만 명확해보이는 것이다.

그 기저에는 복사 생성자가 변환 생성자로 취급되지 않는다는 원칙이 깔려있다는 사실을 명심하자.



6월 14일자로 이 뒷부분의 내용이 생각나서 다시 이어 써본다.

그렇다면 복사 생성자일 때만 저것이 에러가 나는가? 그렇지 않다. 일반적으로 2번 변환을 하게 되면

컴파일러는 변환 과정 중에 n X m 크기의 테이블을 만들어서 변환을 해야할텐데, 이것의 성능이 도저히 측정이 되지 않기 때문에

뺄 수밖에 없는 것이다.


예를 들어 위의 경우 "World" 가 string 이 되고 그것이 다시 CString(const string&) 이 된다는 사실을 찾으려면

1차 변환 대조표와 2차 변환 대조표를 늘어놓고 일일이 매칭을 시켜봐야하는데 이게 몇 개가 나올지 예측이 안되므로

2번 이상의 암시적 변환은 허용하지 않고 있다.
(그러고도 못 찾을 수 있으니 컴파일러 입장에서는 그냥 에러를 뱉는게 속편하지 않은가?)


Posted by OOJJRS
,

서적 Effective C++ 은 개발자에게 널리 알려진 좋은 책이다.

그 중 개정 3판의 20항목에 다음과 같은 이야기가 나온다.


"값에 의한 호출보다는 상수 객체 참조에 의한 호출이 대개의 경우 낫다"


물론 이 책을 이미 정독했거나 충분한 경험을 가진 사람은 왜 저기에 "대개"가 붙는지 알고 있으리라.

그 내용에 대해서는 바로 연이어 21항에서 설명하고 있다.




여기서 살펴볼 것은 20항에서 기본 타입과 STL 반복자, Functor 등에 대해 언급한 부분이다.

저자는 이 세 가지에 대해서는 값으로 넘기는 것이 참조 호출로 넘기는 것보다 낫다고 이야기하고 있다.

원문의 내용을 보지 않아 정확히 해석이 이루어진 것인지 알 수 없지만 일단 위의 내용을 수긍하고 가기 위해

예제를 만들어 테스트를 해보았다.


직접 돌려보기 귀찮은 사람은 결과 텍스트 파일만 보아도 좋다.
 
직접 돌려볼 사람은 디버그 코드가 잔뜩 삽입되는 디버그 모드로 컴파일해서 "이거 순 엉터리잖아!" 라고 하지 말고
 
릴리즈 모드로 최적화를 뺀 상태로 컴파일하기 바란다. 최적화 옵션을 주어 컴파일해버리면 컴파일러가 구조체에 대해서는

알아서 참조 호출을 해버리고 함수 내용이 몇줄 안되면 인라인으로 만들어버린다.

평소라면 똑똑하다며 좋아하겠지만, 이런 테스트로는 참 곤란한 녀석이다.
(VS2008의 컴파일러가 C/C++에 대해 그렇게 효율적인 코드를 생성하지 못한다는 속설에 대해서는 일단 잊자)




테스트는 기본 타입(int), 기본 생성자를 가진 구조체를 자료로 하는 STL 타입(vector 사용), 기본 생성자를 가진 구조체 타입, 사용자가 생성자를 정의한 구조체 타입으로 4개를 등장시켰고

좀 더 많은 대조군과 세밀한 조사가 필요하겠지만 원론적으로는 불필요할 것 같아 생략하였다.




결과 파일에 의하면 중간 중간 스위칭으로 인한 어마어마한 시간 손실을 제외한 유효한 호출값에 대해서

모든 경우에 값 호출보다 참조 호출이 속도에서 우위를 보인다. 저자의 이야기와는 살짝 다른데...?




이론적으로 파헤쳐보자.

분명한 사실은, C++ 에서는 기본 타입이라 하더라도 내부적으로 모두 객체 취급을 받으며 클래스처럼 동작한다.
(물론 진짜 클래스인 것은 아니다)

즉 int i; 라고 선언한다 해서 이것이 C에서처럼 단순하게 메모리에 4바이트 땡기는 것으로 끝나지 않는다는 소리다.

일반적인 구조체/클래스의 생성자 호출과 비슷한 과정을 거쳐 메모리가 초기화되며(0이 아닌 어떤 값, VS 컴파일러는 0xCCCCCCCC로 초기화한다) 서로 대입되는 과정에서는

C에서처럼 단순히 어셈블리 코드의 메모리 로드/스토어 과정으로 변환되는 것이 아닌 operator = 함수 호출이라든지

복사 생성자의 호출 등 <함수 오버헤드>적인 측면이 생긴다.

다만 일반적인 생성과 대입 측면에서 볼 때 최적화 옵션이 주어지면 C++의 기본 인자는 마치 C의 그것처럼 동작한다.

디버그 코드로는 엄청난 내용들이 int i = 3; 이라는 짧은 내용 안에도 미어터지게 들어가서 호출 자체 시간도 약 10배 정도 늘어날 뿐더러

값 호출과 참조 호출은 2배 가량의 격차를 보인다(기본 타입인데...). 하지만 최적화를 하면 걱정할 필요는 없다.
(여기서 우리는 C와 C++의 기본 타입에 대한 차이를 확실히 알 수 있다)

덕분에 기본 타입은 내부적으로 주어진 주소에 메모리 상의 값을 끌어다 집어넣는 1회 연산의 차이로 끝나기 때문에 둘의 속도는 비슷하다. 다음 어셈 코드를 보자.


void f1(int i)를 호출하기 직전 모함수에서의 i 값을 레지스터에 밀어넣는 어셈블리 코드

004401AB  mov         eax,dword ptr [i]     // eax 레지스터에 i 변수의 값을 밀어넣기
004401B1  push        eax                        // eax 레지스터를 스택에 밀어넣기

void f2(const int& i)를 호출하기 직전 ... 어쩌구

00440245  lea           eax,[i]                   // eax 레지스터 값을 i 변수의 주소값으로 만들기
0044024B  push        eax                       // eax 레지스터를 스택에 밀어넣기

※ mov는 제2연산항의 "값"을 레지스터에 밀어넣기 때문에 load 연산(즉, 메모리 접근)이 추가적으로 필요하지만 lea는 주소를 넘기므로 추가적인 메모리 접근이 필요 없어 훨씬 빠르다. 단, lea의 경우 주소값을 써야하기 때문에 레지 대 레지 값 복사는 불가능하다

여기서 볼 수 있듯이 참조 호출이 값에 의한 호출보다 더 빠르다는 것은 일단 여지없는 정론이다.



자, 그럼 STL을 보자. STL은 내부의 모든 존재들이 클래스로 이루어져있다. 반복자도 물론 예외는 아니다.

그렇다면 반복자의 경우도 당연히 값으로 넘겨서 생성/소멸 과정을 거치는 것보다 참조로 넘기는 것이 속도에서 훨씬

빠르리라는 것은 당연히 추측할 수 있고 실제 결과로도 그러하다. 제 아무리 STL이 속도 위주로 설계되었고 최적화되었다고 해도

붕어빵 틀에 재료를 넣으면 붕어빵이 나올 수밖에 없는 것처럼 속도면에서는 값 호출이 참조 호출보다 빠를 수가 없는 것이다.

그 뒤로 이어지는 구조체 호출은 따로 분석하지 않겠다. 워낙 당연하기 때문이다.

void f3(vector< SDATA >::iterator it)를 호출하기 직전 모습

004402EA  lea         eax,[ebp-2Ch]
004402ED  push      eax 
004402EE  call        std::_Vector_iterator<SDATA,std::allocator<SDATA> >::_Vector_iterator<SDATA,std::allocator<SDATA> > (43D789h)
004402F3  mov         dword ptr [ebp-220h],eax

(와, 끔찍해라. 중간의 저 call 명령이 야기할 로스를 생각하며 잠시 묵념. 게다가 STL은 단일 클래스가 아닌 상속의 꽃과 같은 구조들이므로 안에서도 여러 차례 콜이 이루어지고 있다. 묵념을 한 3회는 더 해야할 듯. 게다가 <당연히> 레지스터엔 들어갈 수 없으니 메모리에 살포시 올려두고 함수로 간 다음 다시 찾아와야 한다. 기본 타입 int 형이 자신의 값을 레지스터 eax에 집어넣고 홀가분하게 호출했던 것과 비교하면 극과 극인 셈)

void f4(const vector::iterator&) 를 호출하기 직전 모습

0044038F  lea          eax,[ebp-2Ch]
00440392  push        eax 

(차이가 느껴지시는가?)




자 그렇다면, 여기까지 와서 생각해보자. EC++ 의 저자는 헛소리를 한 것인가?
 
아니면 일부러 교묘하게 독자의 이해력과 집중도를 오판하도록 만든 것인가?
(책의 전반부에서는 분명 "속도"에 중점을 두어 이야기하고 있었는데 후반부에 정리할 때는 다른 이유 때문에 값 호출이 더 낫다고 주장한 것인가?)

그도 아니면 그냥 해석상의 오류인가?

아니면 정말 VS2008 컴파일러가 바보라서 저렇게밖에 안되는건가?(설마 이걸로 믿는 사람은 없다고 생각하겠다)



단호하게 주장하는데, 기본 타입이든 아니든, 위의 내용들은
 
최소한 속도 면에 있어서
값에 의한 호출을 쓸 바에는 상수 참조 호출을 사용하는 것이 훨씬 낫다는 것을 보여주고 있다.

(그리고 그것이 우리의 상식에 부합한다)



그리고 속도 면이 아니라 다른 이유 때문에 위의 세 가지(기본 타입, STL 반복자, Functor)에 대해서는 값 호출이 낫다고

주장한 거라면, 역시 의도를 오해하지 않도록 가필이 필요하다.

어쨌든 책 내용을 수정하셔야 할 듯...
Posted by OOJJRS
,

현재 근황

아직 분류 없음 2010. 2. 19. 08:01
아아, 블로그인지 일기장인지...



졸업하고 취직하여 일하다보니 퇴근하고 두어시간 놀면(!) 작업할 시간이 평일에는 나지 않는다.

주말에 이틀 가량 작업으로 작업량을 모두 대체해야하는데...



여하간 현재 작업들을 모두 손놓고 방치하고 있는 것은 아니다. 조금씩이나마 꾸준히 작업하고 있으니 기대를!

하지만 삼국지 열전의 규모상 이런 속도로 나가다간 언제 완성될지 모르겠다.

후딱 데모판이라도 낼 수 있도록 방향을 잡는 것이 좋을지도.



누리는 버전업하다가 거의 리스트럭처링을 하게 생겼다. 코드를 싹 뜯어고치는구나...



전에 날려먹은 자작 OS도 다시 만들어야하는데..ㅠ_ㅠ 누가 돈 주면서 집에서 코딩만 하라고 하면 그거시야말로 천국!



이외에도 벌려놓은 일이 몇갠지 세기도 어렵다.
Posted by OOJJRS
,


그 동안 분명 뭔가 품사 하나를 빼먹었는데 단어 추가하고 공부하느라 바쁘다고 못 알아봤더랬다.

오늘 보니 부사절 접속사를 빼먹었군...낄렵낄렵

황급히 추가하고 몇 가지 수정하려고 했던 것도 마저 손봐서 다시 올린다

그리고 단어장도 한 번 올려보았다. 어차피 임포트 기능이 있으니 이제 막 올려도 될 듯 싶다.




-----------------------------------------------
구토 Version 3.04 Release 09.12.09.수.
-----------------------------------------------

왠지 시험 공부를 할 때 청소가 땡기는 것처럼, 괜히 토익 공부하는데 코딩이 자꾸 땡긴다... 큰일인데...

하지만 반드시 필요한 기능을 추가한 것이라 거듭거듭 자기 합리화를 하며 1시간을 또 버전업으로 보내버렸지만

당당히 토익 공부를 한다고 생각하고 있다.



...미쳐가는구나.

중간에 3.03은 추가된 기능이 하나밖에 없어 일부러 릴리즈하지 않았지만 이번에는 몇 개 되니 나열해본다.

원래 초기 버전부터 다 탑재하려다가 당장 급하지 않아서 삭제된 내용들이 주를 이루는데,

결국 쓰다보니 없으면 안되겠다 싶어서 부랴부랴 만들었다.



1. 주관식 문제 유형이 생겼습니다. 3.03 추가내용
(정답 판정이 굉장히 단순해서 꼼수가 있지만, 당장 손보긴 어려운 처지입니다)


2. 프로그램 실행 시 오늘의 단어가 소개됩니다. 스페이스 바나 F1 키를 누르면 무작위로 바뀌어줍니다.


3. 평가를 중간에 중지하는 메뉴를 넣었습니다.


4. 사전 병합 기능을 추가하여 다른 사전 파일(물론 구토 전용)의 내용을 통째로 자신의 사전에 가져올 수 있습니다.
(좀 더 세세한 관련 옵션은 다음 버전에 추가될 예정입니다)


5. 이전에 문제 출제시 보기 유형이 출제 단어와 품사가 다른 경우를 조절하였습니다. 버그 수정입니다.
(만약 해당 품사의 단어가 충분치 않다면, 다른 품사가 부득이 출제될 수도 있습니다)


6. 단어장 관리자에서 Ctrl + Enter를 누르면 추가나 수정을 하는 대신 검색을 실행합니다.



현재 툴팁이 제대로 마련되어 있지 않은데 단축키나 관련 부가 사항들은 나중에 한가해지면 추가하겠습니다(...)
Posted by OOJJRS
,
※ 이 글들은 모두 1999년 ~ 2005년 정도까지 운영되던 http://user.chollian.net/~oojjrs 에 올렸던 글들을 편집, 재가공하여 최신화한 내용들입니다. 어린 나이에 어설픈 내용들을 인터넷에 떠돌아다니게 만든 것들에 책임을 져보고자 시작하였습니다(그래서 검색하면 여기 올라온 것보다 더 많은 내용이 있을 수 있습니다). 근데 업데이트는 느릴 수 있어요




성경에 보면 욥기 40장 15절 - 24절에 다음과 같은 얘기가 나온다.


  내 너와 같이 만든 베히모스를 볼지어다, 녀석은 소와 같이 풀을 먹는다.
  그 힘은 허리에 있고 ...(중략)
  그것이 정신 차리고 있을 때에 누가 능히 잡을 수 있겠으며 갈고리로 그 를 꿸 수 있겠느냐 ?


위의 내용은 수많은 베히모스의 설정(?) 중 하나로 성경에서는 하마와 같은 모습으로


본래 베히모스는 사막에 산다고 알려진 전설 상의 괴수다.
 
주로 그 출전과 기원은 복잡한
신화를 지닌 중동 - 아라비아 사막에서 특히 잘 찾아볼 수 있는데,
 
문제는 그 중동의 사막이 워낙 여러 종류의 문명들이 지나다닌 곳이라 체계적으로 확립될 기반이 안되었다는 것이다.

그나마 가장 정설로 받아들여지는 것은 아라비아의 기록과 성경에 인용된 히브리인의 그것인데, 그 외에도 수많은 -

심지어 러시아나 우크라이나의 문화권 영향도 받았음을 알 수 있다.



출처 : 위키 백과


위의 그림에서 중앙에 코끼리 사촌처럼 생긴 동물(?)이 베히모스다.

아라비아 신화에서 바다의 왕자(?)가 레비아탄(밑에 용처럼 생긴)이라면, 육지의 왕자는 바로 저 베히모스다.
(하늘의 왕자 지즈Ziz는 보너스로 알아두자 - 그리핀 비슷하게 생긴 설정이다)
 


히브리어로 B, hemah는 큰 동물이란 뜻이고 강조 또는 복수형으로 Behemoth 라고 한다. 여기서는 큰 몸집을 강조하여

단일 개체를 베히모스로 강조하여 부른 것으로 되어 있다.

저것을 아랍어로 쓰면 여러 가지 버전(?)이 있는데 그 중 하나의 이름은 친숙할 수도 있는, 바로 바하무트다.

Hebrew בהמות, behemot; Arabic بهيموث bahīmūth, or بهموت bahamūt (출처 : 위키)

아랍어는 발도 안담궈본 관계로 저게 대체 무슨 소린지 전혀 설명할 수 없음을 양해해달라.


바하무트 - 베히모스가 일각에서는 용으로 묘사되기도 하지만 이것은 다른 기원에 비해 비교적 최근에 일어난 일로

설정의 과도한 변이가 가해진 것으로 보인다.

서두에 기술한 바대로 베히모스 기원은 워낙 여러 갈래여서, 물고기였다는 설정부터, 공룡, 소, 하마, 심지어 악어로 표현되기도 했다.
 
저런 식이라면 공룡 + 악어로 드래곤이 되는 것도 그다지 어려울 것 같지는 않아보인다.
(드래곤 편에서 다시 설명하겠지만, 용도 계보가 참 다양하다)



그래서 그것을 장황하게 다 나열하기는 그렇고, 몇 가지 공통된 내용과 아라비아/히브리 설정을 기반으로 정리해보자면 다음과 같다.

베히모스는 굉장히 커다란 동물이며, 신을 제외하고는 죽일 수 없다고 한다. 글쎄, 일단 초식(?)을 한다고 나왔으니

온순하지 않을까 싶기는 하지만 기원의 내용에서도 모순된 점이 보이긴 한다.

실제로(?) 베히모스는 레비아탄과 서로 죽이려 드는 사이로 나오는데, 싸워 이긴 녀석이 상대의 음식을 차지한다고 한다.

초식이라던 베히모스가 대체 뭘 어떻게 먹는 것일까? 식물성 플랑크톤을 섭취하는 것일까?

재미있는 사실은, 어떤 성경 해설집에서는 베히모스의 섭식 묘사가 소와 같다는 것에 착안하여, 소처럼 되새김질을 한다는 얘기라고 해석한다.

글쎄... 그것도 나름대로 귀여울 수 있을 것 같다.


출처 : 히어로즈 오브 마이트 앤 매직 3, 이런 녀석이 저 이빨로 되새김질을 한다고...?



그 외에도, 실존하는 동물 - 특히 코끼리나 하마였을 거라고 하는 기원 또한 지배적이다.
 
문제라면, 코끼리는 반추 동물과 관계가 없다는 것이고, 하마는 잡식을 하니 문제지만...

그래도 굳이 실존 동물로 꼽으라면, 여러 뒤죽박죽 설정 가운데 가장 가까운 것은 하마인 것 같다.


출처 : 그림에 나와 있음
Posted by OOJJRS
,

...

내가 급하다고 해서 공개도 빨리 할 필요는 없었는데

이 프로그램이 기존에 나의 다른 제작물들과는 달리 테스트 과정을 거의 다 생략해버린 터라 사용 중에 버그가 뒤늦게 발견된다.

프로그램의 생명인 신뢰성을 그냥 스스로 깎아먹는구나. 차라리 EngWord2가 더 나을지도 모르겠군 쯧쯧...

이러다 오늘 3.1 찍는거 아닐까 모르겠네




-----------------------------------------------------------------------------------
구토 Version 3.01 Release 09.12.07.월.
-----------------------------------------------------------------------------------

이런 우울할 데가

아무리 테스트 시간이 부족해서 후다닥 만들었다지만 이런 기본적인 버그를 내놓고 여태 몰랐을 줄이야.

수정해서 다시 올린다. 얼굴이 다 화끈거린다.

덕분에 하루만에 버전이 0.01 증가해버렸다. 설마 내일 또 치명적인 버그가 생기는건 아니겠지

* 개인적인 단어장 정리가 완성되면 데이터 파일도 첨부할까 합니다




-----------------------------------------------------------------------------------
구토 Version 3.0 Release 09.12.06.일.
-----------------------------------------------------------------------------------


이전에 올렸던 단어장 EngWord2의 업그레이드 버전이다.

그냥 영단어장이라는 프로그램 명에서 구토라는 이름을 붙여보았다.

여러 가지 사정이 있는 이름... 나름대로 맘에 든다.

어휴 - - 그동안 미루고 미루던(?) 단어장 업그레이드인데(사실 그 동안 쓸 일이 없어서 안한 거지만)

하루만에 그냥 싹 해치웠다. 하지만 이것도 당장 급하게 쓰려고 만든 것인지라 자잘한 장식이나 기능은 죄다 빠지고

그야말로 완전 팩트한 것들밖에 안남았다. 1월 중순 쯤 되면 한가해지는데, 그때 생각나면 건드려봐야겠다



기능은 다음과 같다.


1. 단어의 품사 구분 등록, 뜻/예제 등록 분리(예제는 테스트 때 나오지 않는 참고용입니다), 검색 기능(각 품사별 혹은 단어, 뜻, 예제 등의 내용으로 검색이 가능하다)

2. 퀴즈 기능 - 설정에서 문항 수와 제한 시간 등을 설정할 수 있습니다.

3. 퀴즈 시 영->한, 한->영이 랜덤하게 출제

4. 결과 조회 시 각 항목 당 정보 간결 출력

5. 단어 정보 머지 기능 - 새로 입력한 단어 정보가 기존에 있다면, 그 정보를 합쳐서 유저에게 보여준다. 새 단어를 입력할 때 기존에 입력했는지 안했는지 굳이 검색해볼 필요가 없도록 만들었다.


ps : 내가 만드는 프로그램들은 모두 윈도우 레지스트리를 사용하지 않는 대신 환경설정 파일을 생성합니다. GTConfig.ooj 파일을 지우면 매번 자신의 설정이 날아가니 주의하도록 합시다!
Posted by OOJJRS
,



아아

이젠 별 쓸데 없는 걸 다 블로깅하게 되는구나

스파이더 초급은 사실 깨는데만 신경을 쓴다면 어렵지 않은 게임이지만 고득점을 올리려고 하면 생각할 것들이 많아진다.

물리기 기능을 한 번도 안 써야하는 것은 기본이고 미리미리 카드 위치도 잘 맞춰둔다거나 몇 가지 비법(?)도 필요해진다.

저 11패도 깨는데만 신경쓴다면 없을 수 있지만 고득점을 얻겠다고 이런 짓을 하다보면 쌓일 수 있는 패배

잘 모르는 사람은 이런 걸 왜 올리나 하겠지만 스파이더 게임을 조금 유심히, 혹은 깊게 한 사람이라면
 
1210점이 결코 막 나오는 점수는 아니란 사실을 알 수 있을 것이다.




사실 매 분기마다 OS를 재설치하기 때문에 이런 기록들은 쌓아봐야 계속 날아가는데, 이제 차곡차곡 따로 기록을

챙기기로 하였다. 그 중에서도 1210점은 의미가 남다르다. 그 동안 1209점이나 1208점을 매 분기마다 기록해두었지만

90번대 이하로 진입한 것은 처음인 것 같은 기억일 수도(무슨 소리야) - 혹시 기억 못하던 과거에 달성했는지 어쨌는지는

모르지만 어쨌든 기억에 있기로는 처음이구나.


지뢰찾기 같은 손빠르기를 요구하는 기록은 이제 늙어서인지 손목이 뽀개질 듯 하여 도전하기가 쉽지 않지만

이런 카드 게임은 느긋하게 할 수 있다는 게 장점인 듯 싶다.


중급은 아직 1182점에 불과하여 1190점대에 진입하면 스샷 기록을 남길까 생각 중이다.

고급은... 옛날에 한 번 깬 기록이 있는데 기록을 남겨두지 않는 치명적 실수로 인하여 영영 증명이 불가능하게 되었다.

다시 깨야하는데 행운이 같이 맞춰주어야 하는 관계로 쉽지가 않다.
Posted by OOJJRS
,

삼국지 -열전- 은 한 마디로 잡탕 게임이다. 많은 게임의 내용이 뒤섞여 있으며 시뮬레이션의 성격이 강하다.

턴 방식 진행이라고 할 수도 있지만 반-실시간이라고 하는 것이 더 어울릴 듯 하다.

기획 시 가장 중점을 둔 부분은 바로 '현실성'이다. 플레이어와 게임 세계를 동화시켜 현실감 있는 배경이 되게끔 여러 장치를 두어 노력했다.



1. 진행 방식

싱글/멀티 플레이가 가능하며 장수 플레이를 기본으로 한다. 각종 장르의 요소가 섞여 있기 때문에 가장 비슷한 장르를

고르라고 한다면 MMORPG 삼국지를 울티마 온라인처럼 만들었다고 하는 것이 어울릴 것 같다.

1턴은 전략 턴(30초~무제한) / 실행 턴(2분)으로 나뉘어져 있으며 전략 턴에 장수의 행동을 정하고 실행 턴이 되면

동시에 결과가 진행된다. 삼국지6의 그것과 가장 비슷하다고 할 수 있지만 좀 더 확장되어 있다.

실행 턴에는 모든 플레이어(컴퓨터 포함)의 행동이 종료되면 2분을 다 채우지 않더라도 자동 종료된다.

물론 군주나 태수처럼 지휘권을 가진 장수는 다른 장수에게도 임무를 명령할 수 있는데 기존 시리즈처럼 수행하지는 않는다.

이와 관련해서는 다른 글에서 설명할 기회가 있다.

또한 1인칭 시점 개념이 도입되어 플레이어가 군주라고 해도 모든 것을 제어할 수는 없다. 심지어 전투에 출전해도 자신의 부대가 아니면 직접 조종할 수 없다!(간접 조종은 가능하다)

이러한 이유로 삼국지 열전은 필연적으로 좋은 인공지능을 갖추어야 했으며 플레이어의 의도를 각 말단부(?)까지 정확하게

전달할 수 있는 수단을 갖추고 있다.



2. 게임 종료 조건

각 지위에 따른 승리 조건이 따로 마련되어 있을 뿐만 아니라 패배 조건 또한 다양하고 플레이 장수가 죽으면 기존 시리즈처럼

다른 장수를 선택하여 연속하는 것이 아니라 그냥 그대로 끝나게 된다. 이는 나중에 설정을 두어 조절할 수 있게 할까를 고민하고 있다.



3. 멀티 플레이

멀티 플레이를 할 때는 장수의 이름과 이미지만을 선택하고 랜덤한 능력치로 시작하는(타입은 결정할 수 있다) 랜덤 플레이를 지원한다.

삼국지 열전은 장수의 능력치가 은폐되어 있기 때문에 에디터를 사용하지 않는 이상 누가 얼마만큼의 능력치인지 알지 못하므로

랜덤 장수의 시작은 그 특성의 극대화가 된다. 물론 밸런스 파괴 같은 것은 없으며 다양한 방법으로 능력치 차이를 대처할 수 있으므로

관건은 자신의 능력 파악과 가능한 승리 조건을 노리는 전략적 감각이다.



4. 현실성 장치

모든 장수는 어떠한 '판단 기준'을 내재하고 있다. 예를 들면, 관우는 병사 훈련도에 대해 보병80, 극병40, 창병40의 기준 수치를 갖고 있고 장비는 보병70, 극병40, 창병90의 수치를 갖고 있다.
(실제 수치와는 같지 않고 그냥 예제일 뿐이다)

어떤 병사(또는 부대)의 훈련도가 보병75, 극병50, 창병50이라고 해보자.

이때 관우를 플레이하는 유저가 볼 때 그 대상은 보병 B급, 극병 A급, 창병 A급으로 나올 것이고
 
장비를 플레이하는 유저에게는 보병 A급, 극병 A급, 창병 C급으로 보일 것이다.

만약 관우와 장비가 AI라면, 군주인 유저에게 "진언"을 할 때 저 판단을 참고하므로 아마 둘의 의견은 엇갈리게 제시될 것이다.

당연히 관우는 보병 훈련이 더 필요하다고 할 것이고 장비는 창병 훈련이 더 필요하다고 할 것이므로 판단은 알아서...

물론 저 내재된 판단 기준은 공개된 수치가 아니므로 누가 어떤 수치가 더 높은지는 알 도리가 없다.

만약 군주인 유비가 관우처럼 보병80, 극병40, 창병40의 기준을 갖고 있다면, 아마 장비보다는 관우 말을 뽑을 가능성이 더 크지 않겠는가?

유비가 플레이어라도 자기 눈에도 관우처럼 보일테니 말이다.


이런 장치를 보조하는 것이 바로 "평가" 제도다. 평소 유비는 관우를 "보병A, 극병B, 창병B"로, 장비를 "보병A, 극병B, 창병A"로 평가했다고 하자.

그럼 이때 유비는 위의 정보에 기초하여 창병 훈련이 부족하다고 진언하는 장비 말을 선택할 수도 있다.

물론 이 평가도 실제 수치와는 무관하고 어떤 장수 A가 B를 평가하는 기준에 따라 하는 말에 불과하다.

만약 자부심이 높은 장수라면 자신의 능력을 실제보다 더 높게 평가할 것이고 사이가 나쁜 장수가 있다면 그놈의 능력을

더 까서 평가할 수도 있을 것이다. 물론 그것도 장수의 성향에 따라 달라진다. 공사를 엄격하게 구분하는 장수라면

자신의 감정에 따라 다른 장수를 깎아내리는 일은 하지 않을지도 모른다.


즉, 결론적으로 유저는 현실의 우리처럼 장수의 내부 수치가 아니라 "각 장수들이 지각한 내용"에 대해서 받아들일 수밖에 없다.



세부적인 내용에 대해서는 또 다음 기회에 올리도록 하겠다.

Posted by OOJJRS
,

게임 제작이란 원래 눈에 보이는 것보다 보이지 않는 작업이 훨씬 많다보니 그 동안 작업 진척도란 것을 올릴 수가 없었다.

물론 작업을 시작한지도 1년이 훌쩍 넘어섰는데 이것밖에 못한 것은 게으름의 영향도 있다 하겠다.


볼품 없는 성 그래픽에는 일단 참아주시라. 기존 삼국지에서 따다 쓸까도 고려했지만 도대체 저 시점에 맞는 성 그래픽이

5~11 시리즈를 통틀어 한개도 없다니!
(물론 도스 버전은 아예 논외로 했다)


아직 배경 지도로 사용되는 위성 지도 또한 대충 갖다 넣어둔 것일 뿐, 강을 강조하거나 주 구분선, 지형도 같은 것들의

작업은 왠지 요원해보인다.

마찬가지로 저 성들의 위치도 아직 측정이 덜 끝나서 일단 급한대로 10개만 먼저 집어넣은 것이다.
(평원이 가려서 안 보이는데 왜 10개라고 뻥 치냐고 하지 말자)



삼국지 열전에서 성이란 시나리오마다 자유도가 부여된다. 등장 여부가 결정될 뿐 아니라 위치 또한 저대로 사용되지 않을지도

모른다. 시나리오 에디터는 기본적으로 입력된 수십 개의 성의 기본 위치 정보를 갖고 있지만 제작자에 따라서는 아예 딴판의

위치를 사용하거나, 혹은 삼국지에 등장하지 않았던 조그마한 '현'을 만들어 사용할지도 모른다.

시나리오 제작자는 "기본 성 위치 사용하기"를 통해 먼저 지도 위에 성을 깔고 그것을 수정하든지, 자신의 데이터를 갖고 새로

만들든지 할 수 있다.



고민 중에 있는 사실은 게임 진행 중의 성 변동 자율도다. 실제로 삼국지 역사에 기록된 사건만 하더라도 낙양과 신야성이

아예 폐허가 되었으며 백성들을 몽땅 옮겨서 폐허가 된 성들도 후삼국 시대에 몇 존재한다.

때문에 아예 성을 없애거나 새로 만드는 기능을 넣을지 말지 고민중이다.




현재 명령들은 20% 정도 완성되어 있으며 지속적으로 작업 중이긴 하지만 오늘을 끝으로 한동안 잠수를 탈 것 같다.
Posted by OOJJRS
,

Linking Error에 대해서

개발 2009. 11. 24. 16:11
프로그래밍 이론의 가장 외곽이라고 해야할까 - 프로그래머가 당장은 몰라도 처음 배울 땐 크게 개의치 않아도 되는

개념 중 하나가 링킹 과정이다. 컴파일 과정은 사실 건너뛰기도 쉬운 것이 우리나라의 현실 같은데 기본기 장만에

그다지 도움이 되는 현실은 아니라고 하겠다.



어쨌든 그 때문인지 초보 프로그래머나 갓 입문한 사람들은 이 링킹 에러가 등장하면 강도 높은 당황에 직면한다.

많이 쓰이는 Visual Studio를 예로 들어보자(gcc에 대해 다루지 않는다고 너무 미워하지 말라).

(6.0 키 셋팅이라는 가정 하에) F4 단축키를 누르거나 에러 목록을 더블 클릭하면 대개 에러가 난 코드로 이동해준다.

아 근데 이놈의 링킹 에러는 에러라고 뜨면서 컴파일이 안되긴 하는데 어디서 에러가 난건지 도통 알려주질 않으니

고치긴 해야겠고 영어라 무슨 소린진 모르겠고(요즘엔 한글 번역이 되어 나오는 것 같아 그나마 다행스럽다?)

설혹 메시지를 알아보더라도 그 뒤로 솰라솰라 나오는 영문자들은 분명 익숙한 글자 - 내가 만든 함수명 - 들을 찾을 수는

있겠는데 내가 설정한 것들과는 완전 딴판이라 결국 알아볼 수가 없다.

이것을 어떻게 고쳐야하겠는고... 느느니 한숨이요 버리느니 성질이라.



링킹 에러를 접하면 일단 당황하지 않는 것이 중요하다. 컴파일 과정을 자세히 들여다보면 각 파일에 대한 컴파일을 끝내고

링킹 과정을 거친다는 것 정도는 알아낼 수 있다.


[컴파일 과정은 무사히 끝나고 링킹 과정에서 에러가 난 모습]


위와 같은 현상이 일어나는 이유는 간단하다. 컴파일러는 초반에 Func 함수의 원형을 보고
 
"아, 어딘가에 이 함수의 본체가 있겠구나" 라고 생각하고 컴파일을 오류 없이 처리 완료했는데,
 
링킹 과정에 들어와서 실제로 엮어보려고 하니 본체가 어디에도 없는
것이다. 그래서 링킹 에러를 뱉게 된다.

왜 이 에러가 컴파일 시기에 에러를 만들지 않고 링킹 때 만들어내는가 하면, Func라는 함수의 본체가 반드시 같은 파일 내에

소속된다는 보장이 없기 때문이다. 원형은 Test.cpp에 되어 있지만 그 함수의 본체는 Func.cpp에 있을지도 모르는 일이다.

때문에 링킹 과정은 여기저기 흩어져 있을 함수들을 찾아 연결시켜주게 되는데 그 과정에서 컴파일 내용과는 달리

찾을 수 없어서 링킹 에러가 난 것이다.


링킹 에러의 원인은 여러 가지가 존재하지만 근본은 이와 다르지 않다. 다만 저 치명적인 에러(fatal error)를 무서워하지 않고

대처하면 된다. void __cdecl Func(... 어쩌구 라고 나오는 건 그나마 가벼운 경우이며 좀 더 왕창 복잡하게 나오는 경우도

있으나 당황하지 말고 "아 내가 뭔가 함수 본체를 안 만들어주었구나" 라고 생각하고 차분히 찾아보자.



다음은 링킹 에러가 많이 나는 예시다. 초보 시절엔 제법 참고가 될 듯 싶다.

1. 함수 원형과 함수 본체의 형식이 다를 때(또는 본체가 없을 떄) - 즉 리턴 형식이나 인자 형식이 다를 때 툭툭 뱉어낸다. 본질적으로 모든 링킹 에러의 원인이다.

2. 어떤 함수를 실제로 호출하는 코드가 하나도 없다면, 원형만 있고 본체가 없다고 해서 링킹 에러가 나지는 않는다.

3. 다형성Overloading을 이용해서 함수를 여러 개 같은 이름으로 작성했을 때, 실제 호출한 형식에 일치하는 놈이 없을 때 - JAVA/C++ 같은 OOP 언어나 다형성 지원 언어에서 대개 일어나지만 본질은 1번과 같다. 잘 찾아서 수정해주도록 하자

4. 함수가 static으로 외부에 공개되지 않았는데 외부에서 호출해대려 할 때
함수의 본체나 원형에 static이 붙으면 그 함수는 해당 파일 내에서만 사용이 가능하다. 다른 파일에서 호출하려 해도 함수 자체가 외부로 유출이 안되기 때문에 없는 것으로 판단하고 에러를 뱉어낸다. 단 이 경우는 대개 컴파일 단계에서 에러가 잡히며,
드문 경우로 어떤 파일에서 void Func(); 라는 원형과는 달리 그 본체가 static void Func() {} 따위의 내용을 갖고 있다면
링킹 에러를 뱉는다. 이 역시 1번과 본질은 같다.

5. 사실 링킹 에러는 비단 함수에 국한되는 것은 아닌데, 변수의 경우 extern int k; 라는 것을 A.cpp에서 선언했는데 어디에도 int k; 라는 전역 변수가 없거나 static int k; 따위로 선언되어 있다면 마찬가지로 찾을 수 없다고 링킹 에러를 뱉게 된다.


기타 등등의 많은 경우가 생길 수 있는데, 결국 링킹 에러는 컴파일러가 찾으려는 놈을 못 찾아서 생기는 문제다.

너무 두려워만 말고 내가 직접 찾아주자!
Posted by OOJJRS
,


예전에 MSN 메신저가 6.0 ... + 뭐 아무튼 대충 버전이던 시절, 애드온 형식으로 나왔던 지뢰찾기를 시간 때우기로

즐겁게 했었다. 친구나 나나 지뢰찾기 귀신이고 제법 일진일퇴가 대결하는 맛이 있었던 것이다.
(물론 찍기 실력이 더 우수했던 관계로 승률은 내가 더 높았지만)


하지만 주류 메신저를 갈아탄 지금에 와서 겨우 지뢰찾기 때문에 MSN 메신저를 설치하는 것은 많이 귀찮았다.
(2009 라이브는 설치도 왠지 잘 안되고...)

아직도 서비스한다는 보장도 없었고...



하여 친구와 일요일 오후 3시에 만들자, 라고 즉석에서 결의하여 약 6시간 정도의 작업 끝에 1.0 버전을 잽싸게 내놓게 되었다.

프로그램 실행 시


게임 도중 화면



잽싸게 준비하려다보니 개선의 여지가 몇 군데 보이기는 하지만(캐릭터 그림도 없다니!)

그런대로 재미있었다. 좀 더 필드를 키우고 지뢰를 75개로 늘려 균형을 잡았다.

또한 MSN 메신저 지뢰찾기에 있었던 폭탄 아이템 대신 <밟은 폭탄>이라는 개념을 도입하여 일정 크기 이상의 필드를

한꺼번에 열게 되면 횟수를 기록하여 그것이 3회가 되는 쪽이 승리를 취할 수 있는 규칙도 마련했다.

말하자면, 고스톱에서 싸는 것과 비슷하다고 할 수 있겠다 - 우리는 흔히 쌌다고 표현했다.
(이로써 지뢰만 잘 찾는다고 항상 이길 수 있는 것은 아니게 되었다 - 열세에서도 일발 역전 가능! 하지만 너무 크게 싸면 대책도 없다)
(여담이지만, 잘 싸는 친구님이 제안한 규칙이다 - 많이 억울했나보다)



다음 버전에서 개선하려고 준비중인 사항은 다음과 같다.

1. 멀티 플레이 최대 인원 추가(2명 -> 2~4명)

2. 그래픽 효과 추가

3. 지뢰판 및 개수 조절 기능

4. 제한 시간 기능
Posted by OOJJRS
,
삼국지 열전을 제작하기 위한 동기는 다음과 같습니다.


1. AI 계의 발전에 일조

2. 현실적인 배경 및 일본 문화 탈피(일본식 왜곡/재해석, 문화 흡수 지양), 한국 정서에 맞는 한국식 삼국지 문화 형성

단순하게는 서로를 부르는 호칭 같은 대화의 문화에서부터 넓게는 의도적인 역사적 왜곡이나 일본식의 사건 해석 등을 벗어난 한국형 삼국지 게임 제작

3. 다형성(?) 플레이를 제작해보고 싶은 욕구

다양한, 여러 방식을 동시에 조합한 멀티 플레이 지향. 즉 2명이 한 컴퓨터로, 나머지 한 명은 네트워크로 3인이 함께 플레이할 수 있는 방식 등을 지원하고픈 욕심

4. 편리한 UX 제공

5. 높은 수준의 창조적 유저 생산성을 통한 순환 시스템 제작 욕구

게임에서 제공하는 모든 내용을 유저가 동일하게 에디터를 사용하여 만들어낼 수 있는 시스템 제공. 원한다면 자신만의 시나리오를 제공할 수도 있고 반드시 삼국지 시대가 배경이 아닌 다른 역사적인 시대를 배경으로 결과물이 나올 수도 있는 그런 자유성



삼국지 열전의 특징을 다음과 같이 정리할 수 있습니다.


1. 매니지먼트

직접 장수로 성과를 올리는 것이 아닌, 관계와 정책을 통한 관리형 게임

2. 높은 AI

현실이라면 당연히 이래야지? 와 같은 직관성에서 벗어나지 않는 세계의 움직임 구현

※ 개별 대화체 시스템, 정보 수집 결정 시스템, 정보 판단 시스템, 관계 시스템(기획 중), 의사결정 시스템 등

3. 동시 전략, 동시 수행

삼국지6 과 비슷하게, 전략/수행/보고 페이즈로 나뉘어 동시에 현재 상황에서 전략을 상정하고, 동시에 수행하며 결과를 보고받는 시스템

4. 내부 수치 비공개

장수A의 능력치가 내부적으로 수치가 있을지는 몰라도 플레이어에게는 공개되지 않는 시스템. 각 장수의 능력치는 '평가 등급'으로 표현되며, 유비가 관우를 볼 때와 장비가 관우를 볼 때 무력의 '평가 등급'은 서로 다르게 보일 수 있음을 뜻함. 따라서 플레이어는 여러 장수들의 간언을 보고 최종적으로 '선택 행위'를 통해 정책을 선택하는 플레잉 방식을 사용하게 됨

5. 관조자

역사 속의 위인으로는 플레이할 수 없으며(향후 기능 추가로 제공될 여지는 있으나) 기본적으로 자신의 캐릭터를 생성하여 플레이하고 역사에 포함되는 역할.

6. 단일 조작

캐릭터의 지위가 군주라고 하여 다른 장수들의 행동까지 설정하거나 간섭할 수는 없음. 모든 명령의 실행과 그 과정은 자동으로 진행되며 다른 도시의 정치, 행위 등도 모두 자동 위임되어 있음. 오직 플레이어들만이 자신의 캐릭터를 직접 조종할 수 있음.

7. 관계/정치 시뮬레이션

여타 삼국지가 대부분 전쟁과 전투를 테마로 하는 게임이었다면 삼국지 열전은 인간 관계로 대변되는 정치 시뮬레이션. 관계 매니지먼트의 끝은 정치지만 정치가 그리 어려운 개념을 말하는 것이 아님을 강변하는 특징.

Posted by OOJJRS
,

※ 이 글들은 모두 1999년 ~ 2005년 정도까지 운영되던 http://user.chollian.net/~oojjrs 에 올렸던 글들을 편집, 재가공하여 최신화한 내용들입니다. 어린 나이에 어설픈 내용들을 인터넷에 떠돌아다니게 만든 것들에 책임을 져보고자 시작하였습니다(그래서 검색하면 여기 올라온 것보다 더 많은 내용이 있을 수 있습니다). 근데 업데이트는 느릴 수 있어요




녹갈색의 살갗에 90cm의 작은 몸집, 물 위를 뛰어다닐 수 있는 날렵함, 몸집의 절반 가량을 차지하는 긴 꼬리. 상상 속으로 그려본다면 상당히 괜찮은 이미지를 안고 나올 이 도마뱀은 바실리스크라는 현존하는 생물이며. 애완용으로 인기가 있다.



출처 : http://www.dkimages.com



그러나 신화 속에 등장하는 바실리스크는 상당하다. 강력한 괴물일 뿐 아니라 특수능력도 갖추고 있다.


바실리스크는 어원을 그리스 어에 두고 있다. 코카트리스, 혹은 바실리코크 등의 별명으로 불리던 바질리스코스
Basiliskos는 뱀의 왕이라는 뜻이 있다. 또한 코카트리스라는 말을 잘 살펴보면 영어의 악어에 해당하는 크로커다일과 어원이 같다. 대개 바실리스크가 묘사될 때 악어와 비슷하게 표현되는 것은 우연이 아닌 것이다. 하지만 간단하게 악어형 도마뱀! 이라고 하기엔 조금 복잡한 계보를 가지고 있다.


처음 바실리스크는 커다란 뱀의 모습으로 표현되었다. 뱀의 왕이 원래 뜻인 만큼 당연한 일인지도 모른다. BC 1세기경 로마의 플리니우스는 유명한 학자인데, 그의 박물지에 바실리스크가 등장한다. 강력한 독을 가진 카토블레파스와 비교되어 아프리카의 키레나이카 지방을 산지로 들고 있는데, 4미터 가량에 코브라와 흡사한 묘사를 해놓았다. 또 다른 이야기에서는 바실리스크가 뱀의 지배자이기 때문에 모든 독사들이 왕과 같은 바실리스크 앞에서 모두 몸을 땅에 대고 기었기에 오늘날과 같이 스멀스멀꾸물락 기어다니게 되었다고 한다.



출처 : http://www.elfwood.com/



당시만 해도 강력한 독은 있지만 사안(시선이 마주치면 죽일 수 있는)의 능력은 바실리스크에게 없었으며, 족제비가 천적이라는 것 정도로 기술된 것으로 보아 완전히 뱀과 같았던 모양이다.


바실리스크의 독이 얼마나 강한지에 대해서 몇 가지 사례가 있다. 한 기사가 아프리카를 횡단하는 도중 바실리스크와 마주쳤을 때 녀석의 독이 두려워 말에서 내리지 않고 좀 떨어져서 창으로 바실리스크를 찔러 죽였는데
맹독이 창을 타고 올라와 기사와 말이 같이 중독되어 죽었다는 이야기가 있다. 또 비슷한 이야기로 바실리스크의 알을 깼는데 그 독에 중독된다는 내용도 있다. 이것은 중세로 갈수록 심해져서 아예 몸에서 독의 기운을 뿜어내다가 급기야 사안, 혹은 석화 시선(시선이 마주치면 석화되는)까지 발전했다. 이로써 바실리스크는 원래 없었던 신화도 그에 걸맞춰 창조되었다. 석화나 사안의 능력에 대한 얘기들인 것이다.


일설로는 알렉산드로스 대왕이 바실리스크 퇴치를 경험했다는 얘기가 있다. 동방 원정 행군 도중 바실리스크가 나타나 병사들 몇이 석화되어버리자 방패를 들이대어 포위하게 한 뒤 바실리스크가 그 방패에 비친 자신의 시선에 석화가 되게 만들었다는 것이다
(아주 고전적인 내용으로, 페르세우스가 메두사를 잡을 때 써먹었던 방식이다).


사안은 제법 그 과학적(?) 근거도 갖고 있다. 독의 발산은 아무래도 두꺼운 살갗보다는 뚫린 입이나, 귀, 코 등을 통해 더 잘 빠져나오기 마련인데, 그 중 눈은 시선을 조절할 수 있다는 점에서 독의 진행과 더욱 밀접한 관련이 생긴다. 때문에 시선을 따라 독이 퍼져서 눈이 마주친 대상은 대상의 눈에 독이 퍼져서 결국 온 몸이 중독되어 죽고 만다는, 매우 황당한 얘기인 것이다. 그다지 논리적이진 못하지만 매우 강력한 능력임에는 틀림없다.


바실리스크의 몸에서 맹독의 기운이 퍼져 나오니 그 주변은 생물이 살 수 없었고 결국 바실리스크가 사는 곳 주변은 황무지, 혹은 사막이 되어버렸다(바실리스크가 사막에서 태어난다는 설과 상당히 유사점을 보인다). 혹은 아예 바실리스크의 맹독에 맞설 수 있을 만큼 생명력이 강한 지형이 조성되어야 하는데, 그것은 바로 늪지대이다. 때문에 바실리스크는 늪지와 사막, 양쪽에서 사는 것으로 인식되었다.

바실리스크의 특징이 이것 뿐만인 것은 아니다. 이들은 발톱이나 앞발의 힘도 상당하고 이빨도 날카로우며 늪지에서 사는 바실리스크들은 방어적이고 탄탄한 살갗을 갖추었고 사막에서 사는 바실리스크들은 두꺼운 피부에 발빠른 몸놀림을 갖게 되었다.


점차 발전한 바실리스크는 중세 중기 이후로는 닭과 같은 꼴이 되어버렸다. 코카트리스는 악어의 어원일 뿐만 아니라 닭이라는 말도 포함하고 있다고 생각한 것에서부터 발전했기 때문이다(사실 코카트리스는 공룡 중 랩터의 모습으로 자주 표현된다. 잘 생각해보면 닭하고 공통점도 좀 있지 않은가?). 사실 바실리스크가 현재와 같이 도마뱀으로 확립된 것은 근래에 들어와서 악어와 뱀을 합쳐놓고 이미지를 재탄생시켰기 때문이다. 심지어는, 
도마뱀이란 이유로 크게 범위를 잡아서 드래곤 범주로 분류되기도 한다



출처 : http://www.occultopedia.com





출처 : 위키 백과사전, 이 그림은 중세 시대의 유명한 괴물 그림 중 하나다.




바실리스크의 탄생에 대해서도 말들이 많다. 다만 그 많은 변화 중에서도 맹독의 특성만큼은 꼭 따라다닌다.

일설에는 따오기가 독을 가진 생물(주로 전갈이나 뱀. 이집트 지방의 얘기다)을 먹으면 그 독이 따오기의 몸 안에 축적되는데, 그 따오기가 알을 낳게 되면 그 알에서 바실리스크가 태어난다는 것이다. 때문에 따오기가 알을 낳는 시기가 되면 근처 주민들이 돌아다니며 따오기의 알을 모두 깨버렸다는 얘기도 있다(거기에서 또한 따오기의 알에 축적되어 있던 독에 중독되었다고도 한다).

또 다른 설로는 영웅 페르세우스가 메두사를 죽였을 때 사막에 떨어진 메두사의 피에서 태어났다는 것이다. 이 외에도 몇 가지 설이 더 있지만 흥미로운 내용은 아니므로 생략하자.


 

현대의 바실리스크는 굳이 분류한다면 동물계 이하의 파충류과인데도 불구하고 다리가 여섯 달린 것으로 묘사된다(많을 때는 여덟 개까지 달렸다). 머리에는 왕관과 비슷한 무늬나 벼슬이 훌륭하게 달려 있고 작은 것도 3미터에서 큰 것은 5, 6미터나 되는 초대형으로, 전체적으로 녹갈색, 혹은 적녹색을 띄고 있다(사막에 사는 바실리스크는 적녹색, 늪지에 사는 바실리스크는 녹갈색의 식이다. 벼슬은 아마도 닭 설정의 영향이 있지 않을까 한다).


ps : 귀차니즘에 쩔어 있는 제 대신 출처의 그림들을 찾아준 gmmk11 님에게 감사드립니다. 정작 찾아준 시기와 수정한 시기가 엄청나게 차이가 나는군요...

Posted by OOJJRS
,

소개

환상의 세계 2009. 10. 23. 23:53
이 카테고리에 올라오는 글들은 과거 10년 전쯤 http://user.chollian.net/~oojjrs 에 게재하였던 글들을 재가공, 편집하여

최신판(?)으로 만든 것들입니다.
(현재는 없는 주소입니다)

정보의 출처는 대개 관련 전문 서적이나 통신 시절의 신화 전문 동아리 등입니다만
 
글의 내용이 오래된 만큼 일일이 기록할 수 없어 생략하였습니다.


앞으로 올라올 글들 중 최근에 참고한 사이트나 서적 등이 있다면 표기할 예정입니다.
Posted by OOJJRS
,
아마 많은 사람이 헷갈리는 내용 중 하나는 바로 후한 시대의 관직 제도가 아닌가 한다.

본시 꽤 합리적이고 정갈하게 제정되어 내려왔으나 후한의 어지러움에 이런 저런 관직이 생겨나고 없어지고 변형되면서

제법 복잡해졌기 때문이다. 위나라 정치계의 거장이었던 상서령 진군에 의해 발의된 구품관인법을 제정하면서
 
관직의 품계를 9개로 정하여 정리하기 전까지 꽤 문란했던 것.

지금까지의 글이 그러했듯이 기약없는 2탄을 염두에 두고 꽤 흥미가 동할 법한 내용부터 일단 살펴보자.



자사, 목, 태수, 상, 승 등은 헷갈리기 좋은 소재다. 오기도 눈에 띄고 비슷한 것 같으면서도 다르다고 하니 대체 뭘까?

이해하기 위해선 앞선 이야기가 조금 필요하다.



주나라는 우리가 흔히 알고 있는 공후백자남이라는 귀족 체제를 기반으로 한 봉건제로 유지되었다.

주나라의 실질적인 힘이 중국 전 국토를 대상으로 할만큼 크지 못하였기 때문에 멀리 떨어진 곳에는 공작이나 후작을

임명하여(대개는 혈족 또는 공신에게 우선 순위가 돌아갔다) 통치권을 주는 대신 안정을 꾀하는 방법이다.

이후 진나라가 전국 통일 후 왕 위에 황제가 탄생하여 다른 귀족처럼 중간 계급으로 내려 앉았지만
 
왕은 실질적인 권한이 없고 대개 명분 뿐이었기 때문에 공작위보다 크게 높다고 하긴 어려웠다.

어쨌든 이 작위에서 백작위 이하로는 정리되었고 공신들은 대개 후侯로 임명되고, 그 위세가 군주에 못지 않은 자들만이

공公에 임명되었다. 또한 진나라는 기존 주나라의 정치 체제 대신 군현제를 실시하여 전 국토를 관료제에 맞게 정비하였다.



한나라는 통일 제국으로서의 기반이 잘 닦인 진의 체제를 거의 수정 없이 받아들였으며 문제점이 조금 있던 군현제는

약간의 수정을 하여 받아들였다. 전한 초기 군국제가 잠깐 실시되었던 것이다.

군현제가 군과 현으로 나누는 것이라면, 군국제는 군과 국으로 나누었는데, 국과 현은 두 가지 차이가 있다.

~국國이라는 이름에서 알 수 있다시피, 이들 국은 작은 나라라고 할 수 있으며 자치 및 조세권을 인정 받았다.
 
규모 자체는 1개 현과 크게 다르지 않지만 현령은 관리인 반면 이들은 왕(또는 공, 후)의 칭호를 썼고 나라의 공신 혹은 혈족이었다.

다시 말하자면 봉건제와 군현제의 병용이라고 할 수 있는데 결국 후에 강력한 황제였던 한 무제에 의해 정리된 후에는

실질적으로 군현제와 다름없어졌으나 이름뿐인 제도는 그대로 이어졌다.

조조의 고향으로 나오는 패'국' 초현, 유비의 고향인 유'주' 탁현 등으로 표현된다.
(국은 대개 공작이나 후작이 받을 경우 1개 현 정도 규모였지만 왕이 받으면 1개 군 정도를 받기도 하였다)


한나라는 바로 13개 주와 수많은 국으로 이루어졌으며 13개 주州는 다시 군郡으로 나뉘었고 이 군은 여러 개의 현縣으로 구성된 것이다.


자사는 그 중 주를 통치하는 장관이다. 우리나라 식으로 하자면 도지사와 같다.

태수는 바로 군의 통치자다. 강원도지사가 자사라면 강릉시장은 태수다.

국의 통치자가 바로 상이다. 국은 원래대로라면 자사와 같은 급이어야하나 한 무제 이후 크게 약화되어 군이나 현과
비슷하게 되었다. 우리나라 개념으로 상에 해당하는 것은 광역시장이라고 볼 수 있다.

이제 그 이하로 내려와서 현의 대표로 현령 등이 있는데, 구청장에 해당한다고 볼 수 있다. 다만 한나라에서는 현 단위로
성을 쌓았기 때문에(역시 대륙의 규모랄까) 우리나라의 구와 비슷하게 생각하는 것은 조금 오류가 있지만
작위의 상하 관계를 파악하는 비유로서는 무리가 없다.



이 외에도 여러 관리가 있었는데 1개 군의 관직만 해도 태수 밑으로 도위, 승 등이 있어 도위는 군무를 담당하고 승은
태수를 보좌하는 역할을 하였는데 손견이 거쳐갔던 하비승이나 유비가 거쳤던 하밀승 같은 경우를 이제 비교해볼 수
있을 것이다.
(유비가 평원군에 속한 평원현의 영이 되기 전에 거쳤던 하밀승 또는 고당위 작위는 하밀현의 승, 고당현의 위로
승은 정무 담당, 위는 군무 담당이며 같은 급이다)



주목은 원래 없던 관직이었으나 후한 영제 때 각지에 황건적이 일고 혼란이 일어나자 1개 군의 태수 힘으로는 군사력을

행사하기 어려워 주자사에게 군사 권한을 주어 각 태수가 지닌 군사력을 통솔하게 했는데 그것이 주목이다.

원래로는 굉장히 위험한 짓이지만 후한 말기는 그럴 수밖에 없을 정도로 혼란했고 이로 인하여 강력한 군사력을 지닌
지방 호족이나 지방 관리들이 관군의 이름으로 자신의 힘을 기르기에 용이해졌다.

기주 자사 한복으로부터 기주를 취한 원소가 기주 자사 대신 기주목으로 불리고 죽은 연주 자사 유대 대신
(한복이 기주목으로 표현되는 경우도 있어 오기인지 군사권을 받고 온 것인지 헷갈리는 경우가 발생한다)

연주를 취하고 있던 조조가 연주목 등으로 불린 것에는 군사 지휘권을 갖고 있었다는 말로 이해하면 된다.

물론, 이후에는 각 주별로 자사들이 죄다 지휘권을 가지게 되어 실질상 자사 직위는 유명무실해졌고 목으로 칭해졌다.



글의 품질을 위하여 마지막으로 도해를 첨부하고 정리해보았다.


주자사 > 태수 = 상 > 현령( = 상)

주목 = 주자사 + 군권

국은 1개 군 또는 1개 현으로 이루어짐

태수 = 군 총괄 담당
도위 = 군의 군무 담당
승 = 군의 정무 담당

영 = 현 총괄 담당
위 = 현의 군무 담당
승 = 현의 정무 담당

※ 독우나 중위, 정장 같은 설명 안한 작위가 몇 있는데 글이 길어져 일단 제외하였다.
Posted by OOJJRS
,
대개 COM 관련된 내용을 검색하면 그저 표준호출(__stdcall)을 사용하라고 써있을 뿐 그 이유에 대해서는 적혀있지 않다.
(마소의 MSDN에서도 찾아볼 수 없는 내용이라 그런지도 모른다 - 어쩌면 나만 못 찾은 것일지도)

안타까운 일이라고 생각된다.




단순하게 얘기하자면 COM 함수의 원형이 그렇게 만들어져 있으니 공부를 위해선 맞춰가는 수밖에 없지 않겠느냐고

할 수 있겠지만 맥빠지는 이야기다.

C의 기본 호출은 __cdecl이며 해당 함수를 호출하는 쪽(호출원)이 인자 크기만큼의 메모리를 회수한다(스택 이야기).

함수를 완전한 독립된 개체라기보다 하나의 강력한 반복문 정도로 취급하는 것이며 C가 탄생할 때의 배경과 어울린다.

반면 C++의 기본 호출은 여러 가지 이유에서 __stdcall이며 이는 함수 쪽에서 스스로 스택을 정리한다.

메모리 관리 면에서 보자면 독립성과 일종의 캡슐화를 갖춘 셈이고 진보된 방법임은 분명하다. 다만 이것은 메모리 측면의

이야기일 뿐이며 기법 면에서 보자면 가변 인수를 사용할 수 없는 등의 약점이 있다.



COM은 OOP의 개념으로 탄생한 기술이고 따라서 C 호출방식보다 표준호출을 선택한 것 같다.

가변 인자를 사용하지 않는다면, 미미하긴 하지만 C 호출방식보다는 표준호출이 어셈블리 코드에서 조금 더 유리하다.
(C 호출방식은 함수를 여러번 호출하면 그때마다 메모리 회수 코드를 작성해야하므로 그만큼 줄 수가 늘어난다)



COM 함수를 직접 재정의하는 경우가 아닌 이상 일반 함수들도 무조건 표준호출을 할 필요가 없음을 알리기 위해 몇 자 적어보았다.
Posted by OOJJRS
,

원래는 직접 프로그램으로 만들려다가 안 그래도 벌려둔 프로젝트에 이리저리 치이는 바람에

귀차니즘 지수가 급상승하여 괜찮은 프로그램이 없나 찾아보았다.

프로그램들은 버전과 신뢰성 문제로 여기에 직접 올리는대신 제목만 게재하였고 네이버 자료실(http://file.naver.com)에서

검색하면 쉽게 받을 수 있다.




Update Cleanup

윈도우 업데이트 시에는 레지스트리와 윈도우 폴더 내에 시스템 속성으로 설치 파일들이 남게 되는데

업데이트 후에는 사용되지 않으므로 지워주어도 되지만 함부로 손을 대기엔 껄끄러운 것도 사실이다.
(어차피 수동으로 삭제해도 레지스트리는 정리되지 않는다)

업데이트 클린업 프로그램은 이 파일들과 레지스트리를 정리해준다.

쉐어웨어지만 가끔 한 번 해주면 되는 용도이므로 라이센스는 큰 문제가 되지 않는다.

Continue 누르고 다음 화면에서 적당히 선택하고 삭제해주면 된다.

아차, "난 일어 중국어 불어 다 할 줄 아는데 영어는 몰라요!"라는 사람이 있을지도 모르는데,

대충 아무거나 찍어도 될만큼 버튼도 몇 개 없으니 겁내지 말자.

아차차, 비스타와 윈도7은 아직 지원 안된다. 참고 삼으시라.

실행 중 화면




Unlocker

이것은 사실 전용이 아니라 범용 프로그램이다. 공유 속성이나 시스템 속성이 걸려서 수정이 불가능한 대상에 대해

연결을 해제하고 사후 관리까지도 해주는 친절한 프로그램이다.

윈도우 업데이트가 정상적으로 종료되지 않거나 자체 버그에 의해 임시 폴더로 사용되었던 놈들이 사라지지 않고

계속 남아있는 경우가 있다. 귀찮을 뿐더러 놈들중에는 OS를 새로 깔아도 안 지워지는 놈들도 존재한다는 것이다.
(대개의 경우에 비추어볼 때 굉장히 독종들임에 분명하다)

위 프로그램을 설치하고 살포시 해당 폴더에서 오른버튼을 눌러 Unlocker를 선택해주자.

윈도우 업데이트 임시 폴더가 대상이라면 보통은 연결된 핸들이 없다고 나오고 사후 행동을 지정할 수 있게 해준다.

연결된 핸들이 해제되었다고 나온다면, 그냥 삭제키로 가볍게 삭제, 아니라면 사후 행동으로 삭제를 지정해주면 된다.

그래도 가끔 삭제할 수 없으니 다음 부팅 후 삭제하냐고 물어보는 독독독종들이 나타나는데,

부팅 후에 하지 말고 한놈씩 자근자근 되밟아주면 결국엔 다 삭제된다.


실행 중 화면을 캡쳐한 모습
Posted by OOJJRS
,

중국과 유럽 간 무기 체계의 발전은 차이가 존재한다. 여러 가지 원인으로 그것을 설명할 수 있겠지만

기본적인 병사 개념의 차이가 가장 큰 것으로 생각된다.
(여차저차 하지만 역시 무기의 가장 큰 소요계층은 군대인 법이다)

군인이 하나의 직업이자 시민으로 구성된 그리스-로마 계열 군대에서 그들은 주력 전투원이었으며 존중받았다.
(용병을 사용했던 카르타고 등의 지중해 주변국도 결국은 "다른 나라의 시민"을 고용한 것이다)

존중받았다는 말의 의미는 적어도 중국에서 보병이 그러했던 것처럼 머릿수 채우기나 총알받이(총은 없었지만)로

사용된 것은 아니었다는 것을 의미한다.

중국은 인구가 많았던 것도 있지만(대개 소외되는 사실이지만 한나라의 전 영토는 고대 로마의 최대 판도보다 넓었다)

병사가 징병으로 주로 이루어졌기에 농민군이 주축이었고 하나의 직업이라기보다 국방의 의무를 지는 형태였다.

때문에 유럽에서는 중세에 봉건제가 되고 기사가 주전력이 되어 중국의 그것과 비슷한 전투 방식이 태어나기 전까지는

좀 더 보편적인 무기 외에는 생산되지 않았다. 대개 유럽에서 무기의 발전이라 하면 점차 대형화되어가는 추세에
 
각종 문화 차이에 따른 특수한 무기가 존재하는 것 정도가 전부였다.


반면 중국에서 대기병 전술이 발달하기 전까지 전투는 대개 일기토전 또는 계략전이었고

일기토전 - 무술 싸움이란 완전 그들만의 잔치였기에 그들의 기호에 따라 여러 특수한 개량 무기들이 줄줄이 탄생했다.

얼마나 무기가 많았는지 이미 송 말에는 무관의 "기본" 덕목으로(과장이 심하다고 생각되지만) 17종의 무기를 완벽하게

익히는 것을 들고 있다.


한대에도 이미 무기는 굉장히 많았다. 다만 역사적으로 이야기가 계속 추가되어갔기에 당대에는 없었던 무기들이나

조상에 해당하는 무기들이 잘못 그려져 전달되는 것들이 많다(그게 아니라면 아직 고고학적 발견이 이루어지지 않은 것이리라).

예를 들면 극이란 무기는 원래 창과 비슷하나 부속 칼날이 달려 있어 보병이 기병을 상대하기 좋게 만들어진 것이다.

이것까지 들으면 여포의 그것과 크게 다를 바 없어보이지만 부속 칼날이 초승달 모양이 아니라 갈고리 모양이다.
(무슨 차이가 있냐고? 무기편 두번째 시간에 혹시 그림이 추가된다면 그때 보시라)

초승달 모양의 부속 칼날은 송대 이후에 등장했다.
 
그러나 삼국지에는 이미 여러 종류의 극 무기가 등장할 뿐 아니라 여포의 방천화극은 유명하다못해 상징물이 되었다.
 
극의 조상에 해당하는 무기가 있어 이야기가 만들어지면서 혼동이 일어났거나(아니면 송대에 만들어졌거나),

있었던 무기가 대기병 전술이 알려지기 전까지 활용되지 못했다고 생각하거나 해야할 듯 하다.

연노를 발명했다는 제갈량의 결과물에 대해서도 여러 가지 학설이 존재하는데 그것이 실제 있었는가의 여부에서부터

정말 10발씩 동시 발사가 되는가, 위력적이었는가 하는 등의 숱한 의문들을 안고 있다.

이런 삼국지 시대의 무기에 대해서 목록별로 알아볼 주제를 마련해보았다.


아무래도 그림이나 사진을 추가해야겠지만 특유의 귀차니즘으로 이번편까지 넘어갔다.

무기편 -2-에서부터는 이해를 돕기 위한 그림을 첨부하겠다(언제 쓰여질지는?).

Posted by OOJJRS
,



현재 3.1 버전은 다국어 버전(이라고 해봐야 영어 뿐이 없지만)을 준비하고 있으며

소스를 좀 더 가다듬어 소스포지를 위시한 이곳 저곳에 올려볼까 계획 중이다.
(다국어 버전은 그 일환)

다만 현재도 병렬 진행 중인 프로젝트가 있어 언제 완료가 될지는 모르겠다.



애초에 다국어 버전으로 추가할 계획을 갖고 프로그램을 만들었으나 막상 리소스 셋을

하나 더 만들 생각을 하니 잡다한 툴팁과 설명을 원체 많이 써둔지라 양이 만만찮다 :-(

네이버 블로그에서 이사온 글이다

Posted by OOJJRS
,


옵션이 추가되었다.


시간 제한 및 문제 수를 조절할 수 있고 시간 제한은 통합 제한 또는 각 문제당 제한으로 선택할 수 있다.

2.0 버전에서 사용하던 데이터는 그대로 사용할 수 있다.

사용하다보니 대단찮은 버그가 한두개 쯤 발견되었는데 다음 버전 업 때 수정될 듯 하다.


나는 이것을 영어 단어장으로뿐 아니라 한문 단어장(?)으로도 사용하고 있다. 제법 한문 테스트가 그럴듯하다.

(하지만 글자 크기가 워낙 작은 듯하여 다음 버전에서는 글자 크기가 좀 커질 듯)


업그레이드를 하긴 해야겠는데... 요즘 안 쓰다보니 잊고 지낸다.

기존 네이버 블로그에 있던 글을 옮겨왔다

Posted by OOJJRS
,