{"id":5605,"date":"2018-12-30T14:00:43","date_gmt":"2018-12-30T14:00:43","guid":{"rendered":"http:\/\/writeasync.net\/?p=5605"},"modified":"2018-12-23T02:59:11","modified_gmt":"2018-12-23T02:59:11","slug":"building-an-adventure-game-part-4","status":"publish","type":"post","link":"https:\/\/writeasync.net\/?p=5605","title":{"rendered":"Building an adventure game: part 4"},"content":{"rendered":"<p>Welcome to the fourth and final installment of my adventure game saga. We made it <a href=\"http:\/\/writeasync.net\/?p=5603\">up to Day 15 last time<\/a>. Let&#8217;s finish this up now.<\/p>\n<h2>Day 16<\/h2>\n<p>I don&#8217;t like that the method to add an item to a room is called <code>Drop<\/code>. So <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/0cb38facad87381c0a704d7c5e8ed1467e3b3b63\">let&#8217;s fix that<\/a>. This will allow the later implementation of an actual <code>Drop<\/code> command, to give the user the ability to leave behind items currently in the inventory. Now <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/fcc87d78c170be0e3d0b50ab3205ed4be827f0cf\"><code>Drop<\/code> can be implemented<\/a>, which makes use of a new <code>InventoryDropMessage<\/code>. Again, we should give the item a chance to weigh in whether <code>Drop<\/code> is allowed, so we <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/b463652cd0913f874ac1dc429dc21ffa981ad564\">create a <code>DropCore<\/code> to be overridden for custom behavior<\/a>. Now we just need to <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/3552afd13cf990b8e838680b8f80c900903d3446\">support &#8220;drop&#8221; in the sample game<\/a> and make sure <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/3ce8844642691039e7e1d40431cb28b76fd61678\">&#8220;drop coin&#8221; resets the item&#8217;s status<\/a>. One more finishing touch to <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/994ff99f9a5f2724e5c1bee4db170e5b6341bf81\">print a useful message for &#8220;drop&#8221; with no noun<\/a> (with corresponding <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/c48836d6e17dd2658f7ae78f1f9fd943cf339f3b\">coverage in the sample game walkthrough<\/a>) and we are done for the day.<\/p>\n<h2>Day 17<\/h2>\n<p>Today I notice there is a bug in how &#8220;look&#8221; is handled. If you pick up an item and it goes into your inventory, you can no longer look at it! Again I&#8217;ve made the mistake of using too much imperative code in the <code>Room.Look<\/code> handler. I should instead use the ever-faithful MessageBus to <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/0d7eafd4153ba30847ef515349026c30a3982d0a\">send a <code>LookItemMessage<\/code><\/a> when the &#8220;look&#8221; verb is detected. Now I can <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/826246c7f68e986d89c806f5de0850c9e754cd43\">respond to this message in <code>Inventory<\/code><\/a>. With this and a few more small code updates, <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/280001b40ce2fc0be5315fe826bad6cf5633d1bb\">&#8220;look coin&#8221; now works in the sample game<\/a>.<\/p>\n<p>We have amassed quite a collection of game messages, and it&#8217;s time to evaluate them for consistency. I like the pattern of <code>LookItemMessage<\/code>, so I go ahead and change <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/f8d30478caaa5b3a5e24481886c88e82c840c1a5\"><code>InventoryDropMessage<\/code> to <code>DropItemMessage<\/code><\/a>, <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/e751e7a4485e9ada6cf4b7e4d9601878975428d8\"><code>InventoryAddedMessage<\/code> to <code>TakeItemMessage<\/code>, and <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/cb65670e8b948db0075210ef0678a9252259ff8c\"><code>InventoryRequestedMessage<\/code> to <code>ShowInventoryMessage<\/code><\/a>. While we&#8217;re at it, let&#8217;s also <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/908cfef122c08273274b215dc948a15c77a88998\">create a new namespace for all the messages<\/a> so that they can be grouped together. I hold off on moving <code>OutputMessage<\/code> until I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/1727d6bb30f9c3cc910c1c4d4835ef0c47d28d06\">add an <code>Item.Output<\/code> convenience method<\/a> &#8212; I hate it when you have to import a whole namespace just for one class, so this &#8220;fa\u00e7ade&#8221; will be a nice (albeit, small) improvement for the user experience.<\/p>\n<p>Next on the agenda is some overdue cleanup of <code>Item<\/code>. Up to this point, I got away with having a simple constructor for <code>Item<\/code> because the <code>MessageBus<\/code> instance was always passed to each method. But that&#8217;s the problem &#8212; passing the same instance to every method means we should just bite the bullet and <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/da2ce6c962105cbed359be568d9adb9ec0fe0b9c\">make an instance field and a new constructor for <code>Item<\/code> that requires <code>MessageBus<\/code><\/a>. Now we can stop accepting the <code>MessageBus<\/code> parameter in <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/efb7da3fe72ee622f54f7fd5e11ab55390ddcc9c\"><code>Take<\/code><\/a>, <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/a5d7fcedb5f7c852f2c597bad6b0209d3f42705e\"><code>Drop<\/code><\/a>, and <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/b4f3611dc4dd33d77f61a1416bdeb90277123318\"><code>Do<\/code><\/a>. Last (and probably least), I make <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/704c8e2f5660b6f48a5f2e707958c86423b9f3a6\">one small change to add an <code>Output<\/code> extension method to <code>MessageBus<\/code><\/a>; I rather like this micro-improvement\/simplification to the interface.<\/p>\n<h2>Day 18<\/h2>\n<p>Several days ago I added the concept of the <code>RoomMap<\/code> and connections between <code>Room<\/code> objects but did not implement any ability to actually go anywhere. This changes today! First, a simple warmup change to <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/16ea4b6c12b8e47d79502faa419f8a8832a8187a\">ensure map points can connect back to themselves<\/a> (essentially allowing cycles). There&#8217;s no reason why this shouldn&#8217;t work, and many adventure games explicitly use this mechanic (notably the King&#8217;s Quest series with their <a href=\"http:\/\/kingsquest.wikia.com\/wiki\/Magical_law_of_%22containment%22\">magical law of containment<\/a>). Now let&#8217;s <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/81a361081357b5fb4f5609874d352f0dddca93d0\">implement the <code>Go<\/code> method in <code>Room<\/code><\/a>. I already implemented the <code>GoMessage<\/code> well in advance, so now I can just <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/df2ecb1e5ab202bcf42ab1d2c414efdf316b53e8\">use it when processing <code>Go<\/code> for a specific direction of travel<\/a>.<\/p>\n<p>With the groundwork laid for movement, I can <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/2a4f526be26088b1d61509a58833e5534a2ac032\">implement a secondary room in the sample game<\/a>, which goes by the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Autological_word\">autonym<\/a> <code>AuxiliaryRoom<\/code>. To avoid inevitable duplication, I extract the core logic from <code>MainRoom<\/code> and <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/3bc7974ed8946751a7c142f1f7e0b13292b4c844\">push it up to RoomBase<\/a>. Now all current and future rooms can behave the same way for the standard set of commands (&#8220;go&#8221;, &#8220;look&#8221;, etc.). To end the day, I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/b8e2530b7e4ef2e9c0942455e32cba8f41196df1\">handle a few more error cases for &#8220;go.&#8221;<\/a><\/p>\n<h2>Day 19<\/h2>\n<p>The framework for the game is nearly complete, so today I spend time smoothing over a few rough patches. You can&#8217;t tell from the commits (which by their nature demonstrate only working functionality) but I kept running into problems when adding new verbs and nouns to the sample game. Invariably I would forget to register the word in the <code>WordTable<\/code>. To solve this problem once and for all, I change <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/92efc0d2f085b03057569db651683d1d9973c21f\"><code>Noun<\/code> to an instance class<\/a>, <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/90ad6293152ec110bd770617f8ee6ab980665173\">do the same for <code>Verb<\/code><\/a> and then implement a <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/dcd68c6ac62c0ecf39b05709e07f5606ae2a09c0\">reflection-based registration method for <code>Noun<\/code><\/a> followed by <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/42bdc834bbf25179a2bd2816398d62f04f61dfec\"><code>Verb<\/code> as well<\/a>. Now it is impossible to miss the registration step of a new noun or verb. I make one simple change before ending the day &#8212; <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/69d9d99d90f24145f1cff10df8ab405ee9148ff7\">adding a hint to the &#8220;look table&#8221; command<\/a>. It probably isn&#8217;t obvious to the user that moving the table is possible, so I want to help them out a bit here.<\/p>\n<h2>Day 20<\/h2>\n<p>Can you believe that we&#8217;re this far into the development of this project and there is still <a href=\"http:\/\/www.adventureclassicgaming.com\/index.php\/site\/features\/411\/\">no way for the player to die<\/a>? We will have to fix that. It seems logical to leverage the existing <code>QuitHandler<\/code> to allow for ending the game, but we can&#8217;t use it as is. Thus I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/c9c1e05c280b29cf650895fca97987e8b2a0ce6e\">start with a new <code>EndOfGame<\/code> class<\/a>. Unlike the (probably flawed) design of <code>QuitHandler<\/code> which attempts to parse the &#8220;quit&#8221; verb directly, <code>EndOfGame<\/code> uses a message-based protocol (<code>EndOfGameMessage<\/code>) to ultimately signal cancellation of the inner game loop. It also accepts a string of text to display when ending, which could be useful for giving the user a <a href=\"http:\/\/spacequest.wikia.com\/wiki\/SQ1_(AGI)_Deaths\">helpful description of how they met their doom<\/a>. To prepare for the replacement of <code>QuitHandler<\/code>, we need to <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/dca9d00700b5112201bb49e71dcc9dc448f77e50\">add an <code>End<\/code> method to <code>Room<\/code><\/a>. Now we can <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/58a860fe42355f72a7554d3315024e0a6f41e62f\">delete QuitHandler and use EndOfGame<\/a> in the sample game instead. To show off this new feature, we <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/98d501924953d4c5a37ef10c67250ad8980f091f\">add a death scene to the game<\/a>; it simply involves walking eastward off a cliff in <code>AuxiliaryRoom<\/code>. Since this is a terminal condition and a different way to end the game than merely quitting, we need a new <code>WalkthroughDieTest<\/code> to pair with our (now renamed) <code>WalkthroughQuitTest<\/code>.<\/p>\n<p>Looking at some of the other code, I don&#8217;t like how the <code>Coin<\/code> item has to keep track by itself of whether it has been taken or not. So let&#8217;s <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/98d501924953d4c5a37ef10c67250ad8980f091f\">implement a <code>Taken<\/code> flag on the <code>Item<\/code> directly<\/a>. Several TDD cycles later, we can now <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/f8828f9d43100949fdb3b6d31ff1b7a3d3a7d0e1\">remove the custom code from Coin and rely on the framework<\/a>. I&#8217;m also a bit annoyed by the need to pass the parent <code>Room<\/code> to the <code>Table<\/code> item, so I get to <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/c9f0579d238e321fffd0346961955ceea92b0a79\">work on a <code>RoomActionMessage<\/code> protocol<\/a> to handle sending commands to whatever room the player is in. With the addition of a <code>SendRoom<\/code> message on <code>Item<\/code>, I can now <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/7fc32581993be125c2ff27b7a27700e2187eb2e3\">remove the <code>Room<\/code> parameter from the <code>Table<\/code> constructor<\/a>. <a href=\"https:\/\/gamedev.stackexchange.com\/a\/27468\">Loose coupling FTW!<\/a><\/p>\n<p>I now identify one last feature before declaring &#8220;code complete&#8221;: the ability to remove an item. Most adventure games have actions that result in using up an item (e.g. <a href=\"http:\/\/www.ruths-study.com\/miscellaneous\/kingsquest\/kq3spells.html\">making potions in King&#8217;s Quest III<\/a>). So what we want is a way to delete an item from the <code>Inventory<\/code> or a <code>Room<\/code>. Let&#8217;s start with <code>Room<\/code>. There is already a <code>Take<\/code> method on <code>Items<\/code> that does what we want so I just <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/033daa141414354ccfaba0c38b87e5dcfe4965e7\">rename it to <code>Remove<\/code><\/a> to better pair with <code>Add<\/code>. To expose this functionality I now have to <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/565b98e6b26685e44aa07e1d621df9688f4b06a0\">add a public <code>Remove<\/code> method to <code>Room<\/code><\/a>, which just calls the corresponding method on <code>Items<\/code>. I plan to use this feature in the sample game, so let me set this up now by <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/96aa4f377328e49377c9378e8627a6abd087182a\">adding a hole in the wall to the <code>AuxiliaryRoom<\/code><\/a>. Now if the player looks at the wall, the hint &#8220;INSERT COIN&#8221; is given.<\/p>\n<p>For completeness I follow up <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/3847bb4910d198e5da0ede62ecb2f6a3598114dd\">with <code>Inventory.Remove<\/code><\/a>; again, it is basically a pass-through to the underlying <code>Items<\/code> method. As I did before with <code>Room<\/code> I also <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/135168b4b48992096688c94d2ff18ddd2dbcdbeb\">add an <code>InventoryActionMessage<\/code> to send generic commands to the <code>Inventory<\/code> instance<\/a>. At this point, I note that the <code>-ActionMessage<\/code> classes are functionally identical but for their parameter type, so I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/cb3b5256b2971aba625445dfb34e0bf1c1e7f8cc\">use generics to resolve the duplication<\/a>, creating an <code>ActionMessage&lt;T&gt;<\/code>.<\/p>\n<p>As a final step, I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/8b5b7c118b66608d9385810f1f1b3e0f4e3a765d\">add handling of the &#8220;insert coin&#8221; action to the sample game<\/a>. As you might expect, the user can insert the coin into the hole if and only if the <code>Inventory<\/code> contains the item. Once the action is processed, the <code>Coin<\/code> object is removed, never to be seen again. (In a &#8220;real&#8221; game, this action might move the plot along, e.g. by causing a special object to appear in a distant room which is necessary to solve the quest.)<\/p>\n<hr \/>\n<h2>Epilogue<\/h2>\n<p>Well, that is it! Twenty spare-time days of development has led to a usable, if barebones, text adventure game engine. It employs what we might consider the original object-oriented <a href=\"https:\/\/softwareengineering.stackexchange.com\/questions\/264697\/alan-kay-the-big-idea-is-messaging\">principles formulated by Alan Kay<\/a> which place most of the emphasis on messaging. Indeed, almost every time I went down the imperative\/procedural route, some time later I was forced to backtrack and create a message\/subscriber protocol instead.<\/p>\n<p>Probably the biggest takeaway for me was how message passing helped immensely in avoiding the typical <a href=\"https:\/\/stackoverflow.com\/questions\/5153580\/asp-net-create-custom-context-object\">god\/context object approach<\/a> you see in many other frameworks. Since you can never be sure how a user-defined item will interact with its environment, you might be compelled to <a href=\"https:\/\/stackoverflow.com\/questions\/156701\/dealing-with-global-data-structures-in-an-object-oriented-world\">pass around anything you might ever need to every call site<\/a>. I largely avoided this by making one trade-off &#8212; the use of the single <code>MessageBus<\/code> instance in most of the core classes (<code>Item<\/code>, <code>Room<\/code>, etc.). It is not a <a href=\"http:\/\/wiki.c2.com\/?ContextIndependence\">purely context-independent<\/a> approach by any means (is such a thing truly possible in any nontrivial system?), but it minimizes the explicit knowledge needed <a href=\"http:\/\/www.inf.ed.ac.uk\/teaching\/courses\/inf1\/op\/Tutorials\/2008\/crc.html\">between collaborators<\/a> and converges most interactions toward <a href=\"http:\/\/www.codemanship.co.uk\/parlezuml\/blog\/?postid=934\">one core reusable abstraction<\/a>.<\/p>\n<p>Thanks for joining me on this journey. See you next year!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Welcome to the fourth and final installment of my adventure game saga. We made it up to Day 15 last time. Let&#8217;s finish this up now. Day 16 I don&#8217;t like that the method to add an item to a&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[91,41],"tags":[],"class_list":["post-5605","post","type-post","status-publish","format-standard","hentry","category-design","category-tdd"],"_links":{"self":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5605","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5605"}],"version-history":[{"count":5,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5605\/revisions"}],"predecessor-version":[{"id":5612,"href":"https:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5605\/revisions\/5612"}],"wp:attachment":[{"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5605"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5605"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5605"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}