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 />



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