<< first < prev 1 2 3 4 5 6 7 8 9 next > last >> Creating Special Actions...
Creating Special Actions in Our Game

We have only one more thing to do now and our game will be complete: Add some special actions to the game that the player has to do to win in the game. The first command will let the player weld the chain to the bucket in the attic:

(def chain-welded false)

(defn weld [subject object]
  (cond (and (= location 'attic)
             (= subject 'chain)
             (= object 'bucket)
             (have? 'chain)
             (have? 'bucket)
             (not chain-welded))
        (do (def chain-welded true)
            '(the chain is now securely welded to the bucket -))
        :else '(you cannot weld like that -)))

So first we created a new global variable that lets us tell whether we've done this action already. Next, we create a weld function that makes sure all the right conditions are in place for welding and lets us weld.


Let's try our new command:

(weld 'chain 'bucket)
user=> (weld 'chain 'bucket)
(you cannot weld like that -)

Oops... we're don't have a bucket or chain, do we? ...and there's no welding machine around... oh well...

Now let's create a command for dunking the chain and bucket in the well:

(def bucket-filled false)

(defn dunk [subject object]
  (cond (and (= location 'garden)
             (= subject 'bucket)
             (= object 'well)
             (have? 'bucket)
        (do (def bucket-filled true)
            '(the bucket is now full of water))
        :else '(you cannot dunk like that -)))

Now if you paid attention, you probably noticed that this command looks a lot like the weld command... Both commands need to check the location, subject, and object - But there's enough making them different enough so that we can't combine the similarities into a single function. Too bad...

...but since this is Lisp, we can do more than just write functions, we can cast SPELs! Let's create the following SPEL:

(defspel game-action [command subj obj place & args]
  `(defspel ~command [subject# object#]
     `(spel-print (cond (and (= location '~'~place)
                             (= '~subject# '~'~subj)
                             (= '~object# '~'~obj)
                             (have? '~'~subj))
                        [email protected]'~args
                        :else '(i cannot ~'~command like that -)))))

Notice how ridiculously complex this SPEL is - It has more weird quotes, backquotes, commas and other weird symbols than you can shake a list at. More than that it is a SPEL that actually cast ANOTHER SPEL! Even experienced Lisp programmers would have to put some thought into create a monstrosity like this (and in fact they would consider this SPEL to be inelegant and would go through some extra esoteric steps to make it better-behaved that we won't worry about here...)

Game Action

The point of this SPEL is to show you just how sophisticated and clever you can get with these SPELs. Also, the ugliness doesn't really matter much if we only have to write it once and then can use it to make hundreds of commands for a bigger adventure game.

Let's use our new SPEL to replace our ugly weld command:

(game-action weld chain bucket attic
   (cond (and (have? 'bucket) (def chain-welded true))
              '(the chain is now securely welded to the bucket -)
         :else '(you do not have a bucket -)))

Look at how much easier it is to understand this command - The game-action SPEL lets us write exactly what we want to say without a lot of fat - It's almost like we've created our own computer language just for creating game commands. Creating your own pseudo-language with SPELs is called Domain Specific Language Programming, a very powerful way to program very quickly and elegantly.

(weld chain bucket)
user=> (weld chain bucket)
(you do not have a chain -)

...we still aren't in the right situation to do any welding, but the command is doing its job!


Next, let's rewrite the dunk command as well:

(game-action dunk bucket well garden
             (cond chain-welded 
                   (do (def bucket-filled true)
                       '(the bucket is now full of water))
                   :else '(the water level is too low to reach -)))

Notice how the weld command had to check whether we have? the subject, but that the dunk command skips that step - Our new game-action SPEL makes the code easy to write and understand.


And now our last code for splashing water on the wizard:

(game-action splash bucket wizard living-room
             (cond (not bucket-filled) '(the bucket has nothing in it -)
                   (have? 'frog) '(the wizard awakens and sees that you stole
                                       his frog -
                                       he is so upset he banishes you to the
                                       netherworlds - you lose! the end -)
                   :else '(the wizard awakens from his slumber and greets you
                               warmly -
                               he hands you the magic low-carb donut - you win!
                               the end -)))
Getting the Donut

You have now written a complete text adventure game!

Click HERE for a complete walkthrough of the game,

Click HERE for a copy of the source code you can copy & paste into your Clojure prompt in a single step.

In order to make this tutorial as simple as possible, many details about how Lisp works have been glossed or fudged over, so let's look at what those details are...

<< first < prev 1 2 3 4 5 6 7 8 9 next > last >> Creating Special Actions...