2009. 5. 22.

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장까지는 어드벤쳐 게임이 어떤 것인지 대충 맛일 보는데 유용하니 한번 쭈욱 훑어본다.