{"id":5599,"date":"2018-12-16T14:00:43","date_gmt":"2018-12-16T14:00:43","guid":{"rendered":"http:\/\/writeasync.net\/?p=5599"},"modified":"2018-12-15T15:44:15","modified_gmt":"2018-12-15T15:44:15","slug":"building-an-adventure-game-part-2","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=5599","title":{"rendered":"Building an adventure game: part 2"},"content":{"rendered":"<p>Previously, I recounted <a href=\"http:\/\/writeasync.net\/?p=5597\">day 1 of my adventure game project<\/a>. Let&#8217;s continue.<\/p>\n<h2>Day 2<\/h2>\n<p>This was a short day.<\/p>\n<p>First I observe that it isn&#8217;t really necessary for the caller of <code>Game<\/code> to pass the <code>MessageBus<\/code>, so I change that. <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/5dfd996a5b1e2489f4e6642e924a37782b322d3f\">Commit.<\/a> Then I determine that the &#8220;App&#8221; project is really better described as a &#8220;Sample&#8221; use case and change the names accordingly. <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/67dae8aa49347e49effe233dcf33b2402d19b64d\">Commit.<\/a><\/p>\n<h2>Day 3<\/h2>\n<p>Every text adventure game needs to interpret user input so I get to work building the <code>SentenceParser<\/code>. I start with <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/b442c02ee6bfb8ed29d2d39468ac891ffca52ca6\">a simple test<\/a> based around the existing <code>InputMessage<\/code> protocol already established by the <code>TextConsole<\/code>. I quickly decide that the output <code>SentenceMessage<\/code> should <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/6821520bd9ab114c33ab1594d2514c4e883768a5\">split the input into a single verb and noun<\/a>. Since the parser subscribes to input messages on construction, I make sure it is a good citizen by <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/6977bc7b3eae35d25beb7192a62245fa8b830aca\">implementing the Dispose pattern to unsubscribe<\/a>. Finally, I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/1c337095aa2e67a3e4f74499cf71e6b7a2c7dbad\">incorporate the code I have so far<\/a> into the sample <code>Game<\/code>.<\/p>\n<h2>Day 4<\/h2>\n<p>So far the <code>Game<\/code> can accept a very small set of commands. Unfortunately, &#8220;quit&#8221; isn&#8217;t one of them, so there is no way to exit. I change this by first <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/558f97edf9a6f1ed6757352ba0172466454c49e9\">accepting a <code>CancellationToken<\/code> in <code>TextConsole<\/code><\/a>. Then I make it <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/20e8d37388c9deeb1c36f98533bf1f4a3efffb13\">actually respond to cancellation requests<\/a>. After <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/19d1c7e52891545a11b13cb2223d6a09b8e845d7\">a bit of refactoring<\/a>, I add some <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/13e1579e65348365b9229277b8d989812f5ffb33\"><code>Game<\/code> code to handle unknown verbs<\/a>. As a last step, I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/72fc4d9c1785d8f2c6234cac0a94fe38901e7c7a\">add the &#8220;quit&#8221; command<\/a>, thus completing the day&#8217;s work.<\/p>\n<h2>Day 5<\/h2>\n<p>Today is all about <code>Words<\/code>. So far the game only knows how to handle exact matches for verbs and ignores nouns. Let&#8217;s start implementing the concept of a synonym <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/bce6ddd9896a3fd4b35a2a41d475d74f7902bf19\">using a <code>Word<\/code> class<\/a> to track the &#8220;actual&#8221; word entered by the user as well as the &#8220;primary&#8221; word that we match it against. The <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/06c513f0f1eee847e8973487c2f0337436671599\">next test shows this in action<\/a> by accepting &#8220;see,&#8221; &#8220;inspect,&#8221; or &#8220;examine&#8221; as synonyms for the primary verb &#8220;look.&#8221; Several test and implementation cycles later, I am ready to <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/1f6e22bb26165588a943b38feea4fc00cc343765\">incorporate a few words into the game<\/a> &#8212; &#8220;greet&#8221; (&#8220;hello,&#8221; &#8220;hi&#8221;) and &#8220;quit&#8221; (&#8220;exit&#8221;). Since I&#8217;m on a roll, I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/f22b62d13406d6a561def10854a5b2ded166fd75\">add &#8220;take&#8221; (&#8220;get&#8221;) to the game<\/a> as well. I end the day by <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/7fbad8d0ca6240c2bca8dd2b5e3727ea6f2e6431\">extracting the primary words into a Verb class<\/a> that I later <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/7d0f4eff7b93694da75291b46df7ad43bc074a2d\">promote to the top level<\/a>.<\/p>\n<h2>Day 6<\/h2>\n<p>An adventure game with no rooms to explore is hardly a game at all. Thus I begin building an abstract <code>Room<\/code> class <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/590414bb6f16bd84c09fb58e5177c1cd6d6b027a\">with methods to Enter and Register verbs<\/a>. My plan with this is to activate the behavior of the room (i.e. allow it to respond to commands, etc.) in <code>Enter()<\/code> and unregister\/unsubscribe everything in <code>Leave()<\/code> <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/77f0af89e1b524a59f44d1d2538cd7c0488f789f\">which I implement next<\/a>. Note that I am explicitly not using the Dispose pattern here because Dispose implies destruction; rather, I want rooms to live on even after the player has left since reentry is always possible, hence the Enter\/Leave semantics. With enough of the behavior fleshed out, I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/44de275c6efe6289ee1086b284a9935ee31574af\">add a single <code>MainRoom<\/code> to the sample game<\/a> where I move all the verb handling logic so far.<\/p>\n<h2>Day 7<\/h2>\n<p>Looking at the code today, I don&#8217;t like how the <code>TextConsole<\/code> does double duty as a console processor and an input loop. I begin to <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/590a13cded904481c055cf969c128c151c98ea1b\">extract the loop behavior<\/a> into <code>InputLoop<\/code>. To avoid major rework of the code I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/27e941527cb1182964cc4af9cb0ab16393099628\">add an intermediary <code>NewLoop()<\/code> factory method<\/a> to <code>TextConsole<\/code>.<\/p>\n<p>Next, I look at <code>MainRoom<\/code> and decide that it doesn&#8217;t make a lot of sense for a specific room instance to handle a global message like &#8220;quit.&#8221; As I start to extract this behavior, I realize that there is a flaw. My initial approach involves subscribing to the <code>SentenceMessage<\/code> in the Game&#8217;s <code>Run()<\/code> method to respond directly to the &#8220;quit&#8221; verb. Unfortunately, <code>MainRoom<\/code> is also subscribed so I end up with two responses &#8212; the real quit behavior, followed by the unknown verb behavior of Room (&#8220;I don&#8217;t know what &#8216;quit&#8217; means&#8221;). I don&#8217;t commit anything because the code doesn&#8217;t yet work.<\/p>\n<h2>Day 8<\/h2>\n<p>I am determined to solve the previous day&#8217;s problem and I have a plan. Instead of only allowing <code>void<\/code> (<code>Action<\/code>) methods for <code>MessageBus<\/code> subscribers, I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/52f65dd351c74e773c2e2675ffe0adc51ce4e44c\">add support for <code>bool<\/code> return values<\/a>. In the spirit of not breaking things, this support does not replace the existing code but rather allows a second type of subscriber. Eventually I make it so that <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/cc4cefcb761476540bca272e81cba2999bfb9b42\">returning &#8216;true&#8217; stops subsequent subscribers from processing<\/a>. Now I can <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/35acc2fc7117cfddbfb716009c27a83ff7508116\">actually implement the &#8220;quit&#8221; feature<\/a> from yesterday by having the quit handler return <code>true<\/code> if it detects the &#8220;quit&#8221; verb and <code>false<\/code> otherwise, allowing the room to process everything else. Finally, I <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/212445970f296bb0903a1e06350885a750a3a618\">extract this pattern into <code>QuitHandler<\/code><\/a> and <a href=\"https:\/\/github.com\/brian-dot-net\/AdventureGame\/commit\/75ef5cc23f1eff08d42005188e4d1b2c9b2c5425\">use it in the Game<\/a>.<\/p>\n<p>At this point I have a game with a single room that understands a few commands and allows you to quit. It&#8217;s not much but it&#8217;s something!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Previously, I recounted day 1 of my adventure game project. Let&#8217;s continue. Day 2 This was a short day. First I observe that it isn&#8217;t really necessary for the caller of Game to pass the MessageBus, so I change that.&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-5599","post","type-post","status-publish","format-standard","hentry","category-design","category-tdd"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5599","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=5599"}],"version-history":[{"count":3,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5599\/revisions"}],"predecessor-version":[{"id":5602,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/5599\/revisions\/5602"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=5599"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=5599"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=5599"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}