2009. 12. 10.

Remote에 있는 Lisp 구현에 접근해서 개발하기..

준비물

1. 로컬머신
Emacs + Slime

2. 원격머신
SSH , 리습구현

작업

1. 일단 Slime은 로컬 원격  버전을 맞추어서 모두 다운 받는다.
2. 원격 머신에서 다운 받은 Slime의 swank.asd를 적당한 위치(여기에서는 ~/systems/ )에 복사하거나 링크를 만들어 놓는다.
3. 원격머신에서 .sbclrc( SBCL을 쓴다고 가정)을 생성하고 내용을 다음과 같이 만든다.
(require :asdf)
(push "/path/to/~systems/" asdf:*central-registry*)
4. 이제 SBCL에서 다음과 같이 실행시킨다.
(require :swank)
(swank:create-server :port 4005)
5. ssh를 이용 리모트 머신과 로컬 머신의 포트를 서로 맞춘다.
ssh -L4005:127.0.0.1:4005 username@host
6. 클라이언트 머신에서 emacs의 .emacs파일에서 slime을 로딩한다.
(require 'slime)
7. 이제 M-x slime-connect를 해서 서버는 127.0.0.1 로하고 포트를 4005로 맞추면 이제부터 리모트 머신의 swank를 통해 lisp evaluation을 할 수 있다.

8. 추가로 로컬에서 lisp사용시 항상 해당 서버쪽에 맞게끔 이름을 넣고 싶다면 다음과 같은 내용을 .emacs에 넣는다.

;; remote <-> local translate
(setf slime-translate-to-lisp-filename-function
      (lambda (file-name)
        (subseq file-name (length "/ssh:siabard@192.168.10.42:")))
      slime-translate-from-lisp-filename-function
      (lambda (file-name)
        (concat "/ssh:siabard@192.168.10.42:" file-name)))



2009. 12. 2.

Clojure 사용소감

지극히 주관적인 소감입니다.

PAIP(Paradigms of Artificial Intelligence Programming)를 공부하는 중인데 On Lisp이랑 Practical Common Lisp을 clojure 로 공부하는 사람들의 기사를 보고, 하는 김에 리습이랑 클로져를 같이 해보자 하고 시작했습니다.

이 책의 모든 소스코드는 Common Lisp 기준이라서 그래도 같은 계열인데 쉽겠지하고 붙었다가 어려움을 겪고 있습니다.
아직은 극 초반이기는 한데.. Common Lisp에서 익숙한 개념을 Clojure에서는 다르게 봐야하는 부분이 조금씩 있습니다.

1. list보다는 vector
일단 함수의 인자부터 Common lisp은 list 인데 비해, clojure는 vector라는 개념을 사용합니다.
이런 이유로 몇몇 special form의 규칙도 살짝 바뀐게 있습니다.
cond, let이 대표적인데 익숙해지기전에는 혼동을 일으키기 쉽습니다.

2. assoc 대신 set
리습코드를 많이 보면 리스트에서 특정 키값으로 찾는 assoc이 있습니다. 이런 방식 대신 clojure에서는 set이라는 데이터구조를 직접다루게 합니다. 그러다보니 구현상에서 ((..) (..) .. ) 식으로 구현했던 많은 부분을 clojure에서는 { key val ... } 식으로 풀어쓰는 경우가 많게 됩니다. 대신 rassoc 를 대신할 만한 것을 찾기가 어렵습니다.

3. 데이터 값을 함부로 바꾸기 어렵게 되었습니다.
기본적으로 clojure 함수는 java의 Runnable 인터페이스를 구현했고, 각 데이터는 이런 스레드상에서 변경이 된다고 보는 것 같습니다. 그렇기때문에 어느 정도 synchronized 같은 방법을 동원하는 것 같습니다.  dosync 같은 것을 사용해서 값을 변경할 수는 있습니다만.. 어떤 데이터 형인지에 따라서 그 값을 바꿀 수 있는 방법도 다릅니다. ref, atom, agent같은 개념을 사용해서 스레드에 안전한 데이터를 사용하도록 합니다.

4. 재귀가 그리 만만하지 않습니다.
기본적으로 재귀호출은 같습니다만 lazy evaluation이나 tail call optimization같은 것을 사용하려면 여러 면에서 신경써야합니다. 사실 Common Lisp은 tail call optimization이 그닥 많이 구현되지도 않았고, lazy evaluation같은 것은 기본 내용에 포함되지 않는 경우가 많았는데.. clojure는 lazy evalution을 가급적 잘 활용하도록 강조하고 있고, tail call optimization을 지원하기위해 recur나 trampoline 같은 독특한 구조도 지원합니다. (사실 JVM이 tail call optimization을 지원안하는게 가장 큰 이유겠지요)

이런 식입니다. 앞으로 조금씩 더 파나가다보면 더 많은 것이 나오겠지요.. 어쩌면 어색했던 것이 더 편할지도 모르겠습니다.
아무튼 재미있는 Lisp구현임에는 틀림없는 것 같네요..

--
새로움을 느끼기에 삶은 즐겁다..
모험가 아돌 크리스틴을 꿈꾸며..
Sia..



Clojure 의외로 불편하다..

일단 예전에는 List 하나로만 확인했던 작업이 vector, seq, set 으로 나눠지다보니 상당히 혼란스럽다. =.=
특히나 append 같이 명령어가 바뀐 것은 정말 헤메기 딱좋다. (concat 으로 바뀌었다.)

cond, let의 문법 변화는 좋은지 나쁜지는 모르겠지만... 데이터/코드적 순결성에서는 안좋은 느낌.. 나름 vector 로 처리했다고 자부하는 것 같긴한데.. 글쎄??

PAIP 1장 시작이 어느 정도 진행되기는 하니까 계속 지켜볼 생각임..


2009. 11. 23.

Tomcat 웹어플리케이션의 구조

톰캣으로 개발을 진행하려할 때 가장 처음에 막히는 부분은 웹어플리케이션의 구조이다. 일반적으로 파일을 올려서 동작시키기만하면 되던 PHP와는 달리, 자바 서블릿&JSP에서는 웹어플리케이션이라는 하나의 단위로서 해당하는 파일들의 관리를 해줘야한다.

일반적으로 Tomcat에 웹어플리케이션을 개발할 때에는 서블릿 컨테이너의 SERVER_ROOT에서 생성시켜야한다. Tomcat이 /usr/local/apache-tomcat 에 설치되어있다면 SERVER_ROOT는 /usr/local/apache-tomcat/webapps 가 된다.

해당하는 루트에 웹어플리케이션을 개발하자. 이름을 firstapp 이라고 한다면 다음과 같은 디렉토리를 가져야한다.
  • /firstapp : 웹어플리케이션의 루트 디렉토리. JSP나 HTML을 보관한다.
  • /firstapp/WEB-INF : 웹어플리케이션 루트에 포함되지않을 모든 애플리케이션 및 자원이 포함된다. 또한 배치지시자(DD)가 저장되는 곳이다.
  • /firstapp/WEB-INF/classes: 서블릿 및 기타 유틸리티 클래스를 넣어두는 곳
  • /firstapp/WEB-INF/lib: 의존하는 각종 자바 압축파일을 넣어둔다. 예를 들면 JDBC 드라이버등을 들 수 있다.


Tomcat 웹어플리케이션의 구조

톰캣으로 개발을 진행하려할 때 가장 처음에 막히는 부분은 웹어플리케이션의 구조이다. 일반적으로 파일을 올려서 동작시키기만하면 되던 PHP와는 달리, 자바 서블릿&JSP에서는 웹어플리케이션이라는 하나의 단위로서 해당하는 파일들의 관리를 해줘야한다.

일반적으로 Tomcat에 웹어플리케이션을 개발할 때에는 서블릿 컨테이너의 SERVER_ROOT에서 생성시켜야한다. Tomcat이 /usr/local/apache-tomcat 에 설치되어있다면 SERVER_ROOT는 /usr/local/apache-tomcat/webapps 가 된다.

해당하는 루트에 웹어플리케이션을 개발하자. 이름을 firstapp 이라고 한다면 다음과 같은 디렉토리를 가져야한다.
  • /firstapp : 웹어플리케이션의 루트 디렉토리. JSP나 HTML을 보관한다.
  • /firstapp/WEB-INF : 웹어플리케이션 루트에 포함되지않을 모든 애플리케이션 및 자원이 포함된다. 또한 배치지시자(DD)가 저장되는 곳이다.
  • /firstapp/WEB-INF/classes: 서블릿 및 기타 유틸리티 클래스를 넣어두는 곳
  • /firstapp/WEB-INF/lib: 의존하는 각종 자바 압축파일을 넣어둔다. 예를 들면 JDBC 드라이버등을 들 수 있다.


Mercurial 을 이용한 소스 관리

필요한 것
  1. SSH를 지원하는 Mercurial 서버
  2. Mercurial Client 패키지가 설치된 클라이언트
먼저 서버쪽에 repository를 설치할 필요가 있다. 해당 디렉토리에 들어가 다음과 같이 명령을 내린다.
hg init
다음에 클라이언트에서는 해당 서버에 clone을 행한다.
hg clone ssh://username@server/home/hg/repo/ [받을디렉토리]

이제 클라이언트에서는 파일을 수정하면 된다. 다음으로 수정후에 commit을 한다.

hg commit -Am "커밋로그설명"

그리고 해당 파일을 push한다.

hg push

그렇게되면 해당SSH계정의 암호를 입력하게되면 된다.

마지막으로 update하면 끝

hg update









Google Calendar의 국가별 세팅에 따라 하는 일이 다른 듯..

한국버전으로 하니까 사라진 Tasks가.. 영어버전으로 하니 떡하니 나온다.
각국마다 따로 시스템을 관리하는 듯한데 처음해볼때 엄청 당황했다.

사실 국가에 따라서 이런 모듈이 들락 날락 한다는 것 자체가 좀 의아스럽다.
가면 갈수록 클라우드 시스템에 대해 별로라는 생각만 드는 것은 왜그럴까..

2009. 11. 21.

집에서 일하는 데 필요한 제언..

  1. 일하는 공간과 생활하는 공간은 분리한다. 어떠한 경우라도 공간을 분리해야한다. 정 안되면 파티션이라도 쳐야한다. 사람은 공간에 영향을 심각할 정도로 많이 받기 때문에 약간의 분리만으로도 큰 효과를 얻을 수 있다.
  2. 문을 닫아라. 일단 일을 한다면 외부와의 접촉을 모두 끊어버리는 것이 중요하다.
  3. 규칙적으로 일하라. 집에서 일하는 데에도 사무실에서 일하는 것과 같은 규칙을 사용해야한다.
  4. 먹는 데에 소홀해지지 마라. 집에서 일하는 사람은 먹는 시간을 상당히 소홀히 한다. 먹는 시간은 규칙적으로 하고, 필요한 간식도 챙겨 먹도록 한다. 그래야만 일의 맥을 끊는 것을 막을 수 있다.





Fixed-Schedule Productivity

원문에서 나온  Fixed-Schedule Productivity의 요점은

  1. 일과 휴식의 균형이 이상적이라고 생각되는 근무시간의 스케쥴을 정하고
  2. 이 스케쥴을 깰 수 있는 어떤 상황이든 거부한다
라는 것이다. 1을 충족한다는 것은 매우 쉽지만, 2를 충족하는 것은 그렇게 쉽지않다. 수많은 일과 엮인 상황에서 무조건 거부하기란 매우 어려운 일이다. 간단한 사실이 있다면 : 이상적인 스케쥴을 유지하려면 약간의 극단적인 행동도 필요하다는 점이다.
따라서 다음의 상황이 벌어질 수 있다.
  • 작업중인 많은 프로젝트를 과감히 쳐낸다.
  • 일일 스케쥴에서 불필요한 습관을 없앤다
  • 자유시간을 얻는 대가로 다른 사람을 자극할 수 있다.
  • 미루는 습관을 없앤다.
이런 상황을 위해서 원저자는 다음과 같은 작업을 했다고 한다.
  • 작업을 직렬화한다. : 프로젝트 큐를 만들어서, 개별 큐의 최상단에 위치한 프로젝트만을 수행하도록 한다. 이것이 끝나야 다음 작업으로 넘어갈 수 있도록하여, 집중도를 높인다.
  • 결과를 내야할 시점이 언제쯤인지 명확하게한다: 누군가가 큐에 할일을 넣을 때, 이를 평가해서 언제쯤 수행해야할지를 정한다. 이에 대해서 의사소통을 한다.
  • 프로젝트 수행중에 큐가 너무 복잡해져서 특정 프로젝트를 제시간에 마칠 수 없을 것 같다면, 거절한다.
  • 프로젝트가 제어범위를 벗어나서, 스케쥴의 시간을 지나치게 잡아먹기 시작한다면, 프로젝트를 버린다. 다른 더 중요한 일이 다가오고, 큐에 있는 어떤 일과 충돌하게된다면, 좀 덜 중요한 프로젝트를 버린다. 예상시간이 너무 길다면 중지한다. 어느 누구도 당신이 작은 부분에서 무엇을 하는지 궁금해하지 않는 다는 점이 중요하다. 최종적으로, 중요한 완료리스트를 가지고 스스로 판단해야한다.
  • 숨어라. 종종 아무도 보이지 않는 곳에서 일하고는 하는데, email같은 수단이 아니고서는 접촉할 수 없게한다. 하지만 굳이 모든 일에 즉각적으로 대응할 필요는 없다. 중요한 것은 내가 일을 완료하는 것ㅇ이다.
  • 순차작업과 습관화하기. 어떤한 규칙적인 작업이 있다는 이를 그냥 습관화한다. 일요일 아침에 블로그를 쓴다던가, 세미나 자료를 금요일이나 월요일 오전에 읽는 등이다. 습관화된 스케쥴은 비정규적인 프로젝트에 착수하기 쉽게한다. 또한 스케쥴이 해야할 일로 넘치는 것을 방지한다.
  • 일찍 시작한다. 어떤 일을 실제보다 2,3주 먼저 시작할 필요가 있다면 그렇게한다.
모든 일은 그 끝을 알 수 없기때문에, 스케쥴을 고정시키는 것이 중요하다. 그리고 이에 맞추어 모든 요청을 정리해야한다. 유연해지고, 효율적이 되어야한다. 만약 진행이 안된다면, 작업을 변경시켜야한다. 절대, 타협하지않아야한다. 당신 자신이외에는 어느 누구도 당신의 스케쥴에 신경을 쓰지 않는다.

2009. 11. 20.

Paul Graham 의 앱스토어 비평

Paul Graham 이 앱스토어에 대해 비판하다.

Paul Graham은 앱스토어를 소프트웨어의 iTunes로 보고 이를 비판하고 있다. 완성된 제품을 시장에 공급하는 음악이나 도서 산업과는 달리 소프트웨어는 지속적인 개발 사이클(iterate)을 가질 수 밖에 없다는 점을 들어, Apple의 이같은 시도가 개발자들에게 중대한 벽으로 작용한다고 보고 있다.

실제로 사용자들이 보고하는 버그를 수정해서 올려야되는 개발자 입장에서는 새로운 버전을 올릴 때마다 다시 심사(Approval)과정을 거칠 수 밖에 없는 것은 90년대 이전의 소프트웨어 시장을 생각케한다. 당시에 많은 패키지들은 한번 나오면 좀처럼 수정이 어려웠기때문에 제작에 상당한 공을 들여야했었지만, 그럼에도 불구하고 수많은 에러를 만들어 사용자를 당황시킨 적이 한두번이 아니었다.

결국 통신망을 통해 지속적으로 패치를 공급하거나 새로운 버전을 빠르게 공급할 수 있었던 업체들만이 살아남을 수 있었다. 문제는 앱스토어의 방식이 이를 근본적으로 금지시킨다는 점이다.

우후죽순격으로 생기고 있는 앱스토어를 보고 있자면, 소프트웨어의 근본적인 특성을 이해하고, 이를 배려하려는 노력이 턱없이 부족하게 느껴진다. 특히나, 날마다 새로운 기능을 요구하고, 변화를 추구해야하는 지금에 있어서, 프로그램 유통자체를 누군가에게 의존해야만하는 모델은 초기에는 제법 괜찮은 것 같겠지만, 나중으로 갈수록 정책이 개발자의 목을 조르는 일이 벌어지게된다.
이렇게 된다면 개발 동력은 저하될 것은 불보듯 뻔한 일이 될 것이며, 언젠가는 구닥다리 버전만 돌아다니는 고물상이 될지도 모른다.

보다 개방적이고, 덜 권위적이며, 프로그래머 친화적인 정책만이 앱스토어를 더억 강화시키는데 일조할 수 있을 것이라고 생각한다.

2009. 11. 16.

gifimg.php를 이용한 사이트 해킹

요 며칠간 일부 자바 스크립트가 먹통이 되거나 사이트가 정상적으로 열리지 않는 상황이 발생했다.,
웹쉘, 각종 악성코드를 검사하던 중 이미지를 올려놓는 디렉토리에 gifimg.php라는 정체를 알 수 없는 파일을 발견했다.
해당 파일은
eval(base64_decode(...));
와 같이 한줄만 걸려있으며 인코딩된 문자열을 가지고 있다.
이 코드는 일종의 웹쉘역할을 하지만 실제 공격코드를 가지고 있지는 않기 때문에 몇몇 검색툴에서 정상적으로 잡아내지를 못하고 있따다.

해당 파일은 FTP를 통해서 올려지기 때문에, 보유중인 ftp계정의 암호등을 리셋하는 과정에 더해서, ftp 디렉토리에 걸려있는 모든 드라이브를 검사해보도록 한다.

증상으로는 자바스크립트(*.js)나 PHP, ASP파일등에 특정 URL의 자바스크립트를 호출하는 문장이 끼어든다면 이 문제라고 볼 수 있다.
ferozkhan.in / madam / band_vid.php
art4ukorea.com / data / phpinfo.php
visorg.ru / news / style5.php
cmcludhiana.in / event_image / cmc_ludhiana_hospital.php
psusheela.org / php / index_new_template.php
ecaeda.com / language / Checklist.php

해당 URL에서는 악성코드나 스팸메일을 발송하도록 하는 코드를 수행시켜 자기도 모르는 새 스팸메일의 발송처로 악용될 수 있다.
암호화가 부실한 FTP 사이트를 통해 공격이 이루어지기때문에 웹로그만을 감시하게되면 놓치게 되는 부분이니 유의해야한다.


2009. 10. 19.

성공적인 기업을 만드는 구결

다음의 세가지 범주에 해당하는 일을 모두 해야한다.

  1. 세상을 변화시킨다
  2. 고객의 생활을 향상시킨다
  3. 조직에 지속적인 가치를 만들어 낸다.
새로운 기업의 창조는 이 세가지 가치를 모두 충족시킬 수 있어야한다.

회사를 만든다는 것은 새로운 상품을 세상에 내놓는 것 이상의 일을 의미한다.
상품광고, 보안유지, 봉급과 현재 회사의 재정을 유지해야하는 일 등등, 관리라는 측면을 생각한 장기간의 계획이 짜여져있어야한다.
주변의 인물들을 조직화하고 이들을 운영하기 시작해본다면, 실제로 하나의 조직을 운영한다는 것이 상상외로 어렵다는 것을 느끼게 될 것이다.

2009. 10. 7.

Apapche CGI로 동작하는 SBCL 스크립트의 문제점.


#!/usr/local/bin/sbcl --script

(format t "Content-type:text/html; charset=UTF-8~%")
(format t "~%")
(format t "~A~%" (stream-external-format *standard-output*))

Above cgi script returns which charset is on the script running. I hope it'll return UTF-8 but I got ASCII.
Well.. may be some problem is there with Apache CGI.

분명히 위의 스크립트를 실행시키면 UTF-8이 나와줄거라고 믿었지만 결론은 ASCII가 나왔다.
Apache CGI에서 기본 실행시 ASCII로 돌리는 것 같은데.. 환경변수를 UTF-8로 변환할 방법을 모르겠다.
아니면 unicode를 출력할 적절한 방법을 찾아야할텐데 좀 갑갑하다..

이 문제는 apache의 mod_env를 사용해서 풀게되었다.
mod_env는 환경변수를 지정해서 이를 CGI등으로 보낼 수 있도록 하는 모듈이다.
LC_CTYPE을 UTF-8로 지정하는 방법을 사용해서 SBCL이 유니코드로 동작하게 만들었다.

방법은


  1. /etc/apache2/mods-enabled 에 /etc/apache2/mods-available/env.load 의 심볼릭링크를 만들고

  2. /etc/apache2/mods-available/env.conf를 다음과 같이 만든다.

    SetEnv LC_CTYPE en_US.UTF-8<br />SetEnv LANG en_US.UTF-8<br /><br /><br />


  3. /etc/apache2/mods-enabled 에 /etc/apache2/mods-available/env.conf 의 심볼링링크를 만든다.
  4. apache2를 재시작한다.
이제 깔끔하게 출력된다. 그동안의 삽질 이제 끝~~

2009. 10. 5.

열심히 일하지 말고, 영리하게 일하라.

Work Less, Get More Done: Analytics For Maximizing Productivity에서 글쓴이는 더 긴시간을 할애해서 일하는 것 자체가 경쟁력을 만들어내지는 못한다고 말하고 있다. 경쟁업체가 X시간만큼 일한다고, 당신이 X+1시간만큼 일하는 것을 대항전략으로 내세우면, 경쟁사(혹은 또다른 경쟁사)는 X+2 시간만큼 일하면 순식간에 생산성이 역전되기 때문이다.
이런 방식은 전략적으로 상호간의 시간을 빼는 시합이 되기 쉽기때문에, 효과적인 답이 되기는 어렵다. 더우기, 상대하는 회사가 이미 기틀을 가지고 있는 회사라면, 상대방은 막대한 인력과 투입가능한 재원을 가지고 있기때문에, 오랜 시간 일하는 것은 대안이 되기 어렵다.

고전적인 회사라는 개념에서, 관리자와 개별 근로자는 일하는 행태를 눈으로 직접 볼 수 있기때문에, 이들이 측정하는 생산성은 얼마나 오랜동안 일하고 있느냐로 구분되었다. 하지만 이런 방식으로 실제 생산성을 측정하는 것은 불가능에 가깝다.

바로 측정의 방식이 정확해야만 실제적인 생산성을 향상 시킬 수 있다.

필자는 여기서 가져온 것이, 가상-임금(Pseudo-Wage)이라는 개념이었다.
예를 들어보자. CD를 우편으로 부치는 것은 얼마나 가치있는 일일까? 많은 소프트웨하우스가 자신의 소프트웨어를 CD로 배송한다. CD를 만들어내는 작업은, CD자체의 가치와, 추가적인 가치를 총합해내면 된다.
매 4장당 30$가 들어가며, 한장당 5$의 기본가치가 있다고 가정해보면, 한장당 가치는 대략 12.5달러가 된다. 한시간에 4장정도를 얻을 수 있다면 시간장 50~60달러의 가치를 만들어낸다.

말하자면, 이때 드는 시간을 좀더 가치있는 일에 전용한다면, 기존보다 더 많은 수익을 얻을 수 있다.

기법 1: 아웃소싱
가치를 만들어내지 못하는 일이라면 외부에 위탁하는 것을 고려하라. 물론 비용에 대해서는 항상 생각해야한다.

기법 2: 자동화
제품을 만들어내는 공정이야말로 가장 중요한 자산이다. 그러므로 해당하는 공정이 최대한의 효율성이 나도록 끊임없이 혁신한다.

기법 3: 비효율적인 시간을 제거하라.


2009. 9. 23.

언어에 대한 논쟁은 종교논쟁만큼이나 쓰잘데기 없다

언어에 관한 논쟁은 예나 지금이나 피곤합니다.

이 제까지.. 거의 예외없이 모든 경우에 특정 언어에 대한 호불호가 글에 적시되면, 뒤이어 나오는 것은 유서깊은(?) 언어논쟁이 나온다. 비판의 초점이 무엇이든간에 '그러면 그 언어가 틀려먹었다는 거냐?' 며 치열하게 물고 뜯는 냉혹한 말싸움이 예외없이 나온다.

원글뿐 아니라 원글이 트랙백하고 있는 글에서 딱히 C++에 대한 공격을 했다고 보기는 어렵다. 글쓴이는 단순히 C++이 가지는 표현상의 한계를 극복하려는 차원에서 STL을 이야기한 것 같은데, 댓글에서는 C++을 공격한 것처럼 이야기하는 것을 보니.. 물론 표현이 과격할 수는 있지만, 전체적인 문맥을 읽게되면 나름 이해가 되는 수사적인 표현이었을 뿐이라고 생각했는데 말이다.

한 언어를 충분히 익힌다는 것은 상당한 시간과 돈이 필요한 일이다. 또한 어떤 문제를 해결하는데 있어서 필요한 문제해석방식이 형성되기때문에, 언어로 인한 일종의 '틀'이 생길 수 밖에 없다. 그러다보니, 자신이 사용하는 언어에 대한 비판에 있어 아주 예민해지는가 보다. 즉 자신이 가진 가치관에 대한 도전으로 인식하는 듯 하다.

그 러다보니 반론이라는 글의 대부분은 전체적인 주제를 꿰뚫은 토론이라기보다는 지엽적인 문제만을 가지고 집요하게 말꼬리를 무는 글에 가깝게 나온다. 수사적인 표현을 들고 나와서 그에 대한 감정적인 글을 쓰는 것을 보면, 종교에 대한 논쟁을 보는 것 같다.

어 떤 한가지 지엽적인 주장은 항상 옳다. 하지만 전체 문맥에서 그 주장이 항상 옳기는 쉽지않다. 대부분의 경우, 특정 이슈에 대한 문제제기는 그것이 '옳다', '그르다'로 판단되기보다는 '이렇게 된다'와 '저렇게도 된다'라는 결론으로 귀결되는 경향이 더 많다. 특히나 전산문야에서는 상당히 많은 문제들이 수많은 방법으로 해결될 수 있기 때문에, 한 곳의 '좋지 않다'라는 비판이 꼭 '틀리다' 라는 흑백논리로 귀결되지는 않는다. 말하자면 컴퓨터 언어는 과학이라기보다는 일종의 공학이고, 어떤 기술에 가깝기 때문에, 어떤 진리인양 절대성을 가지고 따질 문제가 될 수 없다고 본다.

내가 가진 '정의'가 꼭 다른 사람에게도 '정의'가 될 필요는 없다. '정의'의 반대말은 '불의'보다는 '또다른 정의'에 더 가깝다.
소모적인 논쟁을 일으키는 대신에 다른 사람의 의견을 자신의 자양분으로 만드려는 노력을 하는 것이 더 이익이라는 생각을 해본다.

2009. 9. 22.

블로그 이사중이다.

이글루스 글 이사중이다.
그리 많지 않을거라고 생각했는데 5년동안 올린 글이 300개정도 된다. 대부분은 SICP문제풀면서 올린 문제풀이였지만, 글을 정리하다보니 참 여러 생각이 든다.

이런 글을 썼나 싶기도 하고, 내가 봐도 다른 사람이 쓴 글 같기도 하고.. 여러 가지로 정이든 장소를 떠나려고하니 조금 센치해지기도 하고..

예전에는 Naver 블로그를 썼었다. 아마 거기글도 끌어와야겠지만, 여기 처럼 많은 글은 아니라서 묵묵히 올리는 일이 벅차지만은 않을듯 싶다. 성격이 베베꼬인탓인지는 모르겠지만 한국 대형 포탈이 보이는 모습에 진저리가 나서 게중에 가장 리버럴해보이는 이글루스로 망명해서는 5년을 지내고, SK에 통합되고 나서 변하는 모습을 보면서 다시 망명을 한다.

근데.. 역시나 이사를 하는 일이 만만치않다.
제대로 된 백업툴이 없다보니 글을 긁어다가 일일이 붙이고, 날짜를 수정해가면서 올리는데, 새로운 사이트는 하루에 글을 너무 올리면 일일이 보안문자까지 확인하니 올리다보면 하세월이다.

그래도 꾸역꾸역 글을 넣다보니 어느새 끝을 바라본다. 참 힘들다..
소소히 글이나 올리며 노는 나같은 사람도 이런데 메이저 블로그들은 장소 한번 바꾸려면 얼마나 힘들런지...

예전에 블로그 이사관련한 사이트도 있었는데 지금은 문을 닫아 이용하지못해 하나하나 글을 올린다는 것이 참 우습기만하다. 왠지 IT 세상에 살고 있으면서, 하는 일은 책을 필사하는 중세시대처럼 한다고나 할까..

프로그램 만들어서 돌린다면 안될것도 없지만... 옛글도 한번 찬찬히 보고 싶고, 그러면서 뭔가 반성도해보고 있어서 좋은 시간이었다고 자위해본다.

보낸때는 좀 쿨하게 보내줄 수 없나? 내가 올렸던 글.. 그런 글을 내가 다시 어디에나 올릴 수 있도록 백업하나 받을 수 없는 답답한 닫힌 공간에는 절로 짜증이 난다. 나가기 쉬운만큼 들어오기도 쉬울 거라는 생각은 안해보는지...

새로 이사가는 곳에는 떡하니 블로그 백업, 복원 기능이 아예 자리까지 잡고 있는걸보니 아쉬웠던 생각이 후련함으로 바뀌기까지한다.

2009. 9. 18.

Ex 3.1

let* 문을 lambda 문으로 바꾸는 문제

((lambda (x)
   ( +
     ((lambda (y) (* y y)) x)
     x))
 6)
     
처럼 만들면 된다.
x 의 수식을 계산해서 y값을 뽑아내기때문에 lambda 안에 새로운 lambda가 들어가면 됨..



2009. 8. 20.

첫번째 게시물..

이제 이글루스 게시물을 가져올 것임..

2009. 8. 4.

환영받지 못할 기업의 자세

이윤추구체인 기업을 호불호의 대상으로 삼는다는 점이 다소 껄끄럽기는 하지만, 세상에는 이미 그런 기업들이 존재한다. 이런 감정은 기업이 가지는 많은 이미지를 통해 생성되고 강화되는데, 이러한 이미지에 대한 판단은 개개인이 가진 가치관에 따라 존재할 것이다.

요 즘 IT쪽에서 많이 회자되는 기업이 Apple 이다. 감성에 호소하는 제품들, 새로운 라이프 사이클을 구성하는 탁월함, 사용하는 것만으로 Cool 하다고 느낄 수 있는 그런 만족감을 주는 회사였는데, 요즘 들어서 일하는 모습을 보면, 태생적으로 기업이 가질 수 밖에 없는 뻔뻔함이랄까, 그런 점을 느낀다.

영국에서 한 소녀가 구입한 iPod 이 과열로 보이는 폭발(explode)로 인해 망가졌는데, 이 가족에게 배상을 하는 조건으로 이 일에 대해 외부에 알릴 경우 법적인 조치가 취해질 수 있다는 문건에 서명해줄 것을 권유했다고 한다. The Times 지 보도

국 내에서도 몇몇 Apple 제품이 과열로 보이는 문제점을 보이면서, 리콜관련 실랑이 소식이 들려왔었다. Apple 뿐 아니라 각종 소비자관련 사이트에 들어가면 기업에서 제품에 대한 하자에 대한 보상을 주고, 쉬쉬하고 덮으려는 모습을 너무 쉽게 본다.

인 터넷을 통해 수많은 의견이 생산, 재생산되는 요즈음에 이러한 기업의 자세는, 그동안 쌓아놓았던 호감을 서서히 갉아먹는 나쁜 요소에 불과하게 된다. 보다 적극적인 대응과 솔직한 사과가 회사의 이미지를 지키고, 소비자에게 안심을 주게 하는 등, 선순환적인 요소로 기업에 이득이 된다는 점은 많은 사례를 통해 알려진 바 있다.

하지만, 이런 식으로 무마하려는 노력은, 자칫 잘못하면 더 큰 악영향을 순식간에 불러일으키고 나아가 제품뿐 아니라 기업자체에 심각한 이미지 손상으로 닥쳐올 수 있다는 점을 기업이 인식했으면 하는 소망이다.

2009. 7. 20.

내 리눅스 박스가 자꾸 지저분해지는 이유..

세상 만사 조용하게 살면 좋겠지만 내 리눅스 박스는 전혀 그렇지못하다.

어제도 OpenGL 테스트를 하다가 문제없어야할 프로그램이 뻗어대고 있는 것이었다.

데비안 트리에 들어가서 소스까지 컴파일해다가 올려봤는데 결국은 실패했다.

마침내 얻은 해결책은 CLISP 업그레이드.. 데비안 5.0에 포스팅 된 버전은 2.44.1 인데 현재는 2.47.x..

새로 컴파일해서 돌려보니 정상적으로 운영되기 시작했다.

내 시스템이 잘 정리된 패키지만으로 운영되기를 바라지만 절대 그것은 불가능..

소스를 받아다가 컴파일해서 올려야하다보니 프로그램들은 여기 저기 흩어지기시작하고, 나중에 패키지가 업그레이드어서 새버전이라고 깔릴참이면 기존 버전 지우랴.. 의존성 걸린 패키지들 다시 조정해주랴 정신이 산만해져야한다.

이런 상황이라면 결국 시스템 포맷후 재설치...

패키지만으로도 잘 운영되는 시스템은 역시 꿈일라나... 패키지 관리자가 부지런했으면 하는 소망이지만 봉사자들에게 그런것까지 요구하기는 좀 그렇지.. 아마..

2009. 6. 3.

HTML5 의 Web 게임 가능성..

Google Chrome 이후로 자바스크립트에 많은 관심이 간다.
그중에 하나가 브라우저 상에서의 애니메이션을 제어하는 기술이다.
HTML 5 상에서의 CANVAS로 수행하는 상당히 많은 예제를 보고 있자면 실제로 사용이 가능한 경우가 상당히 많을 것으로 생각한다.
오늘자 Slashdot에서는 플래시 없이 동영상을 VIDEO태그로 실행하는 사이트가 올라왔다. Firefox 3.5 에서만 볼 수 있는 것이 아쉽지만, 앞으로 HTML 5 가 플러그인 시장에 얼마나 엄청난 타격을 입힐지는 기대가 된다.

말하자면 컴퓨터쪽에서의 강력한 연산기능대신 비디오/오디오 등에 특화된 CPU가 있다면, 서버쪽에서 대부분의 연산을 담당하고 클라이언트에서는 웹브라우저가 실제 플랫폼을 대신하는 상황도 생각할 수 있다.

Erlang , LISP으로 만든 게임에서 플레이어 DB데이터와 게임액션을 Erlang이 담당하고, 게임상의 상태처리, 길찾기, API등을 Lisp으로 처리하는 방식은 상당히 좋은 Role Model이 될 수 있을듯하다.

ARM등의 비디오/오디오 강화 CPU에 실연산처리는 클라우드를 포함하는 대규모 서버군이 처리하고, 광대역 처리만 제대로 된다면 기존의 플러그인없이도 HTML 5 만으로도 훌륭한 게임이 나올 수 있을 듯하다.



관련링크


2009. 6. 1.

LISP으로 어드벤처 게임을... Ch 12

이제 각 방별로 해당 방에 들어갔을때 설명을 출력하도록 한다.

;;;; 게임의 Castle 구성하기..
;;; north south east west up down mobs
(defvar *castle*
  '((0 2 0 0 0 0 0 ("You are in the hall way." "There is a door in south." "Through window to the north you can see secret herb garden"))
    (1 3 3 3 0 0 0 ("This is the audience chamber" "There is a window to the south. By looking to the right" "Through it you can see the entrance of the castle" "Doors leave this room to the north, east, and south" ))
    (2 0 5 2 0 0 0 ("You are in great hall, all-shaped room" "There is doors to the east and to the north" "In the alcove is a door to the west"))
    (0 5 0 0 0 0 0 ("This is the monarch's private meeting room" "There is a single exit to the south"))
    (4 0 0 3 15 13 0 ("This inner hallway contains a door to the north." "And one to the west, and a circular stairwell" "passes though the room" "You can see ornamental lake through here"))
    (0 0 1 0 0 0 0 ("You are at the entrance to a fobidding-looking" "stone castle. You are facing east"))
    (0 8 0 0 0 0 0 ("This is castle's kitchen. Though windows in" "The north wall you can see secret herb garden." "A door leave the kitchen to the south" ))
    (7 10 0 0 0 0 0 ("You are in store room admist spices" "vegatables, and vast sacks of floud and" "Other provisions. There is a door to the north" "and one to the south"))
    (0 19 0 8 0 8 0 ("You are slowly descends."))
    (8 0 11 0 0 0 0 ("You are in the real vestibule." "There are windows to the south from which" "you can see ornamental lake" "There is an exit to the east and " "one to north"))
    (0 0 10 0 0 0 0 () )
    (0 0 0 13 0 0 0 ("you are in dank dark dungeon" "There is single exit, a small hole in" "wall towards to east"))
    (0 0 12 0 5 0 0 ("You are in the prison guardroom in the " "basement of the castle. The staiwell" "ends in the room. There is one other" "exit, a small hole in the east wall") )
    (0 15 17 0 0 0 0 ("You are in the master bedroom on the upper" "level of the castle..." "Looking down from the window to the west, you" "can see the entrance to the castle, while the" "secret herb garden is visible below to the north" "window. There are doors to the east and" "to the south"))
    (14 0 0 0 0 5 0 ("This is L-shaped upper hallway." "To the north is a door, and there is a strairwell in the hall as well. You can see" "the lake through the south windows."))
    (17 0 19 0 0 0 0 ("This room was used as the castle treasury in" "by-gone years..." "There are no windows, just exits to the" "north and to the east"))
    (18 16 0 14 0 0 0 ("Ooooh.. you are in the chembermaids' bedroom" "There is an exit to the west and a door " "TO the south"))
    (0 17 0 0 0 0 0 ("This tiny room on the upper level is the" "dressing chamber. There is a window to the" "north. with a view of the here garden down" "below. A door leaves to the south."))
    (9 0 16 0 0 0 0 ("This is a small room outside the castle" "lift which can be entered by a door to the north" "another door leads to the west, you can see" "the lake through the southern windows"))))

설명을 출력할 부분을 만든다.

먼저 방에서 각 요소를 가져오는 함수에, 새로 설명을 출력할 부분을 추가한다.

;;; 방의 리스트에서 각 요소를 가져오는 함수
(defun get-rooms-element (room element)
  (cond ((char= element #\n) (car room))
        ((char= element #\s) (nth 1 room))
        ((char= element #\e) (nth 2 room))
        ((char= element #\w) (nth 3 room))
        ((char= element #\u) (nth 4 room))
        ((char= element #\d) (nth 5 room))
        ((char= element #\m) (nth 6 room))
        ((char= element #\v) (nth 7 room))
        (t
         nil)))

다음으로 해당하는 방의 설명을 출력하는 함수를 만든다.

;;; 방의 모습을 설명하는 루틴
(defun describe-room ()
  (format t "The room is ~A~%" *room*)
  (print-room *room*)
  (if (= *room* 9) (setf *room* 10)))

9번방에 들어온 경우에는 자동으로 10번방으로 이동한다.

;;; 방의 내역을 출력하기
(defun print-room (which-room)
  (let ((desc-list (get-rooms-element (get-room which-room ) #\v)))
    (labels ((iter (field)
               (cond ((null field) )
                     (t
                      (format t "~a~%" (car field))
                      (iter (cdr field))))))
      (iter desc-list))))

마지막으로 플레이어가 11번방으로 들어왔다면 게임을 종료한다.

;;; 전체 이벤트 루프

(defun event-loop ()
  (let ((k (get-rooms-element (get-room *room*) #\m))
        (game-over nil))
    (loop
       (status-report)
       (cond ((= *room* 11)
              (progn
                (format t "Congratulation, you escape the castle")
                (setf game-over t)))
             (t
              (let ((input-key (keyboard-event)))
                (cond ((string= input-key "n") (go-direction #\n))
                      ((string= input-key "s") (go-direction #\s))
                      ((string= input-key "e") (go-direction #\e))
                      ((string= input-key "w") (go-direction #\w))
                      ((string= input-key "u") (go-direction #\u))
                      ((string= input-key "d") (go-direction #\d))
                      ((string= input-key "c") (consume-food))
                      ((string= input-key "f") (fight))
                      ((string= input-key "r") (runaway))
                      ((string= input-key "q") (setf game-over t))
                      ((string= input-key "m") (magic-amulet))
                      ((string= input-key "p") (picking-up))
                      ((string= input-key "i") (inventory))))))
       (if (eq game-over t)
           (return-from event-loop)))))

다음 부터는 완성된 버전을 LISP식으로 조금씩 변화시켜나가보도록 하겠다.

2009. 5. 28.

LISP 으로 어드벤처 게임을... Ch 11

전투 모듈에서는 몹에 대한 공격과 방어가 확률에 의해 진행된다.
방어구를 입은 경우 넘겨야할 난이도를 75%정도로 낮추어서 게임 진행을 쉽게 한다.

;;; 전투모듈
(defun fight ()
  (prompt-read "press return to fight")
  (cond ((= *suit* 1)
         (progn
           (format t "Your armor increases your chance of success~%")
           (setf *ff* (* 3 (floor (/ *ff* 4)))))))
  (cond ((and (= *axe* 0) (= *sword* 0))
         (progn
           (format t "You have no weapons~%You mush fight with Base hands~%")
           (setf *ff* (+ *ff* (floor (/ *ff* 5))))))
        ((and (= *axe* 1) (= *sword* 0))
         (progn
           (format t "You have only an axe to fight with~%")
           (setf *ff* (* 4 (floor (/ *ff* 5))))))
        ((and (= *sword* 1) (= *axe* 0))
         (progn
           (format t "You mush fight with your sowrd~%")
           (setf *ff* (* 3 (floor (/ *ff* 4))))))
        (t
         (let ((z (parse-integer (prompt-read "Which weapon? 1- Axe, 2- Sword"))))
           (cond ((= z 1) (setf *ff* (* 4 (floor (/ *ff* 5)))))
                 ((= z 2) (setf *ff* (* 3 (floor (/ *ff* 4)))))))))
  (taking-arms))
실제 전투모드에서는 확률에 의거해 공방을 결정하고, 전투가 끝나는 것도 확률로 결정한다.
전투종료시 난이도 값을 얼마나 남겼는지를 확인해서 승패를 결정한다.

;; 배틀모드
(defun taking-arms ()
  (let ((prob1 (random 10))
        (prob2 (random 10))
        (prob3 (random 10))
        (prob4 (random 100))
        (prob5 (random 10)))
    (cond ((> prob1 5)
           (format t "~a attacks~%" *monster*))
          (t
           (format t "you attack~%")))
    (cond ((> prob2 5)
           (progn
             (format t "You manage to wound it~%")
             (setf *ff* (floor (* 5 (/ *ff* 6)))))))
    (cond ((> prob3 5)
           (progn
             (format t "The monster wounds you!~%")
             (decf *strength* 5))))
    (cond ((> prob4 35)
           (taking-arms))
          (t
           (cond ((> (* prob5 1.6) *ff*)
                  (progn
                    (format t "~%and you managed  to kill the ~a" *monster*)
                    (incf *mk*)
                    (setf (nth 6 (nth (- *room* 1) *castle*)) 0)))
                 (t
                  (progn
                    (format t "The ~a defeated you." *monster*)
                    (setf *strength* (floor (*strength* 2))))))))))

2009. 5. 27.

LISP 으로 어드벤처 게임을 ... CH 10

여기서 만들게 될 함수들은 상점, 음식먹기 등에 해당한다.
먼저 지난번에 만들었던 Event Loop를 살짝 수정했다.

(defun event-loop ()
  (let ((k (get-rooms-element (get-room *room*) #\m))
        (game-over nil))
    (loop
       (status-report)
       (let ((input-key (keyboard-event)))
         (cond ((string= input-key "n") (go-direction #\n))
               ((string= input-key "s") (go-direction #\s))
               ((string= input-key "e") (go-direction #\e))
               ((string= input-key "w") (go-direction #\w))
               ((string= input-key "u") (go-direction #\u))
               ((string= input-key "d") (go-direction #\d))
               ((string= input-key "c") (consume-food))
               ((string= input-key "f") (fight))
               ((string= input-key "r") (runaway))
               ((string= input-key "q") (setf game-over t))
               ((string= input-key "m") (magic-amulet))
               ((string= input-key "p") (picking-up))
               ((string= input-key "i") (inventory))))
       (if (eq game-over t)
           (return-from event-loop)))))


마법 아뮬렛이 있을때 수행할 함수를 만든다.
;;; 마법 아뮬렛을 사용할 때 수행할 곳(6번과 11번은 제외 )
(defun magic-amulet ()
  (let ((ro (+ (random 19) 1)))
    (cond ((or (= ro 6) (= ro 11)) (magic-amulet))
          (t
           (setf *room* ro)))))


또한 음식을 먹는 함수를 만든다.

;;; 음식 먹기
(defun consume-food ()
  (cond ((>= food 1)
         (progn
           (format t "You have ~a units of food~%" *food*)
           (format t "How many do you want to eat? :")
           (let ((z (parse-integer (read-line))))
             (if (> z food)
                 (consume-food)
                 (progn
                   (decf *food* z)
                   (incf *strength* (* 5 z)))))))))


이제 방에 보물이 있는 경우 집는 함수를 만든다.
;;; 방의 물건 집기
(defun picking-up ()
  (let ((treasure (get-rooms-element
                   (get-room *room*)
                    #\m)))
    (if (< treasure 10)
        (format t "There is no treasure to pickup")
        (progn
          (incf *wealth* treasure)
          (setf (nth 6 (nth (- *room* 1) *castle*)) 0)))))

마지막으로 상점에서 물건을 구입하는 함수를 만든다.
;;; Show shop
(defun show-shop ()
  (format t "You can buy 1 - Flamming Torch ($15)~%")
  (format t "            2 - Axe ($10)~%")
  (format t "            3 - Sword ($20)~%")
  (format t "            4 - Food ($2 per unit)~%")
  (format t "            5 - Magic of Amulet ($30)~%")
  (format t "            6 - Suit of Armor ($50)~%")
  (format t "            0 - To continue Adventure ~%"))

;;; Shop에서 물건사기
(defun inventory ()
  (format t "Provision & Inventory~%")
  (cond ((> *wealth* 0)
         (progn
           (show-shop)
           (let ((z (parse-integer (prompt-read "Enter no of item:"))))
             (cond ((= z 1)
                    (progn
                      (setf *light* 1)
                      (decf *wealth* 15)))
                   ((= z 2)
                    (progn
                      (setf *axe* 1)
                      (decf *wealth* 10)))
                   ((= z 3)
                    (progn
                      (setf *sword* 1)
                      (decf *wealth* 20)))
                   ((= z 4)
                    (progn
                      (let ((q (prompt-read "how many food do you want")))
                        (cond ((> (* q 2) *wealth*)
                               (format t "You Can't Get enough money"))
                              (t
                               (progn
                                 (incf *food* q)
                                 (defc *wealth* (* q 2))))))))
                   ((= z 5)
                    (progn
                      (setf *amulet* 1)
                      (decf *wealth* 30)))
                   ((= z 6)
                    (progn
                      (setf *armor* 1)
                      (decf *wealth* 50)))))))
        (t
         (format t "You have no money~%"))))

일단 기본적인 툴이 모두 마련되었다.
다음장에서는 전투에 대한 부분을 살펴보자.

2009. 5. 25.

2D 플랫폼이 훌륭했던 이유..

What made those old, 2D platformers so great?를 대충 정리해보면..
  1. 조작
  2. 레벨 디자인
  3. 상태
등을 들 수 있다.

조작
조작은 플레이어에 부여하는 모든 능력과 속성을 의미한다. 달리기나, 점프, 스핀대시같은 다양한 이동방법부터 일시적인 무적상태같은 특이상황을 모두 아우른다.

좋 은 조작식이란 간결하고 명확해야한다. 최대한 직관적으로 설계해야하며, 사용자에게 혼동을 줄 수 있는 요소는 최대한 배제해야한다. 예를 들어, 게임중에 오브젝트와 충돌하는 경우,  아이템의 경우에는 습득이 되어야하며, 적과의 경우라면 피해를 입게해야한다. 아이템을 얻는데 필요이상의 동작이 필요하거나, 적과의 충돌을 애매하게 판정해서는 안된다.

이러한 기본 가정하에서 사용자와의 인터페이스를 구성할 수 있다.

좋은 조작법이란 현실에서 보기힘든 빠르고, 정교한 움직임을 보임으로서, 사용자의 동기를 유발할 수 있어야한다. 하지만 본질적인 흥미는 레벨디자인과의 조화에서 얻을 수 있어야한다.

레벨 디자인
게 임을 고유한 외양을 지니는 개별적인 지역으로 구성함으로써, 상당한 효과를 얻을 수 있다. 또한, 상호작용적인 요소를 배치하여, 이러한 특징을 부각시킬 수 있다. 하지만, 이러한 요소는 종종, 부조화를 일으킬 수 있기 때문에 사용에 주의를 해야한다.
사소한 문제같지만, 적절히 규제되지 않는다면 사용자가 혼동을 일으킬 수 있다.

배 경과 동시에, 게임의 레벨은 다양한 플랫폼으로 구성되어있다. 사다리, 미끄럼틀, 순간 이동장치, 계단, 입구같은 요소가 그것이다. 이런 요소는 게이머가 실제로 게임을 진행하는 지역이기때문에, 그 메커니즘을 명확하고 분명하게 이해할 수 있어야한다. 이 부분에서 조작성과의 조화가 이루어져야한다.

좋은 플레이 공간은 플레이어를 다양한 능력을 사용할 수 있도록 끌어당긴다. 기존에 존재하는 다양한 요소를 십분 이용하도록 유도하거나 어느 정도 강제할 정도가 되어야한다.

특 정 지역을 들어가거나 떠나도록 하는 요소를 분명하게 알려주는 것 역시 큰 도움이 된다. 게임상에 수집해야할 요소를 경로에 따라 배치함으로서 게이머가 길을 잃지 않도록 배려할 수 있으며, 이를 통해 게임은 더 쉽고, 즐기기에 편안하게 된다.

상태
다음의 씬을 상상해보자: 캐릭터가 막 점프를 했다. 적은 캐릭터를 노리고, 미사일을 벽에대고 쏘았고, 벽을 파괴시켰다. 이런 씬에서 몇가지 상태를 찾을 수 있다: 점프, 추적, 발사, 파괴가 그것이다.

상 태는 게임상의 물체의 행동과 속성에 붙는 일련의 논리적인 단위의 명명이라고 할 수 있다. 이를 통해 적과 보스의 핵심적인 인공지능과, 플레이어가 이를 파악하고 패턴을 깨는 행위를 표준화 할 수 있다. 게임상의 몹과 요소에 다양한 개성을 부여함으로서, 이에 대항하는데 여러가지 기법을 사용하도록 부추킬 수 있다.

또한 이러한 메커니즘을 효율적으로 배치한다면 상당한 리플레이성을 부여할 수 있으며, 타임어택같은 하드코어적인 시도록 할 수 있는 고난이도의 플레이역시 가능케할 수 있다.

2009. 5. 22.

문자열 뒤집기...

그냥..

(reverse "hello") -> "olleh"

임..

쩝.. 좀 긴 버전..

(defun string-reverse (str)
  (let ((strlen (length str)))
    (cond ((= strlen 0) str)
          ((= strlen 1) str)
          (t
           (string-concat (string (aref str (- strlen 1)))
                          (string-reverse (subseq str 0 (- strlen 1))))))))


iteration 버전?

(defun string-rev (str)
  (labels ((iter (src tar)
             (let ((strlen (length tar)))
               (cond ((= (length tar) 0) src)
                     (t
                      (iter (string-concat src (string (aref tar (- strlen 1))))
                            (subseq tar 0 (- strlen 1))))))))


머 C로 짤줄은 알지만.. 난 LISP을 사랑하니깐..

LISP으로 어드벤처 게임을... Ch 9

이번장에서는 실제로 키보드 입력을 받아들이고 게임을 구성하는 루프를 만든다.
먼저 키보드 입력을 받아들여보자.

;;; 사용자 키보드 입력을 체크하고 그에따란 이벤트 처리를 함
(defun keyboard-event ()
  (format t "What do you want to do:")
  (let ((k (read-line)))
    (if (= (length k) 1)
        k
        (keyboard-event))))

다음으로 이벤트 루프를 구성한다.

;;; 전체 이벤트 루프

(defun event-loop ()
  (let ((k (get-rooms-element (get-room *room*) #\m))
        (game-over nil))
    (loop
       (status-report)
       (let ((input-key (keyboard-event)))
         (cond ((string= input-key "n") (go-direction #\n))
               ((string= input-key "s") (go-direction #\s))
               ((string= input-key "e") (go-direction #\e))
               ((string= input-key "w") (go-direction #\w))
               ((string= input-key "c") (consume-food))
               ((string= input-key "f") (fight))
               ((string= input-key "r") (runaway))
               ((string= input-key "q") (setf game-over t))
               ((string= input-key "i") (inventory))))
       (if (eq game-over t)
           (return-from event-loop)))))


구성된 이벤트 루프를 토대로 해당하는 방으로 이동하는 함수를 만들고, 나머지 모든 부분을 연결하는 게임함수를 만든다.

;;; 해당 방의 방위로 이동하는 함수
(defun go-direction (direction)
  (let ((door (get-rooms-element (get-room *room*) direction)))
    (cond ((= door 0) (format t "There is no door in that direction."))
          (t
           (setf *room* door)))))
 
;;; 실제 게임 실행부분
(defun run-game ()
  ;;; 게임 초기화
  (reset-mobs)
  (init-var)
  (get-player-name)
  (put-gold 4)
  (put-mob 4)
  (set-init-treasure)
  (event-loop))

이제 슬슬 기본틀을 완성된 것 같고.. 음식과 전투, 도망가기, 인벤토리등을 구성하도록 하자

2009. 5. 21.

2등으로 성공하기..

한겨레 21:  2등은 어떻게 살아남는가를 읽고 나서 아웃사이더로 일하는 내가 생각해봐야할 글이라고 생각한다.

원 글에서는 대항마 전략과 체계화 전략을 말해주면서 실제 펩시와 에이비스라는 업계 2위의 업체를 예로 들어 설명했었다. 여기에 내가 생각하는 OS에서의 리눅스, 프로그래밍 언어에서의 LISP을 생각해보았다.

사 실 리눅스 커뮤니티는 오랜동안 윈도를 넘어야한다는 일종의 강박관념을 쥐고 있는 것 같다. 더 적은 리소스, 더 많은 자유, 더 많은 xxx.. 라는 끝도 없는 우월성을 주장하지만 내가 볼 때 가장 먼저 해야할 것은 스스로 한계를 인정해야한다는 사실이다.
이 미 사회에서의 1위는 윈도로 결정이 난 상태이다. 그렇다면, 윈도를 1위로 인정하고 2위로서 1위와 연결하는 전략을 세워야하는 것이 더 적당하다고 본다. 과감하게 2등(현재는 맥에 빌려 3등일지도 모르지만)임을 선언함으로서, 윈도를 분명한 1위 대상으로 인지시키만 (이미 많은 사람들이 공식, 비공식적으로 인정하는 바이다) 자신을 1위와 함께 갈 수 있는 2위로서 자리를 잡는 것이 중요하다고 본다.

예를 들어, 다양한 개발툴을 무료로 쓸 수 있다거나, 서버 테스트 베드로서 아주 훌륭하게 역할을 취할 수 있다거나 하는 점이다. 클라이언트로서는 윈도, 보조툴로 리눅스라는 레벨을 진행한다면 나름 괜찮은 성공을 할 수 있을 것 같다. 이미 집집마다 2~3대의 PC가 기본이 되고 있는 상황에서, mp3나 동영상을 보관할 수 있는 보조적인 부분을 장악한다면 충분한 점유율을 만들 수 있을 것으로 본다.

첫번째 PC는 윈도, 그리고 데이터 보관은 리눅스.. 이런 식으로 대항마 전략을 이끈다면 실제 사람들은 편리하고 간단한 작업은 윈도, 안전하고 신뢰할 수 있는 리눅스라는 식으로 진행한다면 상당한 효과를 줄 수 있을 것이라고 생각한다.

LISP역시 C/C++만큼 속도를 낼 수 있다.. 이런 식의 진부한 강조점을 버리고, C/C++/Java등이 분명한 1위임을 인정하고, 가지고 있는 강점 - 놀라운 추상화 능력이나, 과거에 쌓아두었던 수많은 AI 요소들 - 을 기반으로 협동플레이를 할 수 있다면, 훌륭한 2위자리로 올라설 수 있으리라고 생각한다.

체 계화 전략에서 승부가 갈린 것은 웹에서의 Perl > PHP > Ruby로 이어지는 패러다임의 변화라고 생각한다. 초창기 CGI 패러다임에서 최고의 위치를 자랑하던 Perl을, 간단하고 쉬운 웹 어플리케이션이라는 체계를 완성함으로서 PHP가 끌어내렸고, Rail이라는 걸출한 프레임워크를 무기로 Ruby가 그 자리를 위협하는 것이 좋은 예라고 생각한다.

갈길은 멀다. 아웃사이더중의 아웃사이더 일 (리눅스에서 LISP으로 개발함.. ㅠ.ㅠ)을 계속하고 있지만, 이런 전략을 꾸준히 세워나가면 언젠가 찬란한 2위로 등극할 수 있으리라 생각한다.

LISP으로 어드벤처 게임을... Ch8

슬슬 본게임에 다가간다. 이번부분에서는 매턴마다 수행할 일과, 해당하는 방에 들어갔을때 일어날 일을 처리한다.

매턴마다 일정수치의 체력이 감소하는데, 체력을 회복하려면 음식을 먹어야한다. 체력이 지나치게 많이 떨어지면 죽게된다.
또한 사망시에는 그에 해당하는 점수를 출력해야한다.



;;; 매 틱마다 해야할 일들
(defun tick-task ()
(labels ((decrease-strenth ()
(decf *strength* 5)
(if (< *strength* 10)
(format t "Warning:~A Your strength is running row~%" *char-name*))
(if (< *strength* 1)
(char-dead)
(incf *tally*))))
(decrease-strength)))

;;; 사망시 해야할 일들
(defun char-dead ()
(format t "You have died...~%")
(print-score))


;;; 스코어 출력부분
(defun print-score ()
(+
(* 3 *tally*)
(* 5 *strength*)
(* 2 *wealth*)
*food*
(* 30 *mk*)))



정상적으로 한 턴을 수행하게되면 (즉 죽지않았다면) 일단 현재 상태를 출력해야한다.
현재 빛을 쪼여줄 만한 수단이 없다면, 방의 전체적인 윤곽은 알 수 없다. 다만 방안의 내용은 알 수 있다.

<br />;;; 상태 리포트<br />(defun status-report ()<br />  (format t "~A, Your strength is ~A~%" *char-name* *strength*)<br />  (if (> 0 *wealth*) (format t "You have $~A" *wealth*))<br />  (if (> 0 *food*) (format t "Your provious sack hold ~A units of food~%" *food*))<br />  (if (= 1 *suit*) (format t "You are wearing armor~%"))<br />  (if (not (= (+ *axe* *sword* *amulet*) 0))<br />	  (progn<br />		(format t "You carrying")<br />		(if (= *axe* 1) (format t " axe "))<br />		(if (= *sword* 1) (format t " sword "))<br />		(if (= *amulet* 1) (format t " amulet "))<br />		(format t "~%")))<br />  (if (= 0 *light*)<br />	  (format t "It is too dark to see anything~%")<br />	  (describe-room))<br />  (describe-objects))<br /><br />



해당하는 방을 설명하는 부분은 일단 다음으로 미루고 방안의 물체들을 설명하는 루틴을 보자.
방의 내용을 담고 있는 리스트의 7번째 내용은 보물이나 몹을 가리킨다. 따라서 해당하는 오브젝트에 따라 설명내용을 바꿔준다.


<br />;;; 방의 모습을 설명하는 루틴<br />(defun describe-room ()<br />  ())<br /><br />;;; 방내부의 오브젝트를 설명<br />(defun describe-objects ()<br />  (let ((k (get-rooms-element (get-room *room*) #\m)))<br />	(cond ((> k 0)<br />		   (format t "There is treasure here work $~A" k))<br />		  ((< k 0)<br />		   (describe-monster k)))))<br />



괴물이 존재한다면, 해당하는 값에 따라 괴물을 설정하고 몹의 체력을 결정한다.

<br />;;; 괴물을 설명하고 해당 값을 설정함...<br />(defun describe-monster (k)<br />  (cond ((= k -1) <br />		 (progn<br />		   (setf *monster* "Ferocious werewolf")<br />		   (setf *ff* 5)))<br />		((= k -2)<br />		 (progn<br />		   (setf *monster* "Fanatical fleshgorger")<br />		   (setf *ff* 10)))<br />		((= k -3)<br />		 (progn<br />		   (setf *monster* "Maloventy maldemer")<br />		   (setf *ff* 15)))<br />		((= k -4)<br />		 (progn<br />		   (setf *monster* "devastating ice-dragon")<br />		   (setf *ff* 20))))<br />  (format t "It is ~A.~%The danger level is ~A" *monster* *ff*))<br />



다음장에서는 입력을 받고 해당하는 입력에 따라 행동을 취하게 하는 부분을 알아본다.

2009. 5. 19.

LISP으로 어드벤처 게임을... Ch6, Ch7

6장과 7장을 동시에 공략해보자.
6장의 경우에는 게임상의 맵을 지정하는 루틴이 나온다.
북/남/동/서/위층/아래층/기타 이런 식으로 배열로 해당하는 지도를 구성한다.

또한 7장에서는 초기화 루틴중에 보물과 몬스터를 배열하고, 마지막으로 각종 변수를 저장하는 부분이 나온다.




먼저 게임상의 맵을 구성해보자..



(defvar *castle*
'((0 2 0 0 0 0 0)
(1 3 3 3 0 0 0)
(2 0 5 2 0 0 0)
(0 5 0 0 0 0 0)
(4 0 0 3 15 13 0)
(0 0 1 0 0 0 0)
(0 8 0 0 0 0 0)
(7 10 0 0 0 0 0)
(0 19 0 8 0 8 0)
(8 0 11 0 0 0 0)
(0 0 10 0 0 0 0)
(0 0 0 13 0 0 0)
(0 0 12 0 5 0 0)
(0 15 17 0 0 0 0)
(14 0 0 0 0 5 0)
(17 0 19 0 0 0 0)
(18 16 0 14 0 0 0)
(0 17 0 0 0 0 0)
(9 0 16 0 0 0 0)))

;;; 해당번호 방을 가져오는 함수
(defun get-room (room-num)
(nth (- room-num 1) *castle*))

;;; 방의 리스트에서 각 요소를 가져오는 함수
(defun get-rooms-element (room element)
(cond ((char= element #\n) (car room))
((char= element #\s) (nth 1 room))
((char= element #\e) (nth 2 room))
((char= element #\w) (nth 3 room))
((char= element #\u) (nth 4 room))
((char= element #\d) (nth 5 room))
((char= element #\m) (nth 6 room))
(t
nil)))



이제 7장에서 논의하는 4개의 보물과 4곳에 몬스터를 넣는 부분이다.
또한 추가로 두 개의 방안에 보물을 넣는다.


<br />;;; 모든 방의 mobs정보를 리셋<br />(defun reset-mobs ()<br />  (do ((i 1 (1+ i)))<br />	  ((< (length *castle*) i))<br />	(setf (nth 6 (nth (- i 1) *castle*)) 0)))<br /><br />;;; 방 4개에 금을 넣기<br />(defun put-gold (how-much)<br />  (cond ((= how-much 0) nil)<br />		(t<br />		 (let ((pos (random (length *castle*)))<br />			   (gold (+ (random 100) 1)))<br />		   (let ((gold-in-cell (get-rooms-element<br />							   (get-room (+ pos 1))<br />								#\m)))<br />			 (cond ((or<br />					 (= pos 5)<br />					 (= pos 10)<br />					 (not (= 0 gold-in-cell))) <br />					(put-gold how-much))<br />				   (t<br />					(progn <br />					  (setf (nth 6 (nth (1+ pos) *castle*)) gold)<br />					  (put-gold (1- how-much))))))))))<br /><br />;;; 방 4개에 monster 넣기<br />(defun put-mob (how-much)<br />  (cond ((= how-much 0) nil)<br />		(t<br />		 (let ((pos (1- (random (length *castle*)))))<br />		   (let ((gold-in-cell (get-rooms-element<br />							   (get-room (+ pos 1))<br />								#\m)))<br />			 (cond ((or<br />					 (= pos 5)<br />					 (= pos 10)<br />					 (not (= 0 gold-in-cell))) (put-mob how-much))<br />				   (t<br />					(progn <br />					  (setf (nth 6 (nth (1+ pos) *castle*)) (- how-much))<br />					  (put-mob (1- how-much))))))))))<br /><br /><br />;;; 4번방과 16번방에 추가적인 보물넣기<br />(defun set-init-treasure ()<br />  (setf (nth 6 (nth 3 *castle*)) (random 100))<br />  (setf (nth 6 (nth 15 *castle*)) (random 100)))<br />



이제 다음으로 초기화 루틴 부분이다.

플레이어에 관한 정보에 대한 각종 변수를 초기화한다.


<br />;;; 기본 변수들<br />(defvar *strength* nil)<br />(defvar *wealth* nil)<br />(defvar *food* nil)<br />(defvar *tally* nil)<br />(defvar *mk* nil)<br /><br />;; 게임상 사용 변수<br />(defvar *room* nil)<br />(defvar *sword* nil)<br />(defvar *amulet* nil)<br />(defvar *axe* nil)<br />(defvar *suit* nil)<br />(defvar *light* nil)<br />(defvar *char-name* nil)<br /><br />;;; 기본 변수 리셋<br />(defun init-var ()<br />  (setf *strength* 100)<br />  (setf *wealth* 75)<br />  (setf *food* 0)<br />  (setf *tally* 0)<br />  (setf *mk* 0))<br /><br />;;; 게임상 플레이어 stat 세팅하기<br />(defun get-player-name ()<br />  (let ((pname (read-line)))<br />	(setf *char-name* pname)<br />	(setf *room* 6)<br />	(setf *sword* 0)<br />	(setf *amulet* 0)<br />	(setf *axe* 0)<br />	(setf *suit* 0)<br />	(setf *light* 0)))<br />



이제 다음장에서는 메인 이벤트 처리부분이 시작된다.

LISP 으로 어드벤처 게임을... 5장 전체 구조 짜기

이제 전체 프로그램의 구성을 살펴보자.



전체적인 구조가 위와 같다. (원래 소스코드는 BASIC이었슴..)
이제 대략적인 가상 코드를 작성해보자.

(identify)
(init_routine)
(loop
  (game-loop)
  (if (end-codition?) (return))
(congratulation)


다음으로 필요한 것은 다음과 같다.

(major-handling)
(describe-room-with-light)
(describe-monster-treasure)
(ask-player-move)
(fight)
(room-description)
(death)
(pick-up-treasure)
(tell-player-fight)
(eat-food)
(inventory)
(floor-plan)


등이다.

init-routine은 다음과 같다
assign-variable
fill floor plan array
get-player's name
allot treasure to room
allot monster to room

개략적인 코드가 완성되었다면 이제부터는 실제 코딩..
6장부터 험난한 코드 컨버팅이 시작된다. =.=



2009. 5. 18.

LISP으로 어드벤쳐 게임을... 4장. Floor Plan

4장에서는 실제 지도를 만드는 법에 대해서 살펴본다.



해당하는 이미지는 아래 링크에서 가져올 수 있다.

http://www.atariarchives.org/adventure/picture04-1.gif

그럼 해당하는 지도를 보면..

이런 식이다. 각 방은 지정된 다른 방으로 이동할 수 있는 문이 있다.
이것을 일련의 리스트로 분류해보면 다음과 같다.

1번 방은 동쪽으로 3번, 남쪽으로 2번방과 연결되어 있다. 즉 북/남/동/서에 따른 리스트로 보면..

(0 2 3 0)


이라고 쓸 수 있다. 편의상 0은 막혔다고 가정해본다.
이런 식으로 5번방까지를 모두 리스트로 표시하면

'((0 2 3 0)
(1 0 5 0)
(0 4 0 1)
(3 5 0 0)
(4 0 0 2))

로 표현할 수 있다. 여기에서는 베이직 코드를 가급적 1:1로 LISP으로 변환하는 중이니 일단 딴지는 금물..

이제 각 방과 각 방의 문이 어디로 통해지는지를 LISP코드로 작성하면..


(defvar *map*
'((0 2 3 0)
(1 0 5 0)
(0 4 0 1)
(3 5 0 0)
(4 0 0 2)))

(defun get-room (which)
(cond ((< which 1)
(error "방코드가 1보다 작습니다."))
(t
(nth (- which 1) *map*))))

(defun get-direction (dir)
(cond ((string= dir "north") 0)
((string= dir "south") 1)
((string= dir "east") 2)
((string= dir "west") 3)
(t
(error "지원되지 않는 방향입니다."))))

(defun get-door (room dir)
(nth (get-direction dir) (get-room room)))
이제 기본적인 방을 돌아다닐 수 있다.
개별적인 방의 door를 얻어 이를 문장으로 표현해보면 다음과 같다.



<br />(defun desc-door (door)<br />  (cond ((= door 1) "북쪽으로 가는 문이 있습니다.")<br />		((= door 2) "남쪽으로 가는 문이 있습니다.")<br />		((= door 3) "동쪽으로 나갈 수 있습니다.")<br />		((= door 4) "서쪽으로 가는 문이 열려 있습니다.")))<br /><br />(defun desc-room (room)<br />  (let ((which-room (get-room room)))<br />	(format t "당신은 ~A번 방에 있습니다.~%" room)<br />	(labels ((iter (room)<br />			   (cond ((null room) '())<br />					 (t<br />					  (let ((room_text (desc-door (car room))))<br />						(cond ((not (null room_text))<br />							   (format t "이 방에는 ~A~%" (desc-door (car room))))))<br />					  (iter (cdr room))))))<br />	  (iter which-room))))<br /><br />
이 된다.

이제 마지막으로 방을 돌아다니는 코드를 입력하자.


<br /><br />(defvar *current-room* nil)<br /><br />(defun goto-room ()<br />  (format t "몇번 방으로 가시겠습니까?:")<br />  (setf *current-room* (read))<br />  (desc-room *current-room*))<br /><br />(defun goto-door ()<br />  (format t "어느 방향으로 가시겠습니까?(N[orth]/S[outh]/E[ast]/W[est]:")<br />  (let ((direction (read-char)))<br />	(let ((room (get-door *current-room*<br />						  (cond ((char= direction #\n) "north")<br />								((char= direction #\s) "south")<br />								((char= direction #\e) "east")<br />								((char= direction #\w) "west")))))<br />	  (cond ((= room 0) <br />			 (format t "그쪽은 막혔습니다."))<br />			(t<br />			 (setf *current-room* room))))))<br />


LISP으로 어드벤쳐 게임을... Prolog

Atari 에 관련된 책을 모아놓은 사이트가 있다.

http://www.atariarchives.org/

이 사이트에서 간단한 게임에 대한 책을 가지고 LISP 으로 간단한 구현을 하려고 한다.
그 첫번째가 Creating Adventure Games On Your Computer 이다.

모쪼록 즐거운 여행이 될 수 있기를~~

일단 시작하면서 3장까지는 어드벤쳐 게임이 어떤 것인지 대충 맛일 보는데 유용하니 한번 쭈욱 훑어본다.

2009. 5. 11.

MIT에서 Scheme대신 Python으로 이전한 이유

전산쪽에서 근무하는 사람이라면 한번쯤 맞닥뜨리게될 책이 SICP이다.
이 책은 EE/CS(Eletrical Enginerring / Computer Science) 부문에서 1학년을 대상으로하는 교재였다. 이 기본 교재가 사용된 커리큘럼이 6.001 이었는데 이제는 6.01로 변경되면서 Python을 이용하고 있다.

이에 대해서 많은 이야기들이 있었지만, 최근 한 블로그에서 그에 관한 글이 실렸다.

거기서 한 부분을 살짝 발췌해보았다.

However, nowadays, a real engineer is given a big software library,with a 300-page manual that’s full of errors.  He’s also given a robot,whose exact behavior is extremely hard to characterize (what happenswhen a wheel slips?). The engineer must learn to perform scientificexperiments to find out how the software and hardware actually work, atleast enough to accomplish the job at hand.  Gerry pointed out that wemay not like it this way (”because we’re old fogies”), but that’s theway it is, and M.I.T. has to take that into account.


오늘 날, 실제 공학자는 300페이지에 달하는 에러투성이 메뉴얼이 포함된 방대한 소프트웨어 라이브러리를 제공받는다.또한,행동양식을 정확히 규정하기 어려운 로봇을  추가로 제공받는다. 공학자는 최소한 작업을 자유자재로 수행하기위해서는, 소프트웨어와 하드웨어가 실제로 어떻게 동작하는지 알아내기위한 과학적인 실험을 수행하는 법을 익혀야한다. Gerry는 우리가 이런 방식을 좋아하지 않겠지만, 현실이 그렇다는 것을 지적했고, MIT는 이를 고려한 것이다.

모든 것이 간단하고 분명했던 예전에 비해서, 현재 기술자들이 마주하는 현실은 확실히 복잡하고, 예측불가능하며, 하나의 완벽한 원칙을 통한 방식보다는 시행착오를 거쳐서 얻는 편이 더 간단한 시대가 되었다.
Scheme 이 만들어질 당시에는 정확힌 원칙을 토대로 만들어진 조각들을 하나하나 붙여서 만들면, 그것이 바로 솔루션이 되는 시대였다. 하지만, 시스템이 고도화되었고, 수많은 데이터들과, 복잡성이 자리를 잡은 시대에는, 한 사람의 프로그래머가 모든 시스템을 구성할 수 있는 시대와는 작별을 고해야했다.

다양한 방식의 라이브러리와 환경을 통합해나가야하는 상황에서 Scheme이 보여주었던 방식으로는 한계에 다달은 모양이다. 그 와중에 선택된 것이 Python 이다. 이유는 모르겠지만.. 아마도 커리큘럼 구성진이 실용적이면서도, 우아한 문법, 잘 정리된 라이브러리등에 점수를 주었는지도 모른다.

어쨌거나 6.001 이 더이상 유효한 커리큘럼이 아니라니 무척이나 아쉽다. 마이너한 언어를 다루는 사람에게는 더더욱 그렇다.. 음냠..

PS> 하지만 나처럼 혼자서 개발하는 사람에게는 그냥 그런가보다하는 생각뿐.... Python도 어느 정도 다루긴 하는데... 그놈의 탭인덴테이션은 내 취향이 아니라는 문제가... 그래서 Perl 을 계속하나부다...

2009. 4. 27.

사이냅소프트 막대자르기 문제

얼핏 본바로는 소인수분해를 해서 그 리스트를 나누어진 조각중 가장 큰 것보다 큰 것중 가장 작은것을 가져오면 될듯하다



;; 길이가 같은 여러 개의 막대를 잘라 다양한 길이로 만들었다.
;; 원래대로 막대를 돌려놓을 때 가능한 길이중 가장 작은 값을 찾아라..


;; 해당하는 문제는 소인수 분해를 해서, 그 중에 잘라놓은 막대보다 큰 것중 가장 작은 수를 구하면 된다.
;; 소인수 분해를 하자.. 소인수 분해의 수는 소수만 가능.. 1은 제외하고 2부터..

(defun sum (lst)
(cond ((null lst) 0)
(t (+ (car lst)
(sum (cdr lst))))))

(defun max-lst (lst)
(cond ((= (length lst) 1) (car lst))
((null lst) '())
(t (max (car lst)
(max-lst (cdr lst))))))

(defun min-lst (lst)
(cond ((= (length lst) 1) (car lst))
((null lst) '())
(t (min (car lst)
(max-lst (cdr lst))))))

(defun digest-num (n)
(labels ((iter (s n)
(cond ((>= s n) '())
(t
(cond ((= (mod n s) 0)
(cons s (iter (+ s 1) n)))
(t
(iter (+ s 1) n)))))))
(iter 2 n)))

(defun bar-check (lst)
(let ((wholebar (sum lst))
(max-ele (max-lst lst)))
(let ((dnum (digest-num wholebar)))
(let ((least-max (find-at-least dnum max-ele)))
(cond
((null least-max) -1)
((= wholebar least-max) -1)
(t
least-max))))))

;; 리스트 내역을 필터링하기
(defun filter (lst proc)
(cond ((null lst) '())
(t
(if (funcall proc (car lst))
(cons (car lst) (filter (cdr lst) proc))
(filter (cdr lst) proc)))))

;; 리스트중 v보다 큰 요소중 가장 작은 것을 구하기..
(defun find-at-least (lst v)
(min-lst (filter lst
#'(lambda (c)
(> c v)))))


사이냅소프트 입사문제 2진수 계산기(곱셈)

엄청 지저분해졌지만 그런대로. 답 나옴..
2진수의 곱을 이쁘게 출력하는 함수..



;; 이쁘게 찍기
(defun pretty-print-mul (a b)
(let ((ans (binary-mul a b))
(lst (reverse (binary-bit-calculate a b))))
(let ((digits (length ans)))
(format t "~A~%" (fill-spacer a digits))
(format t "~A~%" (fill-spacer b digits))
(format t "~A~%" (fill-spacer (dashes (max (length a) (length b))) digits))
(dolist (ele lst)
(format t "~A~%" (fill-spacer ele digits)))
(format t "~A~%" (fill-spacer (dashes (length ans)) digits))
(format t "~A~%" (fill-spacer ans digits)))))

(defun dashes (n)
(cond ((= n 0) "")
(t (string-concat "-"
(dashes (- n 1))))))

(defun binary-mul (a b)
(trim-left-zero (sumup (binary-bit-calculate a b))))


;; 2진수 문자열을 풀어서 각 자리수별로 연산 결과를 만들기...
;; 자리수가 증가할 수록 뒷부분에 문자열도 따라서 증가해야한다.
(defun binary-bit-calculate (a b)
(cond ((= (length b) 0) '())
(t
(cons
(string-concat (binary-and a (char b 0)) (spacer (- (length b) 1)))
(binary-bit-calculate a (subseq b 1 (length b)))))))

(defun sumup (lst)
(cond ((null lst) "")
(t (binary-bit-add (car lst)
(sumup (cdr lst))))))

;; 앞쪽의 0 지우기..
(defun trim-left-zero (str)
(cond ((not (char= (char str 0) #\0)) str)
(t
(trim-left-zero (subseq str 1 (length str))))))
;; 2진수 문자열의 합
(defun binary-bit-add (a b)
(let ((la (length a))
(lb (length b)))
(let ((ll (max la lb)))
(let ((ra (fill-spacer a ll))
(rb (fill-spacer b ll)))
(binary-add ra rb)))))

(defun fill-spacer (s n)
(cond ((= (length s) n) s)
(t
(string-concat (string " ")
(fill-spacer s (- n 1))))))

;; 2진수의 합 계산하기..
; 각 항의 값을 더하고 캐리가 발생하면 다음 자리로 올린다.
; Reverse 가 된 것이라고 가정

(defun rev-binary-add (a b)
(labels ((adds (a b)
(cond ((and (char= a #\1) (or (char= b #\0) (char= b #\Space))) "1")
((and (or (char= a #\0) (char= a #\Space)) (char= b #\1)) "1")
(t "0")))
(cadds (a b)
(cond ((and (char= b #\1) (char= a #\1)) "1")
(t "0")))
(binary-iter (c a b)
(cond ((= (length a) 0) c)
(t
(let ((fa (char a 0))
(fb (char b 0))
(ans (string "0"))
(carry (string "0")))
(progn
(setf ans (adds fa fb))
(setf carry (cadds fa fb))
(cond ((string= c "1")
(if (string= ans "1")
(progn
(setf ans (string "0"))
(setf carry (string "1")))
(setf ans (string "1")))))
(string-concat ans
(binary-iter carry
(subseq a 1 (length a))
(subseq b 1 (length b))))))))))
(binary-iter (string "0")
a
b)))

(defun binary-add (a b)
(reverse (rev-binary-add (reverse a)
(reverse b))))

(defun stringloop (a)
(cond ((= (length a) 0) (string ""))
(t
(stringloop
(subseq a 1 (length a))))))


;; 어떤 이진수 A와 0, 1의 곱 결과를 돌려주기.
(defun binary-and (a b)
(cond ((char= b #\0)
(zeros (length a)))
(t a)))

;; 특정 길이만큼 0로 찬 결과를 돌려주기..
(defun zeros (n)
(cond ((= n 0) (string ""))
(t
(string-concat (string "0")
(zeros (- n 1))))))

;; 특정 길이만큼 스페이스
(defun spacer (n)
(cond ((= n 0) (string ""))
(t
(string-concat (string " ")
(spacer (- n 1))))))


사이냅 소프트 입사문제 Triple

주어진 양수의 리스트에서 다음과 같은 조건을 만족하는 조합의 수를 출력하기..

x + y = z

리스트 상의 모든 내역을 조사해서 해당하는 내역에 맞는 값의 조합을 구해내야한다.



(defun findout_triple (lst)
(let ((n (length lst))
(total 0))
(do ((i 0 (+ i 1)))
((eq n i) nil)
(let ((lst-i (nth i lst)))
(do ((j 0 (+ j 1)))
((or (eq n j) (eq i j)) nil)
(let ((lst-j (nth j lst)))
(do ((k 0 (+ k 1)))
((or (eq n k) (eq i k) (eq j k)) nil)
(let ((lst-k (nth k lst)))
(if (or (eq (+ lst-i lst-j) lst-k)
(eq (+ lst-i lst-k) lst-j)
(eq (+ lst-j lst-k) lst-i))
(progn
(format t "~A ~A ~A ~%" lst-i lst-j lst-k)
(setf total (+ total 1))))))))))
total))

조금 헤멨던게 do 문에서 반복의 첨자로 루프가 돌 때, 해당 첨자에 문제가 있을 때까지도 해당식이 평가되기 때문에, 에러가 발생할 수 있다는 것..

지저분하게 풀렸다.. -_-;;;

2009. 4. 23.

사이냅 소프트 입사문제 3번

어떤 자연수가 세 개의 자연수의 제곱합으로 나타낼 수 있는지를 보여주기..

n^2 = a^2 + b^2 + c^2

원래는 하나만 보여줘야하지만 그냥 다 보여주는 루틴 만들어놨음
LISP의 루프 구조하나는 확실히 쓰게됐다...


(defun square (n)
(* n n))

(defun find3num (n)
(let ((sq (floor (sqrt n))))
(dotimes (i (+ sq 1))
(let ((sq1 (floor (sqrt (- n (square i))))))
(dotimes (j (+ sq1 1))
(let ((sq2 (floor (sqrt (- n (square i) (square j))))))
(dotimes (k (+ sq2 1))
(cond ((= (+ (square i) (square j) (square k)) n)
(format t "~A ~A ~A ~%" i j k))))))))))


사이냅 소프트 입사문제 2번의 간략 해..

머.. 각 문자별로 입력하는 키 코드 값을 생성한 후에 전체 길이를 구하면 끝~~



;; Get char and return keypad list
;; ex) (mobilepad #\c) -> (2 2 2)
(defun mobilepad (ch)
(cond
((char= ch #\a) '(2))
((char= ch #\b) '(2 2))
((char= ch #\c) '(2 2 2))
((char= ch #\d) '(3))
((char= ch #\e) '(3 3))
((char= ch #\f) '(3 3 3))
((char= ch #\g) '(4))
((char= ch #\h) '(4 4))
((char= ch #\i) '(4 4 4))
((char= ch #\j) '(5))
((char= ch #\k) '(5 5))
((char= ch #\l) '(5 5 5))
((char= ch #\m) '(6))
((char= ch #\n) '(6 6))
((char= ch #\o) '(6 6 6))
((char= ch #\p) '(7))
((char= ch #\q) '(7 7))
((char= ch #\r) '(7 7 7))
((char= ch #\s) '(7 7 7 7))
((char= ch #\t) '(8))
((char= ch #\u) '(8 8))
((char= ch #\v) '(8 8 8))
((char= ch #\w) '(9))
((char= ch #\x) '(9 9))
((char= ch #\y) '(9 9 9))
((char= ch #\z) '(9 9 9 9))
((char= ch #\Space) '(0))
(t
(error "Incorrect Charater"))))


(defun str2pad (str)
(cond ((= (length str) 0) '())
(t
(append
(mobilepad (char str 0))
(str2pad (subseq str 1 (length str)))))))

;; 코드 길이 구하기
(defun padlength (str)
(length (str2pad str)))


사이냅 소프트 입사문제 1번의 간략해

정확한 해는 될 수 없다. 왜냐하면 어떤 형태의 도형이 되었는지 까지는 체크하지않고, 단순히 이것이 몇 점 도형이 되는지 체크하는 것이기때문..
그리고 점의 순서역시 순서대로 찍어야만 되도록 프로그래밍 되었기때문에 예제 입력에 정확히 일치하지 않는 결과만 나온다.

어쨌거나 시작~

해당하는 그리드의 n번째 시작줄은 A(n) = A(n-1) + n 이 된다. 이때 A(0) = 1 이 된다.
또한 같은 밑변인 경우 같은 n번째 항의 값이어야하고,  같은 빗변인 경우 시작줄 첫번호나 끝번호에서 떨어진 길이가 동일해야한다.
따라서 모든 수가 빗변, 밑변을 이룰 수 있어야하고 그리드 안에 이동가능한 모든 수는 단 한번만 나와야 실제로 도형이 나온다.
머.. 빗변과 밑변의 종류와 수를 조합해서 도형을 찾아내는 방법이 있긴한데.. 거기까지는 패쓰.. 아침에 부랴부랴 만들었더니 엉망이네.. =.=



;; n번째 항 찾기..
(defun get-nth (n)
(cond ((= n 0) 1)
(t
(+ n (get-nth (- n 1))))))

;; x값이 포함될 n번째 항을 찾기
(defun find-least-n (x)
(labels ((find-iter (i)
(cond ((> (get-nth i) x) i)
(t
(find-iter (+ i 1))))))
(find-iter 0)))

;; x 값의 수평 위치를 알아오기
(defun find-pos (x)
(let ((n (find-least-n x)))
(- x (get-nth (- n 1) ))))

;; x 값의 뒤쪽에서의 위치를 알아오기
(defun find-rev-pos (x)
(let ((n (find-least-n x)))
(- x (- (get-nth n) 1))))

;; 같은 Bottom Line 인지 검사
(defun same-bottom (a b)
(= (find-least-n a) (find-least-n b)))

;; 같은 빗변인지 검사
(defun same-slope (a b)
(or (slash-slope a b) (backslash-slope a b)))

;; / 빗변인가?
(defun slash-slope (a b)
( = (find-pos a) (find-pos b)))

;; \ 빗변인가?
(defun backslash-slope (a b)
(= (find-rev-pos a) (find-rev-pos b)))

;;밑변의 모든 점
(defun bottom-point (a b)
(cond ((= a b) (list a))
(t
(cons a (bottom-point (+ a 1) b)))))
;;밑변의 모든 점
(defun bottom-point-rev (a b)
(reverse (bottom-point b a)))

;; / 모양위의 모든 점
(defun slash-point (a b)
(cond ((= a b) (list a))
(t
(cons a (slash-point (+ (find-least-n a) a) b)))))

;; / 모양이지만 a > b 인 경우
(defun slash-point-rev ( a b)
(reverse (slash-point b a)))

;; \ 모양위의 모든 점
(defun backslash-point (a b)
(cond ((= a b) (list a))
(t
(cons a (backslash-point (+ (find-least-n a) a 1) b)))))

;; \ 모양이지만 a > b 인 경우
(defun backslash-point-rev (a b)
(reverse (backslash-point b a)))


;; 점의 집합을 순환 집합으로 만들기
(defun make-circular (lst)
(append lst (list (car lst))))

;; 순환 집합을 각각의 시작점과 끝점의 집합으로 만들기
(defun make-point-set (lst)
(cond ((> 2 (length lst)) '())
(t
(cons (cons (car lst) (cadr lst))
(make-point-set (cdr lst))))))

(defun point-on-line (a b)
(or (same-bottom a b)
(same-slope a b)))

;; 각 점이 모두 라인이 되는지 있는지 검사
(defun all-point-on-line (lst)
(cond ((null lst) T)
(t
(and (point-on-line (caar lst) (cdar lst))
(all-point-on-line (cdr lst))))))

;; 두 점이 이루는 방식에 따라 중간 수를 가져오는 함수
(defun get-path-between (a b)
(cond ((= a b) '())
((> a b)
(cond ((same-bottom a b) (bottom-point-rev a b))
((slash-slope a b) (slash-point-rev a b))
((backslash-slope a b) (backslash-point-rev a b))))
(t
(cond ((same-bottom a b) (bottom-point a b))
((slash-slope a b) (slash-point a b))
((backslash-slope a b) (backslash-point a b))))))

(defun tuck (lst)
(reverse (cdr (reverse lst))))

;; 각 선이 이루는 모든 수를 가져온다.
(defun get-path (lst)
(cond ((null lst) '())
(t
(append
(tuck (get-path-between (caar lst) (cdar lst)))
(get-path (cdr lst))))))

(defun get-path-lst (lst)
(cond ((null lst) '())
(t
(cons
(tuck (get-path-between (caar lst) (cdar lst)))
(get-path-lst (cdr lst))))))
;; 해당하는 점의 집합이 모두 별개의 수로 되어 있을 것
(defun differ? (n lst)
(cond ((null lst) t)
(t
(and (not (= n (car lst)))
(differ? n (cdr lst))))))

(defun all-differ? (lst)
(cond ((null lst) t)
((atom lst) t)
(t
(and (differ? (car lst) (cdr lst))
(all-differ? (cdr lst))))))

;; 검사 함수 : 해당하는 점들이 서로 겹치지 않는 도형을 이룰 수 있는지..
(defun avail? (lst)
(let ((pset (make-point-set (make-circular lst))))
(and
(all-point-on-line pset)
(all-differ? (get-path pset)))))

;; 변의 길이가 동일한가?
(defun same-length? (lst)
(let ((pset (get-path-lst (make-point-set (make-circular lst)))))
(let ((lenlst (mapcar #'length pset)))
(not (differ? (car lenlst) (cdr lenlst))))))

;; 최종 검사 함수 : 해당하는 점들이 도형이며, 변의 길이가 동일한가?
(defun check-shape? (lst)
(if (and (avail? lst)
(same-length? lst))
(print-shape lst)
(format t "~A is not acceptable shape." lst)))

(defun print-shape (lst)
(format t "~A is accepable ~A pointed shape." lst (length lst)))


2009. 4. 6.

Twitter, 성능상의 문제로 Scala를 부분 도입..

근래에 Twitter 에 대해 많은 관심이 집중되는 모양이다. 구글이 인수하려고 한다는 얘기도 들리고 있고, 계속해서 접속량도 늘어나고 있다.

Twitter 는 ROR(Ruby on Rails)의 가장 대표적인 레퍼런스 사이트 중 하나이다. 루비야 그동안 알려진대로 유연성에, 깔끔함에.. 암튼 예쁜 언어임에는 틀림없다. 문제는 사이트가 거대해지면서 생기는 스크립트 언어 특유의 문제점이 곳곳에서 나타나고 있다는 점.

먼저 장기간에 걸쳐 안정적인 서비스를 하는데 있어서, 어느 정도 약점을 보이는 모양이다. Twitter 개발진에서도 이 점을 우려하고 있는 바... JVM의 최적화정도나 안정성에 상당히 긍정적인 시선을 보이는 것 같다.
결국 Twitter에서는 기존의 Rails 프레임 웍을 운영하면서, 적절한 서버쪽 부분의 재구성을 필요로하고 있었는데, Scala가 잘 맞아떨어진 모양이다. 자세한 사항은 Twitter on Scala를 참조하기 바란다.

작년부터 Scala나 Clojure에 대한 인기가 조금씩 나타나는 모양이다.
몇 년간 Perl, Python, Ruby 같은 언어 자체의 뛰어남을 보이는 분야가 강세였다면, 앞으로는 안정된 VM위에서 잘 운영될 수 있는 함수형 언어가 기존의 스크립트와 잘 어울리면서 성능을 보장해줄 수 있는 시스템이 많이 각광을 받을 것으로 생각한다.

C/C++로 엄청난 퍼포먼스를 내는 것이 필요로 할 수도 있고, 반면에 유연함과 안정성을 겸비한 VM위에서 동작하는 모델이 우위를 차지하는 부분이 앞으로 지속적으로 모습을 드러낼 것 같다. 개인적으로는 현재 1.0이 나온 Parrot 기반으로 C/C++과 Perl, 그리고 다른 많은 언어들의 조화로 JVM못지않은 훌륭한 시스템이 나와줬으면 하는 바램이다.

그런 의미로. Perl 만세~~ Long Live C/C++~~
마지막으로 함수형 언어는 꼭 공부해둡시다~~

2009. 3. 30.

아고라 인구 떡밥 물어보기..

"아고라: 인구가 줄면 과연 문제인가" 라는 글을 읽고서 잠깐이나마 감상을 적어본다.

글쓴이의 결론을 떼어내면 다음과 같다.

결론은, 식량 자급 수준까지는 인구감소를 당연히 받아들이고 고령화 문제도 거기서 논의의 출발점을 찾아야 합니다.

나의 결론은 인구감소는 당연히 받아들일 문제가 아니며, 인구와 식량의 문제는 다른 기술적인 문제로 해결할 수 있어야 한다. 라는 것이다.
꿈같은 이야기일지 모르겠지만, 해수를 농수로 이용해 농사를 짓고, 목장대신에 공장에서 고기가 나올 시대가 멀지 않을 것이라고 생각한다. 그렇다면 경지문제나 목축으로 생기는 각종 문제는 상당히 해결이 가능할 것이라고 생각한다.

그 앞에 몇가지 논쟁거리를 던졌는데...

  1. 인구가 많기 때문에 제대로 된 취급을 받지 못한다. 고로 적은 인구가 되어야 사람을 귀하게 여긴다
  2. 인구가 늘어나면 식량의 자급자족이 불가능해지고, 이것이 지나치게 되면 기아에 직면할 수 있다
  3. 앞으로 1대 99의 사회가 되는데, 많은 인구로 인해서 비정규직이 증가될 가능성만 늘어난다.
라고 떡밥을 던져주셨다.

떡밥을 물었으니 나름대로 반대 의견을 던져보는 것이 인지상정.. 반론을 위한 반론이랄까.. 한번 물어봤다.

  1. 인 구가 많다고 해서 대우가 불공평하다면, 근본적인 사회의 의식을 의심해봐야한다. 가장 중요한 인권과 사회가 가져야하는 구성원에의 책임이 제대로 형성되어있지않다면 아무리 인구가 적다해도 사람을 귀하게 여기지는 않는다. 과거 사회에서 인구가 적다고, 사람을 귀하게 여겼는가하면 꼭 그렇지만도 않다. 사회 혼란기에서 안정기로 접어드는 시점에서는 인간에 대한 가치를 높게 매기지만(세금 징수의 대상이던, 국방력의 대상이던.. ), 안정기가 지속될 수록 다시 상대적으로 하락하는 경우도 왕왕 나온바있다.
  2. 식량의 자급자족 문제는 지속적인 기술 개발을 통해 향상되어가고 있다. 줄기세포를 이용한 육류 재배바닷물에서의 곡물 재배같은 기술개발을 통해, 새로운 식료원이 개발되고 있다. 빠른 시일안에 경제적인 손익분기점을 넘기는 힘들겠지만, 한세대안에 양산화가 가능할 것으로 생각한다.
  3. 향 후 사회는 후기 정보화 사회로 이전될 가능성이 매우 높다. 이 사회에서는 개인의 뛰어남 보다는, 개인이 속한 집단의 뛰어남이 훨씬 중요하다. 위키피디아 같은 집단 지성앞에 개인이 가지는 지식체계는 극단적으로 초라해질 수 밖에 없다. 즉, 앞으로 강력한 지식체계를 자랑할 수 있는 개인보다는, 수많은 개인이 필요한 집단 시스템이 향후 사회의 발전을 가져올 수 밖에 없다고 생각한다.
나의 글 역시 논리와는 담을 쌓아버린 반론.. 아니 트집성 글이지만 말이다..

인구수라는 것은 앞으로 우리가 생각한 것 이상으로, 단순한 노동력으로의 환산가치를 넘어선 사회의 힘을 (국력이란 말은 상당히 싫어하기에..) 나타낼 수 있다는 것을 생각해보았으면 한다.

PS> 근데.. 왜 이런 글을 쓰는지는 모르겠네.. \.\

2009. 3. 26.

우리가 특별하지 않다면?

예전에 읽은 책에서 인류의 자존심에 상처를 준 3가지 학문적 결과물에 대한 내용이 있었다.

  1. 코페르니쿠스의 지동설 - 지구는 우주의 중심이 아니다.
  2. 다윈의 진화론 - 인간은 처음부터 존재한 것이 아니라, 오랜 진화의 산물이다.
  3. 프로이트의 정신분석학 - 인간은 비합리적이고 결정적인 존재이다.
바로 인간이 특별한 존재라는 것을 부정하는 내용들이다.

이번에 초끈이론에 대한 몇가지 글을 읽으면서 느낀 것은 우리 우주 역시 특별한 것이 아니라는 점이다. 빅뱅은 그나마 우리 우주를 아주 특별한 존재라고 생각할 수 있는 건덕지라도 조금 남겼지만 말이다.

아 직 이론상의 이야기이지만, 우리 우주는 아주 오래전부터 존재해왔으며, 어느날 갑자기 팽창해버린 결과물이다. 그리고 언젠가 다시 수축하게될 수 있고, 또 언젠가 다시 팽창해서 우주가 될 수 있을 것이다. 이런 우주역시 무한히 많이 존재할 가능성이 있으며, 각기 다른 물리법칙이 적용되고 있을 수도 있다고 한다.

길잃은 입자 또는 막의 충돌로 인해서 그냥 생겨버린 우주에서 백몇십억년 동안 진화된 결과물로 태양계와 지구가 생겨났고, 그리고 인류가 진화되서 나온.. 어떻게 보면 아주 흔하디 흔한 확률상의 결과물로 생겨난 것이 우리라면 과연 어떤 느낌이 들까?

고대로부터 내려온 수많은 신화는 지구와 인간이 아주 특별하게 생겨난 것처럼 이야기하면서, 나름 권위를 내세우지만, 어느 시간 갑자기 툭 만들어져버린 우주에서 충분한 시간만 들이면 생겨날 수 있는 확률상의 존재정도로 충분한 존재였다면 이 세상의 모든 종교는 그 의미를 잃어버릴지도 모르겠다. 그래서 몇몇 종교인들이 그토록 과학에 대해서 질색을 하는지도 모를 일이다. 이들은 자신이 특별하다고 믿었던 믿음이 어느 순간 산산히 부서질 때 느낄 그 공허감을 도저히 견딜 수 없을거다.

과학이 발전하면서 세상의 모습을 하나 둘 발견해가면, 끝에는 우리는 절대 특별하지 않은 존재이며, 존재할 확률이 있음으로해서 존재한 것뿐이라는 단순한 명제만을 남길거라 생각한다. (아니, 이미 다 나와버린건가?)

(갈곳이 없네.. 전혀 과학적인 글은 아니지만 걍 과학밸리로~~)

AliceSoft 社 게임을 Wine에서 해보기

Wine 설치이후로 이러저러한 게임을 해보다가 AliceSoft 의 게임을 얻게됐다.

사용하는 데탑이 리눅스였던 관계로 VirtualBox 에 설치해둔 XP로 게임을 하곤했는데 혹시나 Wine에서 바로 돌릴 방법이 없나 찾아보다가 한 시간정도 삽질해서 알아냈다.

일 부의 게임은 자체적인 스크립트 프로그램을 통해서 게임을 실행하는데 현재 4.5 버전까지 나왔고, 이전 3.5버전은 아마  스크립트 엔진 소스가 공개되었던 것으로 기억한다. 여기서 가능한 것은 이 스크립트 엔진을 통한 어드벤쳐형 게임들이다.

필요한 준비물은 다음과 같다.

  1. 설치할 게임디스크
  2. 윈도에 설치해두었던 레지스트리 파일
  3. 텍스트 에디터
1번은 두말할 나위도 없고.. 일단 해당하는 시디안에 GAMEDATA 디렉토리 내용을 통째로 들어서 디스크로 복사한다.

다음에 wine으로 regedit 프로그램을 열어서 2번의 파일을 레지스트리 내부로 가져온다.
AliceSoft 의 게임은 System.ini에 레지스트리 위치를 기록해두는데 HKEY_CURRENT_USER\Software\AliceSoft 에 대부분 위치한다. 원본 레지스트리 파일에서 이 부분을 통째로 내보내기를 통해 저장해두면 된다.

이제 텍스트 에디터에서 System.ini안에 일본어로 정해진 레지스트리 경로명, ain 파일 이름을 적절히 변경해준다.

마지막으로 Wine 에서 레지스트리를 열어 주어진 값을 살펴보면, 각종 파일의 이름을 기록해둔 것을 볼 수 있다.
이 파일을 적당히 영어로 변경하고, 마지막으로 복사해둔 파일 내용역시 해당하는 이름으로 맞춰서 변경한다.

레지스트리내 파일 이름과 실제 파일 이름이 매치만 되면 게임 실행에는 큰 어려움이 없다.

wine 에서는 게임 설치시에 일본어로 되어있는 파일 복사에 번번히 실패하게되는데 인스톨 디렉토리에 있는 setup.ini랑 gamedata 디렉토리에 있는 파일들을 적절하게 이름만 바꿔주면 아마 이런 과정없이 설치가 될 것으로 보지만.. 내 경우에는 이미 설치가 되어 있어서 약간 삽질을 해보았다.

2009. 3. 14.

오래된 기록 매체...

집에서 몇몇 잡동사니를 정리하다가 5.25인치와 3.5인치 디스켓 몇장이 굴러 나왔다. 예전에 MSX랑 386컴을 쓰던 시절의 게임이 나왔다. 내용물을 확인할 방법이 없어 빛바랜 라벨을 확인해보니  디스켓 게임(이스, D대시, 울티마 6등)과 유틸리티(코렐 드로우, PCTools등)였다. 아마도 어딘가에는 이 때 쓰던 각종 데이터가 들어있을 디스켓이 있을 것이다. 지금으로서는 확인이 불가능하지만...



386컴을 처음 샀을때가 92년이었다. 대학교 입학 선물로 엄청 큰 돈 들여 집에서 구매해주셨고, 덕분에 4년 내내 각종리포트랑 게임을 하면서 - 물론 바로 곧이어 등장한 486에 힘없이 떠밀리지만 - 컴퓨터쪽으로 밥을 먹게 되는 단초가 되었으니,추억이라면 훌륭한 추억이 될지도 모르겠다.

처음에 컴퓨터를 본 것이 국민학교-초등학교라는 말은 아직도 어색하다-5학년때, 친구집에서 본 삼성 SPC-1000 이었다. 이 기종에는 테이프 리더가 붙어 있어서, 각종 프로그램을 테이프에저장하고, 로드할 수 있었다. 지금보면 웃음밖에 안나오지만 당시에는 짧으면 5분에서 길분 한시간 넘게 기다려서 게임을 로딩하고,실행되는 것을 보면서 정말 엄청나게 즐거워했다. (물론 한시간 넘게 로딩해서 에러가 나면 다시 도루묵이 되버리는 바람에뒤집어지는 상황은 불유쾌하지만 말이다.)

당 시에 사용하던 테이프랑 롬팩(가장 처음 사용해봤던 컴퓨터는 대우CPC-800이던가로 기억한다. MSX 기종이었다)은 아직도 집 한켠에 자리 잡고 있지만, 이 데이터는 이제는 돌려볼 수가없다. 미디어가 맛이 가는 것은 둘째치고, 테이프나 롬팩을 읽을 수 있는 장비가 내게는 존재하지 않는 것이다.

불과 20여년 만에 특정 기록매체를 확인할 방법이 거의 사라져 버린 것이다.
만약 집에 테이프, 롬팩, 5.25인치 디스켓, 3.25인치 디스켓을 가지고 있다 하더라도, 이 매체 안의 데이터를 확인해보려면 꽤나 불편한 과정을 거쳐야한다거나 어쩌면 아예 볼 수 있는 기기가 사라져버릴 수도 있다.

플 로피 디스켓이 나오면서 테이프 저장매체는 자리를 잃었고, 이번에는 CD가, 곧이어 DVD가 계속 나오게 되었고, 마침내 현재에는USB같은 매체가 주류를 이루게 되었다. - 하드 디스크는 아직 그 자리를 굳건히 지키고 있지만 SSD같은 매체가 위협을 하고 있다.

지금 가지고 있는 매체안의 데이터를 얼마나 오래 사용할 수 있을까?
데이터가 파괴될지 모른다는 위험은 둘째치더라도, 당장 지금 저장한 데이터를 읽을 기기가 언젠가 사라질지도 모른다는, 정말 상상할 수 없는 상황이 언제든지 벌어질 수 있다. 예전에 각종 데이터를 저장해뒀던 플로피 데이터를 현재 사용할 수 없는 것처럼 - 매체의 데이터 보존성은 차치하더라도 - 지금 우리가 사용하는 USB데이터는 수십년, 아니 수년안에 읽을 수 없을 미래의 모습은 충분히 가능할 것이다.

얼 마나 많은 데이터를 모으고 축적할 것인가에 대해서는 잘 알면서도, 우리가 현재 사용하는 데이터를 어떻게 남겨야 시간이 지난 후에도 이 데이터를 사용할 수 있을 것인가에 대한 논의는 너무나 미약한 것 같다. 과연 우리 자손이 우리가 소소하게 남기는 각종 데이터를 그 때에도 충분히 참고할 수 있을까?

어떤 사람들은 인터넷을 통해 충분히 수많은 데이터를 보존할 수 있다고 말할지도 모른다. 하지만 서비스하는 회사가 사라지는 순간, 수많은 데이터는 너무나 어이없이 사라지는 모습을, 너무나 흔하게 보고 있다. 추억이 어린 사진, 치기 어리지만 순순했던 시기의 글들, 당시에는 너무나 치열하게 고민했던 생각들... 이런 기록을 10년, 20년이 지나 누군가가 볼 수 있을지는 여전히 불확실하다. 네띠앙이나, 하이텔같은 서비스가 접히면서 얼마나 많은 기록들이 접근하기 불가능하게 되었는지는 상상도 할 수 없을 정도니까..

우리가 가진 기록매체를 얼마나 신뢰할 수 있을까? 현재 나오고 있는 출판물이라고 해도, 그 보존기간은 수십년을 넘기가 어렵다. 수천년전에 진흙에 쐐기로 새기고, 구워서 남긴 고대의 기록이나, 조상들이 남긴 서책들앞에, 한순간에 불면 날아갈 것 같은 우리의 기록은 왠지 초라한듯한 생각이 든다.

2009. 1. 18.

Windows 7 to Ship in Multiple Versions?

Tom's hardware의 뉴스에서 읽었다.

The five versions of Windows 7 are as follows: Starter, Home Basic, Home Premium, Business and Ultimate.

이렇다는 얘기다. 현재 나와있는 베타 버전은 Ultimate 버전에 준한다고 하는데.. 다른 여타의 버전에서는 여러 가지 기능이나 차단막이 쳐질 것 같은 분위기인데...

2000, XP, 2003, Vista같은 경우에 여러가지 버전이 있었는데 개발하면서 더 까탈스럽기만 했던 것 같다. Program Files에 대한 접근 문제가 불거졌던 XP 의 경험같은 것이 아직도 생생한데 , 다섯가지 버전 설치에 맞는 가이드라인을 잡으려면 꽤나 고생할 듯한 기분이 든다.

누구를 위한 버전 분리인지는 두고 봐야할 듯 싶다..

2009. 1. 4.

LISP 웹 플밍을 위한 기초설정

기본적으로 CGI모듈로 동작하는 LISP 스크립트를 웹에서 사용하려면 몇가지 작업을 해야한다.

1. 웹서버/CGI Charset 세팅

먼저 아파치 웹서버에서 디폴트 캐릭터셋을 설정한다. 이 파일은 /etc/apache2/conf.d/charset 에 있다.

#AddDefaultCharset UTF-8

이라고 되어 있는 내역을 주석을 지우고(# 문자를 없애면 된다) 원하는 Charset으로 설정한다.

다음에는 CGI Charset을 수정해야한다.
LISP 스크립트 맨 첫째줄에 있는

#!/usr/bin/clisp




#!/usr/bin/clisp -E utf-8


로 수정해 준다.

2. 파일 로딩시 Charset 세팅

LISP파일을 로딩할 때 캐릭터 셋을 설정해준다.

(load "test.lsp" :external-format charset:utf-8)

이것을 해주지 않는다면 로딩시 중간이 잘리거나, 에러가 날 수 있다.