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 을 계속하나부다...