{"id":210,"date":"2014-04-21T23:17:32","date_gmt":"2014-04-21T23:17:32","guid":{"rendered":"http:\/\/mjdarby.net\/blog\/?p=210"},"modified":"2014-04-21T23:17:32","modified_gmt":"2014-04-21T23:17:32","slug":"rogue-detective-part-2-curses","status":"publish","type":"post","link":"http:\/\/mjdarby.net\/blog\/2014\/04\/21\/rogue-detective-part-2-curses\/","title":{"rendered":"Rogue Detective Part 2 &#8211; Curses!"},"content":{"rendered":"<p>Last time, we covered the rather ridiculous choices that led up to my decision to write a roguelike from scratch. Now, we&#8217;ll cover the first steps in the creation of Rogue Detective, and indeed, most games I write.<\/p>\n<p>After the standard step of creating a <a title=\"Rogue Detective\" href=\"https:\/\/github.com\/mjdarby\/RogueDetective\/\">new repository<\/a> for source control comes the creation of the first source file. This is usually the most daunting part of development: Opening up your editor and seeing a completely blank file that needs to be populated. Step one: Save this blank file as &#8216;main.py&#8217;. Great, we&#8217;re half-way to finishing the game, but there&#8217;s no time for a break quite yet.<\/p>\n<p>I already knew we&#8217;d be using the <a title=\"Ncurses\" href=\"http:\/\/www.gnu.org\/software\/ncurses\/\">curses<\/a> library, so a good first step would be to import it. Additionally, I wrote a barebones do-nothing main function and uses a curses utility function to call it. At this point, here&#8217;s the totality of what I had:<\/p>\n<p><pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">\r\nimport curses\r\n\r\ndef main(stdscr):\r\n    pass\r\n\r\nif __name__ == &#039;__main__&#039;:\r\n    &quot;&quot;&quot;Handles all the nasty stuff to do with curses set-up + tear-down&quot;&quot;&quot;\r\n    curses.wrapper(main)\r\n<\/pre><\/p>\n<p>As part of the wrapper function, the main is called and passed a variable called <em>stdscr<\/em>. This is a curses <em>Screen<\/em> object. This is our canvas upon which we&#8217;ll draw our player character, NPCs; the world, basically. This will be passed to the object that controls the world, so it can also control the canvas. Calling the function via the wrapper serves a couple of purposes:<\/p>\n<ol>\n<li>The wrapper handles all curses set-up and tear-down, as mentioned in the docstring. It will initialise curses and set up various desirable cursor attributes such as disabling echo and enabling instant keyboard inputs. After the passed in function has ended, it shuts down curses gracefully.<\/li>\n<li>In case of an unhandled exception in the called function, the wrapper will catch it, shut down curses correctly, and then re-throw the exception. This prevents such a crash from destroying your terminal settings.<\/li>\n<\/ol>\n<p>I went in to this particular stretch of work with the goal of having my character (an @ symbol) move around the screen controlled by the arrow keys. With the program successfully bootstrapped and in a &#8216;working state&#8217;, it was time to accomplish that goal.<\/p>\n<p>I wanted to keep the main function as lightweight as possible, leaving it as only the entry point to the application and nothing more. I accomplished this by writing a <em>Game<\/em> class which the main would instantiate and then call a function of. In this case, the function is called <em>mainLoop<\/em>, and as you might expect, it is responsible for controlling the game loop itself. Before we get in to that, let&#8217;s examine the members of the <em>Game<\/em> class.<\/p>\n<p><pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">\r\nclass Game:\r\n    &quot;&quot;&quot;The game logic itself. The loop and input handling are here.&quot;&quot;&quot;\r\n    def __init__(self, screen):\r\n        &quot;&quot;&quot;Create the screen, player, assets.&quot;&quot;&quot;\r\n        self.screen = screen\r\n        self.running = True\r\n        self.player = Player()\r\n<\/pre><\/p>\n<p>So all we have right now are variables for storing the aforementioned <em>Screen<\/em> instance, another for storing the state of the game (running, not running), and one containing a currently mysterious Player object. How about that <em>mainLoop<\/em> I mentioned?<\/p>\n<p><pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">\r\n    def mainLoop(self):\r\n        &quot;&quot;&quot;Run the game while a flag is set.&quot;&quot;&quot;\r\n        while (self.running):\r\n            self.logic()\r\n            self.draw()\r\n            self.handleInput()\r\n<\/pre><\/p>\n<p>As long as the game is running, we run all the logic for the game, draw the resulting position, then wait for player input.<\/p>\n<p><pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">\r\n    def logic(self):\r\n        pass\r\n<\/pre><\/p>\n<p>No game logic exists at this point, so logic is an empty function.<\/p>\n<p><pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">\r\n    def draw(self):\r\n        &quot;&quot;&quot; Draw it all, filling in the blanks as necessary &quot;&quot;&quot;\r\n        self.screen.erase()\r\n        player = self.player\r\n        self.screen.addstr(player.y, player.x,\r\n                           player.character, curses.color_pair(1))\r\n        self.screen.move(player.y, player.x)\r\n        self.screen.refresh()\r\n<\/pre><\/p>\n<p><em>draw<\/em> actually does something useful. It clears the previous screen, grabs the Player object, draws it in the right place, and finally moves the cursor to the player&#8217;s position. That last step is one of the many niceties of a console-based roguelike: if we don&#8217;t put it directly on the player character, there are two problems:<\/p>\n<ol>\n<li>If we don&#8217;t uniquely colour the player character, the player (user, not game object) won&#8217;t be able to identify his character if multiple objects share the same symbol.<\/li>\n<li>If we don&#8217;t put the cursor on the player character, it has to go somewhere else. And where else would we even want to put it? It needs to go somewhere. Because of problem one, it makes sense to place it on the player character when possible.<\/li>\n<\/ol>\n<p><pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">\r\n    def handleInput(self):\r\n        &quot;&quot;&quot; Wait for the player to press a key, then handle\r\n            input appropriately.&quot;&quot;&quot;\r\n        character = self.screen.getch()\r\n        if character == ord(&#039;q&#039;):\r\n            self.running = False\r\n        elif character == ord(&#039;h&#039;):\r\n            self.player.move(Direction.LEFT)\r\n        elif character == ord(&#039;j&#039;):\r\n            self.player.move(Direction.DOWN)\r\n        elif character == ord(&#039;k&#039;):\r\n            self.player.move(Direction.UP)\r\n        elif character == ord(&#039;l&#039;):\r\n            self.player.move(Direction.RIGHT)\r\n<\/pre><\/p>\n<p>Given the turn-based nature of the game, there&#8217;s no need to do anything particularly clever like pump event queues or have time-out functions. We explicitly wait for the player to input a character via a <em>Screen<\/em> function, check the result against the list of characters we&#8217;re expecting, and perform the associated action if we get a match. In this case, we call one of the <em>Player<\/em> functions to move the character around the screen. You&#8217;ll also notice we pass in an enum. <a href=\"http:\/\/stackoverflow.com\/questions\/36932\/how-can-i-represent-an-enum-in-python\" title=\"Stack Overflow - Python Enums\">In Python<\/a>, enums are usually just static class variables grouped in a class that becomes the enum&#8217;s name:<\/p>\n<p><pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">\r\nclass Direction:\r\n    &quot;&quot;&quot;An enum for the possible movement directions&quot;&quot;&quot;\r\n    UP = 1\r\n    DOWN = 2\r\n    LEFT = 3\r\n    RIGHT = 4\r\n<\/pre><\/p>\n<p>In this case, nothing clever is going on: An enum of four elements in declared for the four directions of movement. Let&#8217;s cover the <em>Player<\/em> object now:<\/p>\n<p><pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">\r\nclass Player(object):\r\n    &quot;&quot;&quot;The player object, containing data such as HP etc.&quot;&quot;&quot;\r\n    def __init__(self):\r\n        &quot;&quot;&quot;Initialise the player object&quot;&quot;&quot;\r\n        self.x = 20\r\n        self.y = 30\r\n        self.character = &#039;@&#039;\r\n<\/pre><\/p>\n<p>At the point in development, the <em>Player<\/em> contains only information strictly required to render it: Its position on the screen (y, x) and the character we should use to represent it. This is used by the <em>draw<\/em> function in the above <em>Game<\/em> class. It has a single member function:<\/p>\n<p><pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">\r\n    def move(self, direction):\r\n        &quot;&quot;&quot;Move the player one unit in the specified direction&quot;&quot;&quot;\r\n        if direction == Direction.UP:\r\n            self.y -= 1\r\n        elif direction == Direction.DOWN:\r\n            self.y += 1\r\n        elif direction == Direction.LEFT:\r\n            self.x -= 1\r\n        elif direction == Direction.RIGHT:\r\n            self.x += 1\r\n<\/pre><\/p>\n<p>This gets called by the <em>handleInput<\/em> function, and moves the player in the given direction. I should probably mention that this code will let you move the player character out-of-bounds of the <em>Screen<\/em> object. This throws an exception and crashes the program. Additionally, you can&#8217;t move to the bottom-right most part of the screen, for a much more subtle reason: Curses will draw the @ symbol in the very last available space, but then attempt to move the cursor along to a location that doesn&#8217;t exist in the screen space. Both of these problems will be fixed later on.<\/p>\n<p>At this point, we can revisit the <em>main<\/em> function and see how we incorporate this new Game object. There&#8217;s no surprises here:<\/p>\n<p><pre data-enlighter-language=\"python\" class=\"EnlighterJSRAW\">\r\ndef main(stdscr):\r\n    &quot;&quot;&quot;Initialises the Game object and basically gets out of the way&quot;&quot;&quot;\r\n    stdscr.bkgd(&#039; &#039;, curses.color_pair(0))\r\n    game = Game(stdscr)\r\n    game.mainLoop()\r\n<\/pre><\/p>\n<p>We set the &#8216;background&#8217; to be made up of blank characters. If we ever erase the screen, it is actually filled with the background character, as set by the <em>bkgd<\/em> function. We then instantiate and start the game.<\/p>\n<p>That&#8217;s it! The actual code looks a little different for the <a href=\"https:\/\/github.com\/mjdarby\/RogueDetective\/blob\/88b9f52fce028a43da24a85e64826be7ee6143a8\/main.py\" title=\"First Commit\">first commit<\/a> (there&#8217;s a superfluous class floating around, and some of the code has been changed here for clarity). How does it look? Well, take a look:<\/p>\n<p><a href=\"http:\/\/mjdarby.net\/blog\/wp-content\/uploads\/2014\/04\/FirstCommit.png\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/mjdarby.net\/blog\/wp-content\/uploads\/2014\/04\/FirstCommit.png\" alt=\"FirstCommit\" width=\"660\" height=\"618\" class=\"aligncenter size-full wp-image-227\" srcset=\"http:\/\/mjdarby.net\/blog\/wp-content\/uploads\/2014\/04\/FirstCommit.png 660w, http:\/\/mjdarby.net\/blog\/wp-content\/uploads\/2014\/04\/FirstCommit-300x280.png 300w\" sizes=\"auto, (max-width: 660px) 100vw, 660px\" \/><\/a><\/p>\n<p>In the future, we won&#8217;t cover whole commits. Indeed, we won&#8217;t cover each commit, but rather the functional milestones of the project. Hopefully you&#8217;ve gained a little bit on insight in to how a Python curses project starts life. Next time, we&#8217;ll look at the implementation of walls and other standard obstacles you tend to see in roguelikes.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Last time, we covered the rather ridiculous choices that led up to my decision to write a roguelike from scratch. Now, we&#8217;ll cover the first steps in the creation of Rogue Detective, and indeed, most games I write. After the standard step of creating a new repository for source control comes the creation of the <a class=\"read-more\" href=\"http:\/\/mjdarby.net\/blog\/2014\/04\/21\/rogue-detective-part-2-curses\/\">[&hellip;]<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,15],"tags":[],"class_list":["post-210","post","type-post","status-publish","format-standard","hentry","category-programming","category-rogue-detective"],"_links":{"self":[{"href":"http:\/\/mjdarby.net\/blog\/wp-json\/wp\/v2\/posts\/210","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/mjdarby.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/mjdarby.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/mjdarby.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/mjdarby.net\/blog\/wp-json\/wp\/v2\/comments?post=210"}],"version-history":[{"count":18,"href":"http:\/\/mjdarby.net\/blog\/wp-json\/wp\/v2\/posts\/210\/revisions"}],"predecessor-version":[{"id":229,"href":"http:\/\/mjdarby.net\/blog\/wp-json\/wp\/v2\/posts\/210\/revisions\/229"}],"wp:attachment":[{"href":"http:\/\/mjdarby.net\/blog\/wp-json\/wp\/v2\/media?parent=210"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/mjdarby.net\/blog\/wp-json\/wp\/v2\/categories?post=210"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/mjdarby.net\/blog\/wp-json\/wp\/v2\/tags?post=210"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}