<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-218012982026091552</id><updated>2012-02-18T18:42:45.045-08:00</updated><category term='mud'/><title type='text'>BogBoa</title><subtitle type='html'>Progess blog for a spare-time project to create a Multi-User Dungeon in Python.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>35</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-3924536868620025974</id><published>2012-02-18T18:31:00.000-08:00</published><updated>2012-02-18T18:42:45.056-08:00</updated><title type='text'>Zomborgs</title><content type='html'>&lt;a href="http://4.bp.blogspot.com/-DIwomO-6p3M/T0BfInBnxEI/AAAAAAAAA28/GLYe22RPN50/s1600/zombs.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 236px;" src="http://4.bp.blogspot.com/-DIwomO-6p3M/T0BfInBnxEI/AAAAAAAAA28/GLYe22RPN50/s400/zombs.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5710668929025164354" /&gt;&lt;/a&gt;&lt;div&gt;Known to use transmat beams to take brains directly from crewfolk skulls.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-3924536868620025974?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/3924536868620025974/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=3924536868620025974' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/3924536868620025974'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/3924536868620025974'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2012/02/zomborgs.html' title='Zomborgs'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-DIwomO-6p3M/T0BfInBnxEI/AAAAAAAAA28/GLYe22RPN50/s72-c/zombs.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-5795759241288689054</id><published>2012-02-18T08:20:00.001-08:00</published><updated>2012-02-18T08:29:59.019-08:00</updated><title type='text'>Failure is an Option</title><content type='html'>&lt;a href="http://4.bp.blogspot.com/-LfHOxuMEpFE/Tz_QjmWsIXI/AAAAAAAAA2w/UwcJp4Vqpko/s1600/path3460.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 240px; height: 277px;" src="http://4.bp.blogspot.com/-LfHOxuMEpFE/Tz_QjmWsIXI/AAAAAAAAA2w/UwcJp4Vqpko/s400/path3460.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5710512162538791282" /&gt;&lt;/a&gt;&lt;div style="text-align: center;"&gt;&lt;span &gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;span style="text-align: left; "&gt;This is the icon for a dead player&lt;/span&gt;&lt;span&gt;&lt;u&gt;&lt;br /&gt;&lt;/u&gt;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-5795759241288689054?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/5795759241288689054/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=5795759241288689054' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5795759241288689054'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5795759241288689054'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2012/02/failure-is-option.html' title='Failure is an Option'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-LfHOxuMEpFE/Tz_QjmWsIXI/AAAAAAAAA2w/UwcJp4Vqpko/s72-c/path3460.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-48062696677036470</id><published>2012-02-17T12:15:00.000-08:00</published><updated>2012-02-17T12:25:23.697-08:00</updated><title type='text'>Character Work</title><content type='html'>&lt;div&gt;I have a notion of how I'd like the chat windows to flow using character portraits.  Player and crew would appear left of chat boxes and encountered NPCs to the right.  I suspect I'm channeling some Japanese RPG. &lt;/div&gt;&lt;a href="http://1.bp.blogspot.com/-CN2qNzxHuWo/Tz61jQp39WI/AAAAAAAAA2Y/ZTt-fj46y4c/s1600/k4.png"&gt;&lt;img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 193px;" src="http://1.bp.blogspot.com/-CN2qNzxHuWo/Tz61jQp39WI/AAAAAAAAA2Y/ZTt-fj46y4c/s400/k4.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5710200994923017570" /&gt;&lt;/a&gt;&lt;div style="text-align: left;"&gt;This is a &lt;b&gt;Kthogin&lt;/b&gt; -- a Lovecraftian inspired hostile.  I'm leaning towards giving each race a unique color for easy recognition.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-48062696677036470?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/48062696677036470/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=48062696677036470' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/48062696677036470'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/48062696677036470'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2012/02/character-work.html' title='Character Work'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-CN2qNzxHuWo/Tz61jQp39WI/AAAAAAAAA2Y/ZTt-fj46y4c/s72-c/k4.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-5610051223583557921</id><published>2012-02-12T10:33:00.000-08:00</published><updated>2012-02-12T12:59:59.321-08:00</updated><title type='text'>Thoughts on Serialization</title><content type='html'>The problem domain is short and sweet;&lt;div&gt;&lt;ol&gt;&lt;li&gt;We need to write game data.&lt;/li&gt;&lt;li&gt;We need to read game data.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;The only real snag is, being single threaded, we need these operations to block as little as possible -- as in thousandths of a second.   I've even toyed with the idea of reading &lt;b&gt;everything&lt;/b&gt; at server start and &lt;i&gt;only performing writes&lt;/i&gt; during execution.  That would cut our blocking problem in half but removes our ability to modify data externally.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The model in my head calls for very little of the&lt;b&gt;&lt;i&gt; R &lt;/i&gt;&lt;/b&gt;in &lt;a href="http://en.wikipedia.org/wiki/Relational_database_management_system"&gt;RDBMS&lt;/a&gt;.  Heck, MUDs have managed very well for decades using flat files on systems with less processing power than your dishwasher.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Lastly, given that this hobby-coding, I have the luxury of asking, 'is the solution fun?'  Let's set a couple goals;&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;I don't want a crash to lose the overall state of the game -- which precludes saving writes for an extended period, or even worse, until server shutdown.&lt;/li&gt;&lt;li&gt;Simple to install.  Freely available software that does not require a DBA to manage.&lt;/li&gt;&lt;li&gt;Simple to operate. No fretting dirty caches, scheduled housekeeping , etc.&lt;/li&gt;&lt;li&gt;I would prefer back-ups and restores to be file copies.  Tar'ing a directory or two is fine.&lt;/li&gt;&lt;li&gt;Some external method to futz with the data while the game is running. &lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;Flat files are certainly doable (and made crazy easy with Python's &lt;a href="http://docs.python.org/release/2.5/lib/module-pickle.html"&gt;Pickle module&lt;/a&gt;).  They meet goals #2, #3, and #4 but I cringe at the amount of runtime file-io needed to cover goal #1 and supporting goal #5 means we have to perform constant reads as well (plus some form of file-locking mechanism).  Ick.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A lightweight dbms like &lt;a href="http://www.sqlite.org/"&gt;SQLite&lt;/a&gt; would work except for goal #3.  Wrapping Python &lt;a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete"&gt;CRUD&lt;/a&gt; in SQL statements is soul-destroying tedious.  Yeah, I could tap a ORM like &lt;a href="http://www.sqlalchemy.org/"&gt;SQLAlchemy&lt;/a&gt; but let's look at that new sexy, NoSQL...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;img src="http://4.bp.blogspot.com/-htIucsKbvu0/TzggU6IShGI/AAAAAAAAA2M/IBoo-yr7VRQ/s400/1scienceb.jpg" border="0" alt="" id="BLOGGER_PHOTO_ID_5708348071265600610" style="float: right; margin-top: 0px; margin-right: 0px; margin-bottom: 10px; margin-left: 10px; cursor: pointer; width: 300px; height: 278px; " /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;    &lt;/div&gt;&lt;div&gt;    &lt;/div&gt;&lt;div&gt;When I was a kid, one Christmas I got this electronics kit with 150 projects.  It was awesome.  You could build things like a crystal radio, lie detector, and light activated room alarm.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Python holds that same appeal for me.  It's a big toy.  Another one I've found is &lt;a href="http://redis.io/"&gt;Redis&lt;/a&gt; -- a dead simple NoSQL data store.  The distinguishing feature is all data is held in-memory which makes it wicked fast.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I'll admit, when I first read that it holds everything is RAM it struck me as rather pointless.  I mean, wasn't I already doing that&lt;i&gt; in my program&lt;/i&gt;? And who wants to hold &lt;b&gt;everything&lt;/b&gt; in memory whether you need it or not?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Having played with it, the utility and genius starts to shine through.  Redis has a clever system of serializing to a file based on the frequency of updates and you can copy this file at &lt;i&gt;any moment&lt;/i&gt; without fear of corrupting it.  The author also provides a really nice CLI tool with history and auto-complete similar to a Linux terminal.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;We're pushing goal #2 a bit since it requires installing three packages, &lt;a href="http://redis.io/download"&gt;Redis&lt;/a&gt;, &lt;a href="https://github.com/antirez/hiredis"&gt;Hiredis&lt;/a&gt;, and &lt;a href="https://github.com/andymccurdy/redis-py.git"&gt;Redis-py&lt;/a&gt; but they all loaded with minimal fuss.  Hiredis needed the Python development libs you can get on Ubuntu using:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"&gt;$ sudo apt-get install python-dev&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;...to be continued&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-5610051223583557921?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/5610051223583557921/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=5610051223583557921' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5610051223583557921'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5610051223583557921'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2012/02/thoughts-on-serialization.html' title='Thoughts on Serialization'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-htIucsKbvu0/TzggU6IShGI/AAAAAAAAA2M/IBoo-yr7VRQ/s72-c/1scienceb.jpg' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-7048179387327339417</id><published>2011-06-27T18:53:00.000-07:00</published><updated>2011-06-28T11:49:30.238-07:00</updated><title type='text'>Crawling a Graph</title><content type='html'>&lt;div&gt;I got the sector crawling code done.  Then I used it to walk a sector and add every linked one to a set.  This set is also added to a master set of visited sectors.   I step to the next sector and, if it's not in the visited set, crawl it.  Repeat for all sectors to generate a &lt;i&gt;collection&lt;/i&gt; of sets.  Each set contains either a group of linked sectors or a single orphan sector.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here's a diagram of crawling from sector 12:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;img src="http://1.bp.blogspot.com/-zEmT4CIxS9k/Tgk3oGjN28I/AAAAAAAAAME/YrDNQ44RNYI/s400/crawl.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 400px;" border="0" alt="" id="BLOGGER_PHOTO_ID_5623086771841391554" /&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Next, I take the collection of sets, pop one off, and work through the remaining trying to find two sector that are adjacent.  Once I do, I give them exits to each other and merge the popped set into the target set.  I repeat this until the collection only contains one set -- the entire universe: &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;img src="http://4.bp.blogspot.com/-2oZadY38yfQ/Tgk3oO3MjNI/AAAAAAAAAMM/SAuN2jC1te4/s400/crawl2.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 400px;" border="0" alt="" id="BLOGGER_PHOTO_ID_5623086774072675538" /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Lastly, I got my&lt;a href="http://en.wikipedia.org/wiki/A*_search_algorithm"&gt; A* path finder&lt;/a&gt; working.  This is the first time I've experimented with path finding.  I tried a variety of heuristics and had the best luck (shortest path + least crawled sectors + fastest time) with the simplest one; Chebyshev.  This is comparing ABS(X1 - X2) to ABS(Y1 - Y2) and&lt;i&gt; use which ever is greater&lt;/i&gt;.  One source I read said to add the number of hops from the starting point but, again, just Chebyshev alone gave the best results.  Weirdness.  Could have a glitch somewhere.    &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://2.bp.blogspot.com/-t1dX8zGSEfI/Tgk3ocnh_UI/AAAAAAAAAMU/YBWuoFlFfpE/s1600/astar.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img style="text-align: left;display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; cursor: pointer; width: 400px; height: 400px; " src="http://2.bp.blogspot.com/-t1dX8zGSEfI/Tgk3ocnh_UI/AAAAAAAAAMU/YBWuoFlFfpE/s400/astar.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5623086777765068098" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I had a funny bug during my testing.  I was using random.seed() so that I'd generate the same initial links every time.  I ran my path finding test; 24 hops.  Ran it again; 20 hops.  Again; 48 hops.  What the heck?  I neglected to consider that sets are unordered and was linking the orphaned sectors in varying sequence.  Changing the set to a list fixed this.&lt;/div&gt;&lt;div&gt;&lt;div style="text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;Two options that I have not explored are one-way exits and links between non-adjacent sectors (wormholes).  Not sure if the added complexity has value.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-7048179387327339417?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/7048179387327339417/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=7048179387327339417' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/7048179387327339417'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/7048179387327339417'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2011/06/crawling-graph.html' title='Crawling a Graph'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-zEmT4CIxS9k/Tgk3oGjN28I/AAAAAAAAAME/YrDNQ44RNYI/s72-c/crawl.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-8043579731810320619</id><published>2011-06-25T18:56:00.000-07:00</published><updated>2011-06-28T10:13:32.342-07:00</updated><title type='text'>Visualizing Data</title><content type='html'>&lt;div style="text-align: left;"&gt;To create the game space, I'm going to need to assign random sector connections, tweak those into usefulness, and eventually write an A* path finder.  I really need some way to &lt;b&gt;SEE&lt;/b&gt; this hooey.&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: left;"&gt;&lt;a href="http://en.wikipedia.org/wiki/Scalable_Vector_Graphics"&gt;Scaled Vector Graphics&lt;/a&gt; are an &lt;a href="http://en.wikipedia.org/wiki/XML"&gt;XML&lt;/a&gt;-based image format that I really like.  Google suggested a couple of Python libraries but I ended up writing my own based on code to generate &lt;a href="http://en.wikipedia.org/wiki/Xhtml"&gt;XHTML&lt;/a&gt; from a previous project.  A few hours later I had my universe mapper.  Here's a 25 sector uiverse with some random pathways.  Notice that sector 23 is an orphan, with no way in and no way out.  That's the issue I'm working on now.&lt;/div&gt;&lt;img src="http://1.bp.blogspot.com/-0kJOeidUnxA/TgaSODgE3HI/AAAAAAAAALs/6YLfjiMtiSg/s400/test.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 400px;" border="0" alt="" id="BLOGGER_PHOTO_ID_5622341954974309490" /&gt;&lt;br /&gt;&lt;div&gt;You might notice the absence of NE and SE jumps to fit the hex paper scheme from my previous post.  I decided that the center-most sector (in this case, 12) would be the &lt;i&gt;hub&lt;/i&gt;, which will always get 6 exits.  More on using this to cure orphanism later. &lt;div&gt;&lt;br /&gt;&lt;div&gt;Just to be stupid, I generated a map with 40,000 sectors.  The script ran for only a second or two but my image viewer choked so bad that I had to go read a book while it chugged through and finally produced this (zoomed out) mess:&lt;/div&gt;&lt;img src="http://2.bp.blogspot.com/-5pkUdTVJGvM/TgaaLPjFUII/AAAAAAAAAL8/DWwg2lgX5nE/s400/40K_sects.png" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 324px; height: 400px;" border="0" alt="" id="BLOGGER_PHOTO_ID_5622350702761562242" /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-8043579731810320619?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/8043579731810320619/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=8043579731810320619' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/8043579731810320619'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/8043579731810320619'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2011/06/visualizing-data.html' title='Visualizing Data'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/-0kJOeidUnxA/TgaSODgE3HI/AAAAAAAAALs/6YLfjiMtiSg/s72-c/test.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-6539770587014048307</id><published>2011-06-25T08:48:00.000-07:00</published><updated>2011-06-25T09:28:39.336-07:00</updated><title type='text'>The Universe is a Diamond</title><content type='html'>&lt;div style="text-align: left;"&gt;Now that I've got the websocket server working I've got the itch to write a space rpg inspired by the classic BBS game &lt;a href="http://en.wikipedia.org/wiki/Trade_Wars"&gt;Tradewars&lt;/a&gt;.&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I especially liked the spatial system they used where sectors formed these baffling, mole-tunnel chains.   Lucky exploration might find you a highly profitable port or, better yet, a nice dead-end sector to hide your ships and planets in.&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let's say we want each sector to link up to six others, proving a movement pattern like hex paper;&lt;/div&gt;&lt;img src="http://4.bp.blogspot.com/-phN8bHjbF1A/TgYGh9DANsI/AAAAAAAAAK0/FqIXnLUjoYg/s400/thumbnail.gif" style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 117px; height: 157px;" border="0" alt="" id="BLOGGER_PHOTO_ID_5622188365211252418" /&gt;&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Internally, I plan on using a bog-standard XY array.  Why?  Because it &lt;i&gt;feels&lt;/i&gt; like it would make things like pathfinding and NPC distribution easier.  We'll see.&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So let's say we're in Sector 54 (yellow) and can move to six adjacent sectors (blue):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;img src="http://4.bp.blogspot.com/-60uhxaFpUMo/TgYLJisVaSI/AAAAAAAAALk/sqe432NUf3U/s400/hex1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5622193443378129186" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 128px; height: 129px; " /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;I'm thinking that if I rotate the player's orientation slightly and omit Northeast and Southwest movement, I can fit the hex over the grid like so;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;img src="http://1.bp.blogspot.com/-7HHYS4URnPs/TgYIpNkUNzI/AAAAAAAAALM/XDJsX0pT94M/s400/grid1.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5622190688928282418" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 298px; height: 298px; " /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;To the player's perspective, he's flying around a diamond-shaped space.  Here's an example of moving from Sector 53 to 01.   It was important to me that the sector you came from retain the proper orientation.&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;img src="http://4.bp.blogspot.com/-daJL5AwXtLs/TgYJOAFXbRI/AAAAAAAAALc/CnnBr99wUoE/s400/grid2.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5622191320963968274" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 400px; height: 400px; " /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "&gt;&lt;span class="Apple-style-span" style="color: rgb(0, 0, 0); "&gt;Virtual hex paper.  Next step is to break all the links into twisty little passages -- not alike.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-6539770587014048307?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/6539770587014048307/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=6539770587014048307' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/6539770587014048307'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/6539770587014048307'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2011/06/universe-is-diamond.html' title='The Universe is a Diamond'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/-phN8bHjbF1A/TgYGh9DANsI/AAAAAAAAAK0/FqIXnLUjoYg/s72-c/thumbnail.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-168236747260938265</id><published>2011-06-17T17:49:00.000-07:00</published><updated>2011-06-17T18:26:11.747-07:00</updated><title type='text'>Python 3</title><content type='html'>I've been ignoring Python 3K.  &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I code under Linux and have been perfectly happy with the 2.x versions that most distros include by default.  But Python 3 nags at me like a late property tax of uncertain size. &lt;br /&gt;&lt;br /&gt;A while back someone filed a bug that Miniboa didn't work with Python 3.  "Get off my lawn", I thought to myself.&lt;br /&gt;&lt;br /&gt;To switch gears for a second -- I've found that deleting big chunks of code (&lt;span style="font-style:italic;"&gt;murder your darlings&lt;/span&gt;) is usually a sign that you're on the right track.  I deleted the unfinished Telnet, co-routine, and character-at-a-time code from Netboa.  Knowing that the codebase will never be this slim again, I thought it was a good time to take a shot at Python 3 compatibility and issued an 'apt-get install python3'.     &lt;br /&gt;&lt;br /&gt;Like the five stages of denial, I bet there's a sequence that Python coders go through to port their code to 3K that starts with "keep adding parenthesis", takes them mindless cutting and pasting from googles of error messages, and finishes with them sobbing over byte encoding.  Python 3 has two types of strings; bytes and unicode.  Oddly, byte sequences are very like Python 2 strings minus any formatting options but with all kinds of gotchas.  Take this weirdness for example:&lt;br /&gt;&lt;pre name="code" class="py"&gt;&amp;gt;&amp;gt;&amp;gt; '\x20\x20'[0] == '\x20'&lt;br /&gt;True&lt;br /&gt;&amp;gt;&amp;gt;&amp;gt; b'\x20\x20'[0] == b'\x20'&lt;br /&gt;False&lt;br /&gt;&lt;/pre&gt;Following the &lt;a href="http://lucumr.pocoo.org/2010/5/25/wsgi-on-python-3/"&gt;advice of Armin Ronacher&lt;/a&gt;, I switched most everything to byte sequences as those play nice with sockets and the keys needed for handshaking.  After a bit of import hackery, Netboa seems to work under both 3+ and 2.6+.  Due to a change in exceptions using the "as" keyword, I lost 2.5 compatibility.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-168236747260938265?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/168236747260938265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=168236747260938265' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/168236747260938265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/168236747260938265'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2011/06/python-3.html' title='Python 3'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-4451503356819596959</id><published>2011-06-15T17:02:00.000-07:00</published><updated>2011-06-15T17:29:13.054-07:00</updated><title type='text'>WebSockets</title><content type='html'>&lt;div&gt;Yes, long hiatus.  Hi.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://en.wikipedia.org/wiki/WebSockets"&gt;WebSockets&lt;/a&gt; have amazing potential for browser-based gaming.  Unfortunately, they are also in a constant state of flux, partially because the draft spec keeps changing and partially because browser makers want to minimize the amount of dickery that internet doofuses can inflict on you.  Firefox and Opera have temporarily dropped WebSocket support but they should be back in again in Firefox 6.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;The last few nights I've been trying to fix a handshake bug with my implementation of draft76 handshaking.  This is the version currently used by Chrome and Safari.  Finally got it working again after writing the following test case:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="py"&gt;&lt;br /&gt;#!/usr/bin/env python&lt;br /&gt;#------------------------------------------------------------------------------&lt;br /&gt;#   netboa/websocket/sanity_check76.py&lt;br /&gt;#   Copyright 2011 Jim Storch&lt;br /&gt;#   Licensed under the Apache License, Version 2.0 (the "License"); you may&lt;br /&gt;#   not use this file except in compliance with the License. You may obtain a&lt;br /&gt;#   copy of the License at http://www.apache.org/licenses/LICENSE-2.0&lt;br /&gt;#   Unless required by applicable law or agreed to in writing, software&lt;br /&gt;#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT&lt;br /&gt;#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the&lt;br /&gt;#   License for the specific language governing permissions and limitations&lt;br /&gt;#   under the License.&lt;br /&gt;#------------------------------------------------------------------------------&lt;br /&gt;&lt;br /&gt;"""&lt;br /&gt;Wrote this to test that I'm correctly generating the responses for draft76&lt;br /&gt;websockets.&lt;br /&gt;"""&lt;br /&gt;&lt;br /&gt;import struct&lt;br /&gt;import hashlib&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;## From the draft76 docs:&lt;br /&gt;&lt;br /&gt;REQUEST1 = (&lt;br /&gt;    'GET /demo HTTP/1.1\r\n'&lt;br /&gt;    'Host: example.com\r\n'&lt;br /&gt;    'Connection: Upgrade\r\n'&lt;br /&gt;    'Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n'&lt;br /&gt;    'Sec-WebSocket-Protocol: sample\r\n'&lt;br /&gt;    'Upgrade: WebSocket\r\n'&lt;br /&gt;    'Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n'&lt;br /&gt;    'Origin: http://example.com\r\n'&lt;br /&gt;    '\r\n'&lt;br /&gt;    '^n:ds[4U'&lt;br /&gt;    )&lt;br /&gt;&lt;br /&gt;CORRECT_RESPONSE1 = "8jKS'y:G*Co,Wxa-"&lt;br /&gt;&lt;br /&gt;REQUEST2 = (&lt;br /&gt;    'GET /demo HTTP/1.1\r\n'&lt;br /&gt;    'Host: example.com\r\n'&lt;br /&gt;    'Connection: Upgrade\r\n'&lt;br /&gt;    'Sec-WebSocket-Key2: 1_ tx7X d  &lt;  nw  334J702) 7]o}` 0\r\n'&lt;br /&gt;    'Sec-WebSocket-Protocol: sample\r\n'&lt;br /&gt;    'Upgrade: WebSocket\r\n'&lt;br /&gt;    'Sec-WebSocket-Key1: 18x 6]8vM;54 *(5:  {   U1]8  z [  8\r\n'&lt;br /&gt;    'Origin: http://example.com\r\n'&lt;br /&gt;    '\r\n'&lt;br /&gt;    'Tm[K T2u'&lt;br /&gt;    )&lt;br /&gt;&lt;br /&gt;CORRECT_RESPONSE2 = 'fQJ,fN/4F4!~K~MH'&lt;br /&gt;&lt;br /&gt;def parse_request(request):&lt;br /&gt;    req = {}&lt;br /&gt;    segments = request.split('\r\n\r\n', 1)&lt;br /&gt;    assert(len(segments) == 2)&lt;br /&gt;    header, payload = segments&lt;br /&gt;    lines = header.split('\r\n')&lt;br /&gt;    line = lines.pop(0)&lt;br /&gt;    items = line.split('\x20',2)&lt;br /&gt;    assert(len(items) == 3)&lt;br /&gt;    req['method'] = items[0]&lt;br /&gt;    req['request_uri'] = items[1]&lt;br /&gt;    req['http_version'] = items[2]&lt;br /&gt;    for line in lines:&lt;br /&gt;        parts = line.split(':\x20', 1)&lt;br /&gt;        assert(len(parts) == 2)&lt;br /&gt;        req[parts[0].lower()] = parts[1]&lt;br /&gt;    req['payload'] = payload&lt;br /&gt;    return req&lt;br /&gt;&lt;br /&gt;def get_word(key):&lt;br /&gt;    digits=''&lt;br /&gt;    spaces=0&lt;br /&gt;    for char in key:&lt;br /&gt;        if char.isdigit():&lt;br /&gt;            digits += char&lt;br /&gt;        elif char == '\x20':&lt;br /&gt;            spaces += 1&lt;br /&gt;    assert(spaces)&lt;br /&gt;    assert(digits)&lt;br /&gt;    keynum = int(digits)&lt;br /&gt;    assert(keynum % spaces == 0)&lt;br /&gt;    wordval = keynum / spaces&lt;br /&gt;    assert(wordval &lt; 2 ** 32)&lt;br /&gt;    return struct.pack("!I", wordval)&lt;br /&gt;&lt;br /&gt;def create_token(req):&lt;br /&gt;    key1 = req.get('sec-websocket-key1', None)&lt;br /&gt;    assert(key1 is not None)&lt;br /&gt;    word1 = get_word(key1)&lt;br /&gt;    key2 = req.get('sec-websocket-key2', None)&lt;br /&gt;    assert(key2 is not None)&lt;br /&gt;    word2 = get_word(key2)&lt;br /&gt;    salt = req.get('payload', None)&lt;br /&gt;    assert(salt is not None)&lt;br /&gt;    assert(len(salt)==8)&lt;br /&gt;    return hashlib.md5(word1 + word2 + salt).digest()    &lt;br /&gt;&lt;br /&gt;req = parse_request(REQUEST1)&lt;br /&gt;print create_token(req) == CORRECT_RESPONSE1&lt;br /&gt;&lt;br /&gt;req = parse_request(REQUEST2)&lt;br /&gt;print create_token(req) == CORRECT_RESPONSE2&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;Please note that this version is scheduled to be replaced by a draft called Hybi-08 which features a way simpler algorithm (which I *think* I have it nearly ready to drop in).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;My interests are clearly taking Netboa towards being a browser-based game server.  The combination of HTTP + WebSockets is quite appealing.  It's really time to knuckle down and learn some Javascript -- or, more likely, lots of JQuery.  &lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-4451503356819596959?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/4451503356819596959/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=4451503356819596959' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/4451503356819596959'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/4451503356819596959'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2011/06/websockets.html' title='WebSockets'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-9073509984370084991</id><published>2011-04-22T09:07:00.000-07:00</published><updated>2011-04-22T09:08:56.149-07:00</updated><title type='text'>Chrome WebSocket Protocol Update</title><content type='html'>&lt;div&gt;Just making a note here; &lt;/div&gt;&lt;div&gt;&lt;a href="http://blog.chromium.org/2010/06/websocket-protocol-updated.html"&gt;http://blog.chromium.org/2010/06/websocket-protocol-updated.html&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-9073509984370084991?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/9073509984370084991/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=9073509984370084991' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/9073509984370084991'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/9073509984370084991'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2011/04/chrome-websocket-protocol-update.html' title='Chrome WebSocket Protocol Update'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-5306933094299349202</id><published>2010-08-22T10:53:00.000-07:00</published><updated>2010-08-22T12:00:00.913-07:00</updated><title type='text'>HTTP Server</title><content type='html'>In my previous post, I mentioned the goal of separating protocols from the client object.  As an experiment, I started writing a HTTP service using the new code.  This would let me run a light web server as a service in conjunction with something like WebSockets on the same, single-threaded server.&lt;br /&gt;&lt;br /&gt;You definitely don't want to waste time serving big files in a single threaded application because it blocks but for little things like a login page it could be handy.  Plus, I've written web based apps for years but never fully understood what was happening under the hood and here was a chance to find out.&lt;br /&gt;&lt;br /&gt;The actual HTTP protocol is pretty simple.  It only has eleven commands (or methods);  HEAD, GET, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT,  and PATCH.  Currently, I only support one, GET (which is easily 99.9% of the web).  All other methods are responded to with a status code &lt;span style="font-style: italic;"&gt;501 Not Implemented&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Here's my index.html rendered on Chromium:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_k-xgcnlJm1E/THFvcu0mDMI/AAAAAAAAAKA/qgaz-3VQH40/s1600/Screenshot-Hello+World+-+Chromium-1.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 210px;" src="http://4.bp.blogspot.com/_k-xgcnlJm1E/THFvcu0mDMI/AAAAAAAAAKA/qgaz-3VQH40/s400/Screenshot-Hello+World+-+Chromium-1.png" alt="" id="BLOGGER_PHOTO_ID_5508306358646475970" border="0" /&gt;&lt;/a&gt;This actually represents three separate HTTP GET requests; the index.html file, the skull image, and the tiny skull favicon shown on the tab.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_k-xgcnlJm1E/THFvk3y4uJI/AAAAAAAAAKI/M_cdtwe2X5w/s1600/Screenshot-view-source:http:--localhost:7777-+-+Chromium.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 210px;" src="http://3.bp.blogspot.com/_k-xgcnlJm1E/THFvk3y4uJI/AAAAAAAAAKI/M_cdtwe2X5w/s400/Screenshot-view-source:http:--localhost:7777-+-+Chromium.png" alt="" id="BLOGGER_PHOTO_ID_5508306498494183570" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The skull is a compressed SVG, to get Chromium to display it I had to add Content-encoding parameter to my HTTP Response;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_k-xgcnlJm1E/THFvpnqdfZI/AAAAAAAAAKQ/gVdPt-SLIbc/s1600/Screenshot-Developer+Tools+-+http:--localhost:7777-.png"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 274px;" src="http://3.bp.blogspot.com/_k-xgcnlJm1E/THFvpnqdfZI/AAAAAAAAAKQ/gVdPt-SLIbc/s400/Screenshot-Developer+Tools+-+http:--localhost:7777-.png" alt="" id="BLOGGER_PHOTO_ID_5508306580063223186" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-5306933094299349202?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/5306933094299349202/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=5306933094299349202' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5306933094299349202'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5306933094299349202'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/08/http-server.html' title='HTTP Server'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_k-xgcnlJm1E/THFvcu0mDMI/AAAAAAAAAKA/qgaz-3VQH40/s72-c/Screenshot-Hello+World+-+Chromium-1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-2959290447871110870</id><published>2010-08-22T10:18:00.000-07:00</published><updated>2010-08-22T10:53:02.197-07:00</updated><title type='text'>Netboa</title><content type='html'>I've started the total re-write of my asynchronous network library &lt;a href="http://code.google.com/p/miniboa/"&gt;Miniboa&lt;/a&gt;.  The new code, in addition to being cleaner, is different in several ways.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;What's New&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;No longer Telnet-centric; my aim is to keep connections generic and wrap them in protocol handlers.&lt;/li&gt;&lt;li&gt;Services; you can now run multiple services under a single server.   Each service listens on a different port and can have unique protocols  and events.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Added hooks for coroutine-based, character-at-a-time protocol handlers.&lt;/li&gt;&lt;li&gt;No longer line-based; the new API has &lt;span style="font-weight: bold;"&gt;get_ch() &lt;/span&gt;to get one character or &lt;span style="font-weight: bold;"&gt;get_input()&lt;/span&gt; to retrieve the entire input buffer.  This is support users that want to write features like text editors and command completion.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;New event; &lt;span style="font-weight: bold;"&gt;on_input()&lt;/span&gt; that is called when the data is received from the client.&lt;/li&gt;&lt;li&gt;When run under Linux, it uses the &lt;a href="http://www.kernel.org/doc/man-pages/online/pages/man4/epoll.4.html"&gt;&lt;span style="font-weight: bold;"&gt;Epoll&lt;/span&gt;&lt;/a&gt; API introduced in Linux kernel  2.5.44.  All other platforms user&lt;span style="font-weight: bold;"&gt; Select()&lt;/span&gt;.    &lt;/li&gt;&lt;/ul&gt;I kept the server polled from within the user's game loop.  I know that event and callback driven designs are more efficient, but I really like the approachability of that pattern.  It can be hard to think in terms of callbacks.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-2959290447871110870?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/2959290447871110870/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=2959290447871110870' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/2959290447871110870'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/2959290447871110870'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/08/netboa.html' title='Netboa'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-4536976613943653514</id><published>2010-05-02T18:38:00.001-07:00</published><updated>2010-05-02T19:16:01.650-07:00</updated><title type='text'>The Plague that struck Azeroth</title><content type='html'>I ran across some old screen-shots from &lt;a href="http://en.wikipedia.org/wiki/Corrupted_Blood_incident"&gt;an event that occurred in the  MMO World of Warcraft on September, 13 2005&lt;/a&gt; after the opening of a  dungeon called Zul'Gurub.  One of the encounters involved a  damage-over-time spell called Corrupted Blood that spread from the  victim to anyone nearby.  It was designed to be contained within the  instance but players found that they could dismiss infected pets and  later recall them with the effect still running, &lt;span style="font-style: italic;"&gt;and massively contagious&lt;/span&gt;,  regardless of where they went.&lt;br /&gt;&lt;br /&gt;This is what the main Horde city  of Orgrimmar looked like;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_k-xgcnlJm1E/S94ruUVOpSI/AAAAAAAAAIw/JGPQJXhc_n8/s1600/WoWScrnShot_091605_214228.jpg"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 320px;" src="http://4.bp.blogspot.com/_k-xgcnlJm1E/S94ruUVOpSI/AAAAAAAAAIw/JGPQJXhc_n8/s400/WoWScrnShot_091605_214228.jpg" alt="" id="BLOGGER_PHOTO_ID_5466855072406086946" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_k-xgcnlJm1E/S94rtq7ZWVI/AAAAAAAAAIo/lQsaN75zMJ4/s1600/WoWScrnShot_091605_213911.jpg"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 320px;" src="http://4.bp.blogspot.com/_k-xgcnlJm1E/S94rtq7ZWVI/AAAAAAAAAIo/lQsaN75zMJ4/s400/WoWScrnShot_091605_213911.jpg" alt="" id="BLOGGER_PHOTO_ID_5466855061291882834" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_k-xgcnlJm1E/S94rtU3KwrI/AAAAAAAAAIg/AwtJOfpvXAg/s1600/WoWScrnShot_091605_213727.jpg"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 320px;" src="http://4.bp.blogspot.com/_k-xgcnlJm1E/S94rtU3KwrI/AAAAAAAAAIg/AwtJOfpvXAg/s400/WoWScrnShot_091605_213727.jpg" alt="" id="BLOGGER_PHOTO_ID_5466855055368569522" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_k-xgcnlJm1E/S94rsicFvQI/AAAAAAAAAIY/_H26s0Ibseo/s1600/WoWScrnShot_091605_213620.jpg"&gt;&lt;img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 320px;" src="http://4.bp.blogspot.com/_k-xgcnlJm1E/S94rsicFvQI/AAAAAAAAAIY/_H26s0Ibseo/s400/WoWScrnShot_091605_213620.jpg" alt="" id="BLOGGER_PHOTO_ID_5466855041833221378" border="0" /&gt;&lt;/a&gt;Like Morpheus said about The Matrix, this world was built on rules and it was fascinating to see those rules play out in completely unintended ways.  Low level players running into town would die in a tick or two.  Anyone high enough to survive the intitial damage only helped to spread it farther, including NPCs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-4536976613943653514?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/4536976613943653514/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=4536976613943653514' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/4536976613943653514'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/4536976613943653514'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/05/plague-that-struck-azeroth.html' title='The Plague that struck Azeroth'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_k-xgcnlJm1E/S94ruUVOpSI/AAAAAAAAAIw/JGPQJXhc_n8/s72-c/WoWScrnShot_091605_214228.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-912656001630707037</id><published>2010-04-19T17:03:00.000-07:00</published><updated>2010-04-19T17:11:59.782-07:00</updated><title type='text'>Not Actually Dead</title><content type='html'>To the 1.2 people who actually follow this blog -- I've taken a new job as a county IT director which I'm enjoying but there's sooooo much stuff to unravel and mentally digest that it may be some time before my head is clear enough to code on my free time again.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-912656001630707037?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/912656001630707037/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=912656001630707037' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/912656001630707037'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/912656001630707037'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/04/not-actually-dead.html' title='Not Actually Dead'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-5842023274263602228</id><published>2010-03-21T09:49:00.000-07:00</published><updated>2010-03-22T05:34:11.763-07:00</updated><title type='text'>Thinking about Miniboa 2.0</title><content type='html'>In the early 90s, my hobby was running a computer bulletin board system (BBS) in Virginia called&lt;span style="font-style: italic;"&gt; Grendel's Place&lt;/span&gt;.  I started with a free package called &lt;a href="http://en.wikipedia.org/wiki/RBBS-PC"&gt;RBBS&lt;/a&gt; and then bought a license for the more spiffy &lt;a href="http://en.wikipedia.org/wiki/TriBBS"&gt;TriBBS&lt;/a&gt;.  Like the joke said; &lt;span style="font-style: italic;"&gt;a sysop is someone who spends thousands of dollars to let other people use his computer&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;I spent a lot of time writing utilities and experimental games with Borland's Turbo C compiler.  Man, I loved that program.  Eventually, I bought their C++ compiler for Windows but the combination of the awful C++ language and Microsoft's hideous Foundation Class Library killed my hobby programming for years.&lt;br /&gt;&lt;br /&gt;BBSes had a wealth of &lt;a href="http://en.wikipedia.org/wiki/Category:Door_games"&gt;Door&lt;/a&gt; games, which were external programs that the BBS passed control of the serial port to while the user played them.  Some of these were, like &lt;a href="http://en.wikipedia.org/wiki/TradeWars_2002"&gt;Tradewar 2000&lt;/a&gt; and &lt;a href="http://en.wikipedia.org/wiki/Solar_Realms_Elite"&gt;Solar Realms Elite&lt;/a&gt; were incredibly compelling.  Since the typical BBS had only a single phone line, these  games tended to be turn-based with only so many moves allotted to each player per day.&lt;br /&gt;&lt;br /&gt;Having started to work with Telnet, I immediately lamented the absence of two features from my days coding for a BBS.  The first was IBM's extended character set that let you draw lines and blocks.  Check out this screen from &lt;a href="http://www.johndaileysoftware.com/products/bbsdoors/globalwar/"&gt;Global War&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_k-xgcnlJm1E/S6aTaVFl0KI/AAAAAAAAAIQ/fngCA-hMK9Q/s1600-h/img-screen_gwarafrica.gif"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 250px;" src="http://3.bp.blogspot.com/_k-xgcnlJm1E/S6aTaVFl0KI/AAAAAAAAAIQ/fngCA-hMK9Q/s400/img-screen_gwarafrica.gif" alt="" id="BLOGGER_PHOTO_ID_5451206479525040290" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The second thing I missed was ANSI.SYS, a DOS driver from Microsoft that controlled character color and cursor placement.  Most BBS clients either used ANSI.SYS directly or as the standard against their own implementation.  You could safely make assumptions about the capability of your visitor's terminal and do neat things.&lt;br /&gt;&lt;br /&gt;Telnet clients, on the other hand, are &lt;span style="font-weight: bold;font-size:130%;" &gt;horrible&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;They are &lt;span style="font-weight: bold;"&gt;horrible&lt;/span&gt; in two ways -- actually implementing the Telnet protocol and supporting those same ANSI control codes.  Feature support across the various clients is so awful and quirky that it's impossible to rely on anything but the shallowest of implementations --  even little things like setting the background color.&lt;br /&gt;&lt;br /&gt;While writing Miniboa, I scaled back many of my aspirations and focused on two areas; things that mostly worked regardless of client and things that were harmless if they failed.  If your client supported NAWS, you got 'on-the-fly' terminal sizing.  If not, you got 80 columns.  This is one of the reasons Miniboa is line-based (as in, it gives you an entire line of client input rather than keystrokes).  It worked by default on every client I tested.&lt;br /&gt;&lt;br /&gt;Another reason I went line-based was I'm toying with the idea of writing a Jabber/XMPP server to support play from instant messaging and wanted to keep my interface to the clients duck-typed; &lt;span style="font-weight: bold;"&gt;client.get_cmd()&lt;/span&gt; and &lt;span style="font-weight: bold;"&gt;client&lt;/span&gt;.&lt;span style="font-weight: bold;"&gt;send()&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Whew, that's too much background eh?&lt;br /&gt;&lt;br /&gt;OK, OK -- Miniboa 2.0.  I had a request to add character support to Miniboa and realized there was no way to do it without some really ugly hacks and the right thing(tm) was to decouple, decouple, decouple.  Currently, Telnet is handled via a do-it-all class that stores the Socket object as a member property and performs a lot of state juggling.  I think I want to switch things over to nested coroutines.  Where the inner ones perform progressively lower level IO.   &lt;br /&gt;&lt;br /&gt;David Beazley, author of the excellent &lt;a href="http://www.amazon.com/gp/product/0672329786/"&gt;Python Essential Reference&lt;/a&gt;, gave a lecture entitled &lt;a href="http://www.dabeaz.com/coroutines/"&gt;A Curious Course on Coroutines and Concurrency&lt;/a&gt; at PyCon 2009.  You can watch a video of it here:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://blip.tv/file/1996136/"&gt;Part 1&lt;/a&gt;&lt;br /&gt;&lt;a href="http://blip.tv/file/1995823/"&gt;Part 2&lt;/a&gt;&lt;br /&gt;&lt;a href="http://blip.tv/file/1995872/"&gt;Part 3&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Another development comes from Confuto and Stendec who have kindly donated code for implementing WebSocket and Flash servers with Miniboa.  This is pretty exciting because of the potential for rich clients that both represent.  Plus if I do write a Jabber server, it would benefit from the decoupling process as well.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-5842023274263602228?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/5842023274263602228/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=5842023274263602228' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5842023274263602228'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5842023274263602228'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/03/thinking-about-miniboa-20.html' title='Thinking about Miniboa 2.0'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_k-xgcnlJm1E/S6aTaVFl0KI/AAAAAAAAAIQ/fngCA-hMK9Q/s72-c/img-screen_gwarafrica.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-8423879634089349313</id><published>2010-02-16T08:07:00.000-08:00</published><updated>2010-02-24T08:24:19.997-08:00</updated><title type='text'>Character Skills</title><content type='html'>Now that I've covered &lt;a href="http://bogboa.blogspot.com/2010/01/character-stats.html"&gt;Stats&lt;/a&gt;, I wanted to talk about &lt;span style="font-weight: bold;"&gt;Character Skills&lt;/span&gt; but let's start with a clear definition since &lt;span style="font-style: italic;"&gt;skill&lt;/span&gt; has multiple meanings in the context of computer RPGs;&lt;br /&gt;&lt;blockquote&gt;In Bogboa, &lt;span style="font-weight: bold;"&gt;Skill&lt;/span&gt; represents the actor's &lt;span style="font-style: italic;"&gt;measure of talent&lt;/span&gt; when it comes to performing abilities within given categories.&lt;br /&gt;&lt;/blockquote&gt;So a player attempting to disarm a trap might rely on an &lt;span style="font-style: italic;"&gt;comprehend_traps&lt;/span&gt; skill to provide the base number for the success check.  Building on the stat post, that base number might be bumped up a few percent if they possess lots of &lt;span style="font-style: italic;"&gt;cunning&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Skill Modifiers&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The idea is to maintain a list of &lt;span style="font-style: italic;"&gt;skill modifiers &lt;/span&gt;that vary per guild (character class).  The &lt;span style="font-style: italic;"&gt;rogue guild &lt;/span&gt;might have a &lt;span style="font-style: italic;"&gt;comprehend_traps &lt;/span&gt;modifier of 1.0 while the &lt;span style="font-style: italic;"&gt;warrior guild&lt;/span&gt; has .01.  This results in a warrior having 1/10th of the trap removing potential of a rogue.  If we set it to zero, warriors will always fail regardless of level and cunning.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_k-xgcnlJm1E/S3rvUkh5UCI/AAAAAAAAAH8/UVxiFqp1ccM/s1600-h/dp1790706.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 146px;" src="http://4.bp.blogspot.com/_k-xgcnlJm1E/S3rvUkh5UCI/AAAAAAAAAH8/UVxiFqp1ccM/s400/dp1790706.jpg" alt="" id="BLOGGER_PHOTO_ID_5438922636685561890" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;You can imagine these modifiers like the sliders on an equalizer as we use them to control the relative power of each guild.  Creating hybrid classes would be a simple matter of setting a ratio for primary &lt;span style="font-style: italic;"&gt;skill modifiers&lt;/span&gt; -- a paladin might have a &lt;span style="font-style: italic;"&gt;healing&lt;/span&gt; skill of .65 and a &lt;span style="font-style: italic;"&gt;combat&lt;/span&gt; skill of .85 as compared to a cleric's (1.0, 0.5) and a warrior's (0.0, 1.0).  If a guild does not list a given skill we treat the modifier as zero.&lt;br /&gt;&lt;br /&gt;The potential power of a skill modifier is immense and, like a thermostat that goes to a billion, should be used great care.  It's my intention to stick very close to 1.0 as the maximum modifier value*.  This keeps power ratios cleanly defined across guilds and relative to mob level.  Let's contrive an example to illustrate why this is probably a good idea.  We have a designer that wants to create a Cavalier Guild and decides they're like warriors but better with swords.  Assuming the warrior had 1.0 sword skill modifier, he gives his cavalier 1.2.  Now they are 20% deadlier than mobs their level -- a level 40 cavalier attacks like he's 8 levels above his opponent.  This makes them too easy, so our designer bumps up NPCs to compensate and now every other player guild has a rougher time of it.&lt;br /&gt;&lt;br /&gt;The designer could try to balance this offense power with lower defense but it's tricky.  Sustained damage output sets the rate of experience gain for the character.  Unless you implement some form of downtime, &lt;span style="font-style: italic;"&gt;almost dying&lt;/span&gt; is meaningless.   &lt;br /&gt;&lt;br /&gt;* An exception to this would be Wizard Guild defense and health modifiers, which will be something like 100000.0 to make them immortal.  Conversely, their offensive skills should be 0.0 to discourage abuse.        &lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;br /&gt;Skill Caps&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Skills are capped at; &lt;pre&gt;actor_level * 10 * guild_modifier&lt;/pre&gt;Not much needs explained here; a level 20 warrior with a 1.0 combat modifier will have a maximum combat skill of 200.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Raising Skills&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;There are many ways to handle this but I've always been partial to raising skills through using them.  Most of the skill rating functions (see below) have an optional &lt;span style="font-weight: bold;"&gt;tutor&lt;/span&gt; argument.  If this is set to &lt;span style="font-weight: bold;"&gt;True&lt;/span&gt; and the player's current skill is not maxed, he gets a chance to increase his current skill during the check.  The chance becomes progressively lower as the player closes in on his current max skill.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Rating Skills&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;This is the meat and potatoes of the skill system.  Using a group of functions, we rate an actor's skill by looking up the current value and applying any stat related bonuses or penalties.  The result is a &lt;span style="font-weight: bold;"&gt;rating&lt;/span&gt;: a numeric value used for comparisons and success/failure checks.  Some ratings are derived by combining other ratings.  For example, a player's &lt;span style="font-weight: bold;"&gt;hit rating &lt;/span&gt;is the average of his &lt;span style="font-weight: bold;"&gt;melee combat rating &lt;/span&gt;and the specific &lt;span style="font-weight: bold;"&gt;weapon rating&lt;/span&gt; for the type of weapon he's carrying.  This is to accommodate scenarios like a level 20 warrior who has never touched a mace suddenly picking one up.  Even though his skill in blunt weapons is zero, his melee combat skill provides &lt;span style="font-style: italic;"&gt;some&lt;/span&gt; chance of landing a hit.&lt;br /&gt;&lt;br /&gt;It's a small shift it perspective, but thinking in terms of skill ratings was something I found helpful.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Application&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;We've already covered that we'd use skill ratings for deciding success but not how those rolls will be calculated.  Here's an example for striking with the player's primary weapon.  Basically, we pick a random number in the range of 0 to (defender's defense rating + attacker's hit rating ).  If the number is higher than the defender's defense, it is a hit.  You can see how either value grows, probability will shift accordingly but the attack always has a chance to hit. &lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="py"&gt;  &lt;br /&gt;def primary_weapon_test(src, tar):&lt;br /&gt;    """&lt;br /&gt;    Test for a hit using Source's primary melee weapon.&lt;br /&gt;    """&lt;br /&gt;    hit = skill.primary_hit_rating(src)&lt;br /&gt;    defend = skill.defense_rating(tar)&lt;br /&gt;    return bool(random.randrange(defend + hit) &gt; defend)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;One of my goals is to avoid hard-coding rules as much as possible.  So rather than telling a magician he cannot use plate armor, I'd rather use his guild's skill modifiers to provide more benefit and AC from a robe but, if he really wants to, he can dress up in heavy plate.  &lt;span style="font-style: italic;"&gt;&lt;/span&gt; Same thing for weapons.&lt;br /&gt;&lt;br /&gt;Similarly, learned abilities wont be guild specific but will require certain skill values to obtain.  A potent healing spell might be available to a cleric at level 10, a druid at 20, and a templar at 40 depending on their &lt;span style="font-style: italic;"&gt;healing_magic &lt;/span&gt;modifiers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-8423879634089349313?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/8423879634089349313/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=8423879634089349313' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/8423879634089349313'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/8423879634089349313'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/02/character-skills.html' title='Character Skills'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_k-xgcnlJm1E/S3rvUkh5UCI/AAAAAAAAAH8/UVxiFqp1ccM/s72-c/dp1790706.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-2256981989548419264</id><published>2010-01-30T09:03:00.000-08:00</published><updated>2010-01-30T10:36:34.009-08:00</updated><title type='text'>Character Stats</title><content type='html'>I have finally settled on a set of character stats to use for the game.  I've been torn over using a generic (some may say tired) but immediately recognizable D&amp;amp;D inspired set like STR, DEX, INT, WIS, CON, CHR versus making up completely different ones with the risk they'd seem needlessly exotic and labored.&lt;br /&gt;&lt;br /&gt;I wanted to split them evenly between physical and mental attributes with as little overlap as possible.  For example, I've always found &lt;span style="font-style: italic;"&gt;dexterity&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;agility&lt;/span&gt; awkwardly close to each other.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;Physical&lt;/span&gt; &lt;span style="font-weight: bold;"&gt;Stats&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Brawn&lt;/span&gt; - strength, heartiness, and balance.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Vigor&lt;/span&gt; - stamina, pain tolerance, and body recuperation rate.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Precision&lt;/span&gt; - dexterity, grace, accuracy, and physical reaction time.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Mental Stats&lt;/span&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Knowledge&lt;/span&gt; - memory, education, and life experience (street smarts).&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Faith&lt;/span&gt; - confidence, willpower, zealotry, and mental recuperation rate.&lt;/li&gt;&lt;li&gt;&lt;span style="font-weight: bold;"&gt;Cunning&lt;/span&gt; - intelligence, ingenuity, and mental reaction time.&lt;/li&gt;&lt;/ul&gt;An even split meant either 4, 6, or 8 stats -- 4 was too few and 8 was too many.  We want different character guilds/classes to prize different stat combinations without things getting out of hand.  Let's also try to make mental attributes appealing to melee classes.  Monks should want &lt;span style="font-weight: bold;"&gt;faith&lt;/span&gt; and rogues &lt;span style="font-weight: bold;"&gt;cunning&lt;/span&gt;.  &lt;span style="font-weight: bold;"&gt;Faith&lt;/span&gt; might seem like an odd word but it will fit in the setting I'm planning.  Lastly, let's drop the wisdom versus intelligence caster meme. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Application&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;For every 10 points in a given stat, the character will receive a 1% bonus towards related skills.  In the case of negative stats, characters will suffer a 1% penalty per negative stat point.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-2256981989548419264?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/2256981989548419264/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=2256981989548419264' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/2256981989548419264'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/2256981989548419264'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/01/character-stats.html' title='Character Stats'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-4329566470060535636</id><published>2010-01-27T08:09:00.000-08:00</published><updated>2010-01-27T08:15:35.824-08:00</updated><title type='text'>Key-Value Data Loading</title><content type='html'>I wanted to follow up with an example of loading an avatar's persistent dictionaries from a key-value store in the SQLite database;&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="py"&gt;&lt;br /&gt;def load_account(client, name, uuid):&lt;br /&gt;    """&lt;br /&gt;    load a previously created account.&lt;br /&gt;    """&lt;br /&gt;    client.send('\nWelcome back, %s. Your last visit was %s.\n\n' %&lt;br /&gt;         (name, last_on(name)))&lt;br /&gt;    ## Create an in-game Avatar from stored data&lt;br /&gt;    avatar = Avatar(client)&lt;br /&gt;    ## Read from database&lt;br /&gt;    avatar.profile = fetch_kv_dict(uuid, 'profile')&lt;br /&gt;    avatar.resources = fetch_kv_dict(uuid, 'resources')&lt;br /&gt;    avatar.skills = fetch_kv_dict(uuid, 'skills')&lt;br /&gt;    avatar.worn = fetch_kv_dict(uuid, 'worn')&lt;br /&gt;    avatar.carried = fetch_kv_dict(uuid, 'carried')&lt;br /&gt;    avatar.abilities.update(fetch_kv_set(uuid, 'abilities'))&lt;br /&gt;    play_account(avatar)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Just in case it wasn't clear.  You may notice an absence of a '&lt;span style="font-weight: bold;"&gt;stats&lt;/span&gt;' dictionary.  Character stats will be computed based on race + gear, so aren't saved.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-4329566470060535636?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/4329566470060535636/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=4329566470060535636' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/4329566470060535636'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/4329566470060535636'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/01/key-value-data-loading.html' title='Key-Value Data Loading'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-6312525578981867334</id><published>2010-01-27T07:43:00.000-08:00</published><updated>2010-01-27T07:47:21.531-08:00</updated><title type='text'>Persistence</title><content type='html'>I've had a few questions about how I plan to handle persistence, as in saving the states of game objects in case the server is restarted.  The quick answer is I'm not -- with the exception of player character data.  Bogboa is using two forms of data storage; YAML files and an SQLite database.  I'll discuss each. &lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;YAML Files&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Currently, YAML is the only third-party library used in Bogboa.  I didn't want to use any really, but YAML is such a human friendly format that I went with it.&lt;br /&gt;&lt;br /&gt;On startup, the world is created from one or more directory trees filled with YAML files that define individual rooms, items, races, etc.  This is a one-way conversion where each YAML file is parsed into a Python dictionary that is used to set attributes of game classes.  One of my long-term goals is to support reading these YAML trees from zip files so that designers could release adventure modules that contain complete dungeons with new monsters and items that load seamlessly into existing game worlds.  This is one of the reasons I'm using UUID's for keys, since they don't require multiple developers to coordinate or negotiate around potential collisions like vnums do.&lt;br /&gt;&lt;br /&gt;So restarting the server cleans every floor of dropped loot and every dead NPC is alive again.  This appeals to me because it's simple and troubleshooting scripts is easier from known states. &lt;br /&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-weight: bold;"&gt;SQLite Database&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Starting in Python 2.5, SQLite3 is part of the standard library (and a kick-ass little SQL database) which makes using it a non-brainer.&lt;br /&gt;&lt;br /&gt;My early tables were pretty vanilla with columns; (name, race, level, guild, stat1, stat2, etc) and I was always changing them.  Also, I was using a lot of sub-tables to hold information like skills, inventory, and character flags.  These sub-tables all had nearly identical structure; (character_uuid, skill_name) and (character_uuid, inventory_slot).&lt;br /&gt;&lt;br /&gt;At the same time, I was making structural changes to how I split player from their avatars (which is why the codebase is currently b0rked).  I knew that the closer I handled avatars like NPCs the easier things would be down the road.  Moving all the various traits into dictionaries was helpful and this transition to key-value thinking migrated to how I stored character data as well.&lt;br /&gt;&lt;br /&gt;What I wanted was a way to load whole dictionaries at a time but update individual key-value pairs on the fly.  When you log in, your character's profile dictionary is retrieved containing (name, race, guild, level, ...) and when you gain a level, your characters (profile + level) record is incremented by one and if the server crashes a moment later you, hopefully, didn't lose it.  &lt;br /&gt;&lt;br /&gt;One of the nuisances of SQL is you often have to do a SELECT before you can save data because sometimes you cannot be sure if you need to use the INSERT or the UPDATE command.  SQLite supports a non-standard but convenient variation of this called INSERT OR UPDATE.  The catch is the key has to be unique.&lt;br /&gt;&lt;br /&gt;What I did was make the key = (Character UUID + Some Catergory Name + Key Name);&lt;br /&gt;&lt;pre name="code" class=""&gt;&lt;br /&gt;CREATE TABLE IF NOT EXISTS key_value&lt;br /&gt;    (&lt;br /&gt;    uuid TEXT,&lt;br /&gt;    category TEXT,&lt;br /&gt;    key TEXT,&lt;br /&gt;    value TEXT,&lt;br /&gt;    PRIMARY KEY(uuid, category, key)&lt;br /&gt;    );&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In the above example, the category name would be 'profile'.&lt;br /&gt;&lt;br /&gt;Reading all the rows of a category would look like;&lt;br /&gt;&lt;pre name="code" class=""&gt;&lt;br /&gt;SELECT key, value&lt;br /&gt;FROM key_value&lt;br /&gt;WHERE uuid = ? AND category = ?;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;And saving an individual key-value would look like;&lt;br /&gt;&lt;pre name="code" class=""&gt;&lt;br /&gt;INSERT OR REPLACE INTO key_value (uuid, category, key, value)&lt;br /&gt;VALUES (?, ?, ?, ?);&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-6312525578981867334?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/6312525578981867334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=6312525578981867334' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/6312525578981867334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/6312525578981867334'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/01/persistence.html' title='Persistence'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-4377451813459739977</id><published>2010-01-16T10:22:00.000-08:00</published><updated>2010-01-16T10:24:57.286-08:00</updated><title type='text'>Everything I need to know I learned from D&amp;D</title><content type='html'>Clever presentation by a guy who compares software development to playing Dungeons and Dragons:&lt;br /&gt;&lt;br /&gt;&lt;object width="400" height="225"&gt;&lt;param name="allowfullscreen" value="true"&gt;&lt;param name="allowscriptaccess" value="always"&gt;&lt;param name="movie" value="http://vimeo.com/moogaloop.swf?clip_id=8755360&amp;amp;server=vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=&amp;amp;fullscreen=1"&gt;&lt;embed src="http://vimeo.com/moogaloop.swf?clip_id=8755360&amp;amp;server=vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=&amp;amp;fullscreen=1" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="400" height="225"&gt;&lt;/embed&gt;&lt;/object&gt;&lt;p&gt;&lt;a href="http://vimeo.com/8755360"&gt;All I Need To Know About Life I learned From Dungeons and Dragons. An IgniteOKC Talk.&lt;/a&gt; from &lt;a href="http://vimeo.com/user406937"&gt;Chad Henderson&lt;/a&gt; on &lt;a href="http://vimeo.com"&gt;Vimeo&lt;/a&gt;.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-4377451813459739977?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/4377451813459739977/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=4377451813459739977' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/4377451813459739977'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/4377451813459739977'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/01/everything-i-need-to-know-i-learned.html' title='Everything I need to know I learned from D&amp;D'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-5979282731072029365</id><published>2010-01-07T16:14:00.000-08:00</published><updated>2010-05-02T19:03:35.128-07:00</updated><title type='text'>MUD Game Programming</title><content type='html'>I believe the only book ever written as &lt;span style="font-style: italic;"&gt;howto&lt;/span&gt; for creating a MUD was &lt;a href="http://www.amazon.com/MUD-Game-Programming-Development/dp/1592000908"&gt;MUD Game Programming&lt;/a&gt; by Ron Pelton.  Originally published in late 2003, it's been out of print for a while and I finally managed to snag a used copy for a reasonable price.  It even came with an unopened CD.&lt;br /&gt;&lt;br /&gt;I don't expect to glean a great deal from it having already written an asynchronous server, plus Pelton is using C++ where I'm doing everything in Python.  Still, it seems like a quality book with a lot of example code.  Books on game programming tend to suffer a poor signal to noise ratio.  There's a ton of dreck out there, especially garbage written by industry &lt;span style="font-style: italic;"&gt;insiders&lt;/span&gt; who have no clue about programming but feel like they can help you with their experience interviewing other non-programmers.  Ugh.  Even worse are the ones aimed at teens -- clearly targeting the &lt;span style="font-style: italic;"&gt;well-intentioned but useless gift from Mom&lt;/span&gt; category.&lt;br /&gt;&lt;br /&gt;Pardon the rant.&lt;br /&gt;&lt;br /&gt;Anyways, I've read the first couple chapters on networking and socket programming and Pelton is delivering the goods.  He is specific and writes in an friendly, engaging manner.  He gets extra points for covering Linux in addition to Windows.   Recommended (if you can find it).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-5979282731072029365?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/5979282731072029365/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=5979282731072029365' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5979282731072029365'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5979282731072029365'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/01/mud-game-programming.html' title='MUD Game Programming'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-7503413631678473315</id><published>2010-01-02T08:58:00.000-08:00</published><updated>2010-01-06T09:43:08.983-08:00</updated><title type='text'>Work Around</title><content type='html'>--start of edit&lt;br /&gt;&lt;br /&gt;Well, it turned out that the approach listed below does not find methods from base classes.  I revised it to use __getattribute__() instead which returns a bound instance of the class but that only lives for the scope of the cmd_driver() method:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="py"&gt;&lt;br /&gt;def cmd_driver(self):&lt;br /&gt;    ## call the driver method for current state&lt;br /&gt;    self.__getattribute__(self.state)()&lt;br /&gt;&lt;/pre&gt;---end of edit&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;My last post detailed a garbage collection issue I was having when an instance of the User class referenced itself using a bound variable.  I got around it changing my state property to a &lt;span style="font-style:italic;"&gt;string&lt;/span&gt; variable holding the &lt;span style="font-style:italic;"&gt;name&lt;/span&gt; of the method I wanted to call and then calling said method via some class introspection;&lt;br /&gt;&lt;pre name="code" class="py"&gt;def cmd_driver(self):&lt;br /&gt;    ## call the driver method for current state via class introspection&lt;br /&gt;    self.__class__.__dict__[self.state](self)&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That kind of introspection always feels like duct tape.&lt;br /&gt;&lt;br /&gt;I should point out that Python's garbage collector &lt;span style="font-style:italic;"&gt;probably&lt;/span&gt; would have deleted those User instances eventually and that I could have manually ended the connection by calling &lt;span style="font-weight:bold;"&gt;socket.close()&lt;/span&gt;.  I prefer the immediate garbage collection closure because it's an indicator that everything is running smoothly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-7503413631678473315?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/7503413631678473315/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=7503413631678473315' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/7503413631678473315'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/7503413631678473315'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2010/01/work-around.html' title='Work Around'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-183344491047086687</id><published>2009-12-23T14:15:00.000-08:00</published><updated>2009-12-23T14:53:14.906-08:00</updated><title type='text'>Dynamic Methods and Garbage Collecting</title><content type='html'>I really enjoy the Python programming language.  Even when it bites you, it takes you on an interesting journey.&lt;br /&gt;&lt;br /&gt;In the MUD, I use the term &lt;span style="font-weight: bold;"&gt;Client&lt;/span&gt; to refer to the session with the user at the other end of the wire.  The &lt;span style="font-weight: bold;"&gt;Client&lt;/span&gt; is passed to a &lt;span style="font-weight: bold;"&gt;User&lt;/span&gt; object that represents the player in some state or another.   When a &lt;span style="font-weight: bold;"&gt;User&lt;/span&gt; gets deleted it gets chomped up by Python's garbage collector which should delete the &lt;span style="font-weight: bold;"&gt;client&lt;/span&gt; which in turn should delete the &lt;span style="font-weight: bold;"&gt;socket&lt;/span&gt; which fires the socket's &lt;span style="font-weight: bold;"&gt;close()&lt;/span&gt; method on the way out and gives the cat a fish, err, drops the user resulting in the famous  '&lt;span style="font-style: italic;"&gt;Connection closed by foreign host..&lt;/span&gt;'&lt;br /&gt;&lt;br /&gt;Only it wasn't.&lt;br /&gt;&lt;br /&gt;Deleted clients and "disconnected" users were hanging around typing to unmonitored, uncaring sockets.  After a bit of poking I found the problem was some cheap hackery I was using for the login process.  I was using a property called "cmd_driver" like a state machine, changing it to point to the next method I wanted input to go;&lt;br /&gt;&lt;pre class="python"&gt;self.cmd_driver = self.get_password&lt;/pre&gt;Turns out, this was setting cmd_driver  to a &lt;span style="font-style: italic;"&gt;bound method;&lt;/span&gt; one that is wrapped inside a reference to the an instance of the class, which in this case was itself.  This caused the reference count to increase by one and avoid the garbage collector.&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_k-xgcnlJm1E/SzKcOMrRrlI/AAAAAAAAAHE/HakUpULaIM0/s1600-h/i-am-with-stupid-t-shirt-cafepress.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 350px; height: 350px;" src="http://2.bp.blogspot.com/_k-xgcnlJm1E/SzKcOMrRrlI/AAAAAAAAAHE/HakUpULaIM0/s400/i-am-with-stupid-t-shirt-cafepress.jpg" alt="" id="BLOGGER_PHOTO_ID_5418565069414968914" border="0" /&gt;&lt;/a&gt;Here's a demonstration snippet;&lt;br /&gt;&lt;pre name="code" class="py"&gt;&gt;&gt;&gt; class A(object):&lt;br /&gt;...     def __del__(self):&lt;br /&gt;...             print "__del__ called"&lt;br /&gt;...     def foo(self):&lt;br /&gt;...             pass&lt;br /&gt;...&lt;br /&gt;&gt;&gt;&gt; a = A()&lt;br /&gt;&gt;&gt;&gt; del a&lt;br /&gt;__del__ called&lt;br /&gt;&gt;&gt;&gt; a2 = A()&lt;br /&gt;&gt;&gt;&gt; a2.bar = a2.foo&lt;br /&gt;&gt;&gt;&gt; del a2&lt;br /&gt;&gt;&gt;&gt; &lt;/pre&gt;&lt;br /&gt;So I need to work out a healthier version of the state machine.&lt;br /&gt;Maybe one that calls unbound methods of the class or instance methods&lt;br /&gt;via some introspection.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-183344491047086687?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/183344491047086687/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=183344491047086687' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/183344491047086687'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/183344491047086687'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2009/12/dynamic-methods-and-garbage-collecting.html' title='Dynamic Methods and Garbage Collecting'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_k-xgcnlJm1E/SzKcOMrRrlI/AAAAAAAAAHE/HakUpULaIM0/s72-c/i-am-with-stupid-t-shirt-cafepress.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-5428996158791267919</id><published>2009-12-18T14:23:00.000-08:00</published><updated>2009-12-18T14:30:39.052-08:00</updated><title type='text'>Random Name Generation</title><content type='html'>A few years ago I was playing around with a random name generator.  My approach was to cobble together random letter combination like;&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;leading consonants + vowels + inner consonants + vowels + closing consonants&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Basically, I was aiming for something pronounceable with commons letters weighted to appear more often.  It produced output such as;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier;"&gt;Votharn Eristacark Iplortidot Birtoil Udaeteahieb Aceastoherk Reloist Tharnog Wasterk Femewelav Ublyrrielic Cekird Owritothol Hoogoh Obloukajarriem Sleebont Niestart Pekev Lirtooth Efentoidagix Klyckas Yryfesat Klooton&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Yeah ... that's really, really awful.&lt;br /&gt;&lt;br /&gt;So I decided to give it another whack.  This time I started with the premise, 'what sounds most like a name?' &lt;br /&gt;&lt;br /&gt;Names do!&lt;br /&gt;&lt;br /&gt;I found a couple files with over a thousand of the most common male and female first names on the &lt;a href="http://www.census.gov/genealogy/names/names_files.html" rel="nofollow" onclick="window.open(this.href, '_blank'); return false;"&gt;US Census Bureau's web page&lt;/a&gt; and started playing.   I wrote a Python script that used regular expressions to slice a batch of words into three lists;&lt;br /&gt;&lt;br /&gt;List 1 = Zero or more vowels + One or more consonants at the start of the word&lt;br /&gt;List 2 = One or more vowels + One or more consonants inside the word (not at the start or the end).  We can get 0 or more of these patterns depending on the word.&lt;br /&gt;List 3 = One or more vowels + Zero or more consonants at the end of the word.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Side note&lt;/strong&gt;:  If you haven't dug into regular expressions yet I highly recommend you check them out.  I avoided them for years and now they're an essential part of my programmer tool box.  Another big plus is their utility spans multiple languages.&lt;br /&gt;&lt;br /&gt;I also tracked the frequency of each pattern, sorting by most common first and discarding the rares.  Finally, I dumped the output formatted as Python lists that I could paste right into the source of the next script.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="py"&gt;import re&lt;br /&gt;import operator&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;_FILENAME = 'data/elves2.txt'&lt;br /&gt;_CULL = 1&lt;br /&gt;&lt;br /&gt;## Match 0 or more vowels + 1 or more consonants at the start of the word&lt;br /&gt;_LEAD = re.compile(r'^[aeiouy]*(?:qu|[bcdfghjklmnpqrstvwxz])+')&lt;br /&gt;## Match 1 or more vowels + 1 or more consonants inside a word (not start/end)&lt;br /&gt;_INNER = re.compile(r'\B[aeiouy]+(?:qu|[bcdfghjklmnpqrstvwxz])+\B')&lt;br /&gt;# Match 1 or more vowels + 0 or more consonats at the end of a word&lt;br /&gt;_TRAIL = re.compile(r'[aeiouy]+(?:qu|[bcdfghjklmnpqrstvwxz])+$')&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def token_lists(names):&lt;br /&gt;&lt;br /&gt;   lead, inner, tail = {}, {}, {}&lt;br /&gt;&lt;br /&gt;   ## Populate dictionaries; key=pattern, value=frequency&lt;br /&gt;   for name in names:&lt;br /&gt;&lt;br /&gt;       match = re.match(_LEAD, name)&lt;br /&gt;       if match:&lt;br /&gt;           pat = match.group(0)&lt;br /&gt;           count = lead.get(pat,0)&lt;br /&gt;           lead[pat] = count +1&lt;br /&gt;&lt;br /&gt;       matches = re.findall(_INNER, name)&lt;br /&gt;       for pat in matches:&lt;br /&gt;           print pat,&lt;br /&gt;           count = inner.get(pat,0)&lt;br /&gt;           inner[pat] = count +1&lt;br /&gt;&lt;br /&gt;       match = re.search(_TRAIL, name)&lt;br /&gt;       if match:&lt;br /&gt;           pat = match.group(0)&lt;br /&gt;           count = tail.get(pat,0)&lt;br /&gt;           tail[pat] = count +1&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;   ## Convert dicts to a list of tuples in the format (pattern, frequency)&lt;br /&gt;   lead_srt  = sorted(lead.items(),key=operator.itemgetter(1),reverse=True)&lt;br /&gt;   inner_srt  = sorted(inner.items(),key=operator.itemgetter(1),reverse=True)&lt;br /&gt;   tail_srt  = sorted(tail.items(),key=operator.itemgetter(1),reverse=True)&lt;br /&gt;&lt;br /&gt;   ## Build lists of patterns ordered most to least frequent and cull rares&lt;br /&gt;   lead_list = [ x[0] for x in lead_srt if x[1] &gt; _CULL ]&lt;br /&gt;   inner_list = [ x[0] for x in inner_srt if x[1] &gt; _CULL ]&lt;br /&gt;   tail_list = [ x[0] for x in tail_srt if x[1] &gt; _CULL ]&lt;br /&gt;&lt;br /&gt;   return lead_list, inner_list, tail_list&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if __name__ == '__main__':&lt;br /&gt;&lt;br /&gt;   names = open(_FILENAME, 'rt').readlines()&lt;br /&gt;   lead_list, inner_list, tail_list = token_lists(names)&lt;br /&gt;&lt;br /&gt;   print '#', len(lead_list), len(inner_list), len(tail_list)&lt;br /&gt;   print '_LEADS = ', lead_list&lt;br /&gt;   print '_INNERS = ', inner_list&lt;br /&gt;   print '_TAILS = ', tail_list&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Next I used a script to assemble random names from these lists.  Here's the one for male names:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="py"&gt;import random&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;_LEADS =  ['d', 'j', 'm', 'r', 'l', 'w', 'c', 'h', 'g', 'b', 'br', 't', 'k',&lt;br /&gt;   'n', 's', 'cl', 'fr', 'f', 'p', 'st', 'v', 'ch', 'sh', 'gr', 'tr']&lt;br /&gt;_INNERS =  ['er', 'ar', 'el', 'or', 'an', 'ic', 'arr', 'am', 'ol', 'on',&lt;br /&gt;   'al', 'en', 'ill', 'in', 'err', 'and', 'il', 'om', 'et', 'arl', 'ev',&lt;br /&gt;   'ac', 'ust', 'av', 'ert', 'enn', 'ent', 'ath', 'onn', 'it', 'os', 'enc',&lt;br /&gt;   'yr', 'em', 'ist', 'anc', 'arc', 'ich', 'as', 'est']&lt;br /&gt;_TAILS =  ['o', 'on', 'e', 'y', 'ey', 'er', 'in', 'an', 'ie', 'io', 'en',&lt;br /&gt;   'is', 'el', 'us', 'es', 'as', 'ian', 'ed']&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;def namegen():&lt;br /&gt;   syllables = random.randint(0,1)&lt;br /&gt;   if random.random() &gt; .85:&lt;br /&gt;       syllables += 1&lt;br /&gt;   name = random.choice(_LEADS)&lt;br /&gt;   for x in range(syllables):&lt;br /&gt;       name += random.choice(_INNERS)&lt;br /&gt;   name += random.choice(_TAILS)&lt;br /&gt;   return name.title()&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if __name__ == '__main__':&lt;br /&gt;&lt;br /&gt;   for x in range(100):&lt;br /&gt;       print namegen(),&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Which gives output like;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Tyron Frian Grathan Charcan Tren Garrian Stasas Cholian Dites Ris Tie Non Lin Lon Wian Wio Ramas Lo Larus Pan Cio Wasie Mely Chie Levey Bustel Lillen Ben Ponny Panian Nyrus Bre Raso Stel Fasis Konandas Starrin Hian Starlio Rey Jathen Frander Ven Talamio Samin Ches Handey Sterian Nencin Sathon Ponel Citel Momen Wus Rencey Dence Rencan Bevon Jo Bre Tis Non Heved Ne Shestas Lales Ferrus Werrus Foso Standie Ver Gis Giner Ver Nin Res Veled Clon Geler Mustin Gio Bry Juste Shanan Cled Trely Chon Trin Binis Jinen Jacy Ralio Bo Metel De Sian Brie Fanan Donen Jed&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Seeding with female names, I get;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:courier new;"&gt;Alianne Cler Cishel Kroller Namie Dah Kyn Niton Hie Trosancel Cher Na Angy Sestin Jah Dathe Pin Traton Teris Dishon Goren Sheny Frindis Chalin Frie Lolleen Ware Chrena Hy Veannon Dyn Padica Sher Chie Kon Brollis Cisi Gy Chey Kreni Dulani Gancis Shancey Satheen Chreanny Shy Clin Cessia Kriner Shestie Hancah Ner Trorer Ger Tren Ramerrer Freanne Kaurah Frolah Kritah Harley Kisten Eleen Trellyn Chiannel Trosi Alisin Genery Cla Mie Jate Shosacey De Angoron Kis Gianon Mandite Chryn Wer Shadalie Polon Gita Ner Fron Va Chrabica Vatia Clia Tacel Rareen Treen Angia Henen Angin Poreen Eleannel Ken Brer Branca Hesta&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-5428996158791267919?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/5428996158791267919/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=5428996158791267919' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5428996158791267919'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5428996158791267919'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2009/12/random-name-generation.html' title='Random Name Generation'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-1715543277181690490</id><published>2009-12-17T05:36:00.000-08:00</published><updated>2009-12-17T06:18:06.058-08:00</updated><title type='text'>MiniBoa</title><content type='html'>Over on &lt;a href="http://www.mudbytes.net"&gt;Mudbytes&lt;/a&gt;, Idealiad (&lt;a href="http://kooneiform.wordpress.com/"&gt;aka Kooneiform&lt;/a&gt;) started a &lt;a href="http://www.mudbytes.net/index.php?a=topic&amp;amp;t=2044"&gt;thread about creating a Python Socket MUD&lt;/a&gt; -- a very tiny socket library that Python coders could use as the basis for their MUD projects and experiments.  There already were a few of these for other languages such as &lt;a href="http://teensymud.kicks-ass.org/wiki/show/HomePage"&gt;TeensyMud&lt;/a&gt; for Ruby but nothing for Python.  I really liked this idea and, as I had a working Telnet server already, I decided to repackage my network modules as &lt;a href="http://code.google.com/p/miniboa/"&gt;MiniBoa&lt;/a&gt;.  Instead of the BogBoa's GPL licensing, I switched to the more permissive Apache 2.0 (one of the recommended licenses for maximum compatibility with the Python's own).&lt;br /&gt;&lt;br /&gt;To be honest, the code was a lot messier to convert into a stand-alone library than I expected -- because this was the first code I wrote for BogBoa and I was hooking into it less than gracefully.  Since I didn't want to maintain two copies of (mostly) the same modules, I decided to revamp BogBoa to use MiniBoa for networking.&lt;br /&gt;&lt;br /&gt;You can find code and documentation at the &lt;a href="http://code.google.com/p/miniboa/"&gt;project page &lt;/a&gt;and some discussion on this &lt;a href="http://www.mudbytes.net/index.php?a=topic&amp;amp;t=2337"&gt;Mudbytes thread&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-1715543277181690490?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/1715543277181690490/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=1715543277181690490' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/1715543277181690490'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/1715543277181690490'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2009/12/miniboa.html' title='MiniBoa'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-2367718442434531595</id><published>2009-02-04T05:53:00.000-08:00</published><updated>2009-02-04T14:43:09.379-08:00</updated><title type='text'>Days and Nights</title><content type='html'>My first trip through Kithicor Forrest when like so:&lt;br /&gt;&lt;br /&gt;"&lt;span style="font-style: italic;"&gt;A Zombie Trooper begins to cast a spell.&lt;/span&gt;"&lt;br /&gt;"&lt;span style="font-style: italic;"&gt;You have been slain by a Zombie Trooper.&lt;/span&gt;"&lt;br /&gt;&lt;br /&gt;Here I learned the lesson of not trying to pass through Kithicor Forrest at night.  I would have to wait 12 hours for daylight (or 36 actual minutes).  Kithicor is a zone in the online game Everquest.  Days in Everquest are 72 real-world minutes long.&lt;br /&gt;&lt;br /&gt;I've been thinking about how to handle the passage of time.  I knew that I wanted days to be much shorter than real time.  World of Warcraft uses actual time.  As a player, I found EQ's system much more engaging.  It would actually get dark, vendors went inside their homes, werewolves came out, and Kithicor Forrest filled with $*@#&amp;amp; zombie troopers.&lt;br /&gt;&lt;br /&gt;WoW just lowers the gamma a touch.  Boring.  My guess is Blizzard chose real time to simplify the scheduling of in-world events with real-world holidays.  The downside is you can't ask players to wait eight hours while an NPC goes to bed so vendors never sleep.&lt;br /&gt;&lt;br /&gt;I decided to go with Real Time x 12 where a &lt;span style="font-style: italic;"&gt;game&lt;/span&gt; day equals two &lt;span style="font-style: italic;"&gt;real&lt;/span&gt; hours.  That gives us an hour of daylight followed by an hour of night for sneaky rogue-style stuff.&lt;br /&gt;&lt;br /&gt;I also wanted to (roughly) align &lt;span style="font-style: italic;"&gt;game&lt;/span&gt; years to &lt;span style="font-style: italic;"&gt;real&lt;/span&gt; months.  I decided to junk game months entirely since they were only about two and half actual days long.  You've probably seen the &lt;a href="http://www.chinese.new-year.co.uk/calendar.htm"&gt;Chinese Calendar &lt;/a&gt;where they cycle through years named after animals.  That's what I wanted.  Named years that cycle as real months pass.  You get an illusion of years passing contained within an endless, MUD friendly cycle.&lt;br /&gt;&lt;br /&gt;Now, how to implement it?&lt;br /&gt;&lt;br /&gt;Some games use a tick counter that tracks time but my design isn't based around game ticks -- plus I want time to pass even if the server is taken offline.  Worse, I really don't want it to revert to 00:00:00 whenever  I restart it like an unplugged VCR.&lt;br /&gt;&lt;br /&gt;My scheduler was already polling &lt;a href="http://docs.python.org/library/time.html"&gt;time.time()&lt;/a&gt; which returns the number of seconds since Jan 1, 1970.  Since this is an imaginary world, I can get away with a few things.  Real years are about 365 and 1/4 days long, hence the need for leap days.  If I use a solar year instead of a calendar year I can forget about leap days.&lt;br /&gt;&lt;pre name="code" class="py"&gt;&lt;br /&gt;## Number of seconds in a solar year&lt;br /&gt;SOLAR_YEAR = 31556925.215999998&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To align the first game year I decided to define a mini-epoch of January 1st, 2009.  Consulting the &lt;a href="http://www.timestampgenerator.com/"&gt;UNIX timestamp generator&lt;/a&gt; gave me:&lt;br /&gt;&lt;pre name="code" class="py"&gt;&lt;br /&gt;## UNIX_ADJ is used to align the start of game time with Jan 1, 2009 00:00 GMT.&lt;br /&gt;UNIX_ADJ = 1230768000.0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Next, I defined a game cycle that gave me a spot to speed up time in case I want to test things:&lt;br /&gt;&lt;pre name="code" class="py"&gt;&lt;br /&gt;## GAME_CYCLE describes the relation of twelve game years to one real year.&lt;br /&gt;## If you want to speed up or slow down time change the divisor.&lt;br /&gt;GAME_CYCLE = SOLAR_YEAR / 1.0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now, let's define some constants for computing relative days.  As mentioned above, we probably wont use GAME_MONTH, GAME_DAY, or GAME_SECOND for anything.  Instead, we'll describe the date in Julian terms like '&lt;span style="font-style: italic;"&gt;Day 240 of the Year of the Ominous Sounding Noun&lt;/span&gt;'.  In our perfect orbit, game years are 360 game days long.&lt;br /&gt;&lt;pre name="code" class="py"&gt;&lt;br /&gt;GAME_YEAR = GAME_CYCLE / 12.0&lt;br /&gt;GAME_MONTH = GAME_YEAR / 12.0&lt;br /&gt;GAME_DAY = GAME_MONTH / 30.0&lt;br /&gt;GAME_JULIAN = GAME_YEAR / 360.0&lt;br /&gt;GAME_HOUR = GAME_DAY / 24.0&lt;br /&gt;GAME_MINUTE = GAME_HOUR / 60.0&lt;br /&gt;GAME_SECOND = GAME_MINUTE / 60.0&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The calculations are pretty simple.  We can define CENTURY_OFFSET to tweak the displayed century in case you wanted the game to begin in 1100 or Stardate 26,301.&lt;br /&gt;&lt;pre name="code" class="py"&gt;&lt;br /&gt;tstamp = time.time() - UNIX_ADJ&lt;br /&gt;minute = int((tstamp % GAME_HOUR) / GAME_MINUTE)&lt;br /&gt;hour = int((tstamp % GAME_DAY) / GAME_HOUR)&lt;br /&gt;day = int((tstamp % GAME_MONTH) / GAME_DAY)&lt;br /&gt;julian = int((tstamp % GAME_YEAR) / GAME_JULIAN) + 1&lt;br /&gt;month = int((tstamp % GAME_YEAR) / GAME_MONTH)&lt;br /&gt;numbered_year = int(tstamp / GAME_YEAR) + CENTURY_OFFSET&lt;br /&gt;named_year= numbered_year % 12&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Finally, we can also compute the current amount of sunlight for outdoor areas:&lt;br /&gt;&lt;pre name="code" class="py"&gt;&lt;br /&gt;def sunlight(self):&lt;br /&gt;  """&lt;br /&gt;  Calculate the current sunlight level.&lt;br /&gt;  Return an integer value in the range of 0 (Midnight) to 12 (Noon).&lt;br /&gt;  """&lt;br /&gt;  tstamp = THE_TIME - UNIX_ADJ&lt;br /&gt;  hour = int((tstamp % GAME_DAY) / GAME_HOUR)&lt;br /&gt;  return int(12 - abs(12 - hour))&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-2367718442434531595?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/2367718442434531595/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=2367718442434531595' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/2367718442434531595'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/2367718442434531595'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2009/02/days-and-nights.html' title='Days and Nights'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-6102459394249506320</id><published>2008-09-21T12:10:00.000-07:00</published><updated>2008-09-21T12:19:57.628-07:00</updated><title type='text'>YAML</title><content type='html'>One of my goals was to have zero dependencies -- where all you would need is a recent released of Python to run the MUD.&lt;br /&gt;&lt;br /&gt;I think I'm going to add one though - &lt;a href="http://pyyaml.org/"&gt;PyYAML&lt;/a&gt;.  Because of how I'm doing object properties, I need to let designers script events for rooms, objects, and monsters.  Which means a lot of text editing.  Early on, I was considering &lt;a href="http://en.wikipedia.org/wiki/XML"&gt;XML&lt;/a&gt; for this, but it's a painful format to edit manually. &lt;a href="http://en.wikipedia.org/wiki/YAML"&gt; YAML&lt;/a&gt; on the other hand, is a very tight, clean format meant for straight editing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-6102459394249506320?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/6102459394249506320/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=6102459394249506320' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/6102459394249506320'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/6102459394249506320'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2008/09/yaml.html' title='YAML'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-5348036879313305321</id><published>2008-09-20T11:15:00.001-07:00</published><updated>2009-02-04T14:45:53.674-08:00</updated><title type='text'>Subversion and Junk Files</title><content type='html'>&lt;div dir="ltr"&gt;BogBoa is hosted on GoogleCode.com which uses &lt;a href="http://subversion.tigris.org/"&gt;Subversion&lt;/a&gt; for version control.  I really like Subversion but often you need to tell it to ignore junk files like *.pyc (compiled Python programs) and *.aux (&lt;a href="http://www.latex-project.org/"&gt;LaTex&lt;/a&gt; leftovers).&lt;br /&gt;&lt;br /&gt;You can tell Subversion to ignore specific types of files by editing &lt;b&gt;.subversion/config&lt;/b&gt; in your home directory and adding a line like:&lt;br /&gt;&lt;pre name="code"&gt;&lt;br /&gt;global-ignores = *.pyc *.log *.out *.toc *.aux *.sqlite *.pdf&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-5348036879313305321?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/5348036879313305321/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=5348036879313305321' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5348036879313305321'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5348036879313305321'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2008/09/subversion-and-junk-files.html' title='Subversion and Junk Files'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-7579248664162720955</id><published>2008-09-19T14:40:00.001-07:00</published><updated>2008-09-19T14:40:10.650-07:00</updated><title type='text'>Test from Email</title><content type='html'>This is a test message sent from email.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-7579248664162720955?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/7579248664162720955/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=7579248664162720955' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/7579248664162720955'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/7579248664162720955'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2008/09/test-from-email.html' title='Test from Email'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-8214156691195113458</id><published>2008-01-29T11:06:00.000-08:00</published><updated>2008-09-19T11:06:40.520-07:00</updated><title type='text'>Signs, Portents, and Little White Stickers</title><content type='html'>Remember that scene in Time Bandits when the giant creator's head was chasing them shouting, "&lt;span style="font-style: italic;"&gt;RETURN WHAT YOU HAVE STOLEN&lt;/span&gt;"?  Now picture the frozen noggin of Walt Disney saying it in the voice &lt;a href="http://en.wikipedia.org/wiki/Sonny_Bono_Copyright_Term_Extension_Act"&gt;of a certain immortal/undead&lt;/a&gt; rodent.&lt;br /&gt;&lt;br /&gt;I really like getting used books from Amazon.  You can save a lot off the retail price and, so far, I've had great luck with regards to the condition of the books.&lt;br /&gt;&lt;br /&gt;Last week I ordered &lt;a href="http://www.amazon.com/Designing-Virtual-Worlds-Riders-Games/dp/0131018167"&gt;Designing Virtual Worlds&lt;/a&gt; by Richard A. Bartle.  Mr. Bartle famously co-wrote the first MUD.  It arrived yesterday and I saw this sticker on the contents page:&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_k-xgcnlJm1E/R59qXcePgJI/AAAAAAAAACA/_ayo7AnWDqY/s1600-h/prop.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_k-xgcnlJm1E/R59qXcePgJI/AAAAAAAAACA/_ayo7AnWDqY/s400/prop.jpg" alt="" id="BLOGGER_PHOTO_ID_5160960649002451090" border="0" /&gt;&lt;/a&gt;Interesting, eh?  I sent Mr. Bartle an email and some scans which &lt;a href="http://www.youhaventlived.com/qblog/2008/QBlog290108A.html"&gt;he added to his blog&lt;/a&gt;.  He was even kind enough not to mention what a cheap bastard I was for buying it used.&lt;br /&gt;&lt;br /&gt;I just hit chapter two and, so far, it is an excellent and entertaining read -- especially for someone like me that grew up on text adventure games and then bounced around MMORPG's.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-8214156691195113458?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/8214156691195113458/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=8214156691195113458' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/8214156691195113458'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/8214156691195113458'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2008/01/signs-portents-and-little-white.html' title='Signs, Portents, and Little White Stickers'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_k-xgcnlJm1E/R59qXcePgJI/AAAAAAAAACA/_ayo7AnWDqY/s72-c/prop.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-6406142792372901238</id><published>2008-01-26T11:04:00.000-08:00</published><updated>2008-09-19T11:06:02.388-07:00</updated><title type='text'>"You are in a maze of twisty little passages, all alike"</title><content type='html'>When I was a kid, a pack of us used to play Dungeons &amp;amp; Dragons and I was often coerced into the role of Dungeon Master.  Yes, it was as nerdy as it sounds but hey, it was fun and really, really cheap entertainment.  One adventure I ran my victims, err ... cohorts through required them jumping blindly into portals to go from the current section into the next (really, it was just a way to buy time as I made up the next part).  Plus, I could make up anything without needing it be consistent with the previous setting.&lt;br /&gt;&lt;br /&gt;They &lt;span style="font-style: italic;"&gt;hated&lt;/span&gt; the portals.&lt;br /&gt;&lt;br /&gt;They were right to.  It was a cheap and crummy design.&lt;br /&gt;&lt;br /&gt;I'm fleshing out the geography system for the MUD, e.g. moving from one location to another.  Historically, these are called &lt;span style="font-style: italic;"&gt;'rooms'&lt;/span&gt; with simplistic, direction based linking between them.  You're in '&lt;span style="font-style: italic;"&gt;The Dining Hall&lt;/span&gt;' and go &lt;span style="font-style: italic;"&gt;'north'&lt;/span&gt; to be in the '&lt;span style="font-style: italic;"&gt;The Kitchen&lt;/span&gt;'.  The simplicity of this design lends itself to a text based MUD.   I could sweat area dimensions and the coordinates of players, creatures, and objects but how do I convey that to a player in a meaningful way using words alone?  If you have to kill the goblin king to get his scepter, do you really care what bearing and how many meters away he is?&lt;br /&gt;&lt;br /&gt;One concern I had was dropping players into a &lt;a href="http://en.wikipedia.org/wiki/The_Lady,_or_the_Tiger%3F"&gt;lady and the tiger&lt;/a&gt; scenario where they type 'east' and suddenly find themselves in certain death.  The answer to this is the same as it is in books.  Foreshadowing.  Alan Quartermain doesn't sail his canoe smack into the headhunters' camp.  He finds skulls on sticks, burned settlements, gross nasty things that all say, '&lt;span style="font-style: italic;"&gt;turn the hell back, Dude.&lt;/span&gt;'&lt;br /&gt;&lt;br /&gt;In other words, it's not a programming problem.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-6406142792372901238?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/6406142792372901238/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=6406142792372901238' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/6406142792372901238'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/6406142792372901238'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2008/01/you-are-in-maze-of-twisty-little.html' title='&quot;You are in a maze of twisty little passages, all alike&quot;'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-740456312191893024</id><published>2008-01-20T11:02:00.000-08:00</published><updated>2009-02-04T14:44:31.847-08:00</updated><title type='text'>Parsing Commands</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_k-xgcnlJm1E/R5PPP5ARiXI/AAAAAAAAAB4/u7iNzANBkVI/s1600-h/chat.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_k-xgcnlJm1E/R5PPP5ARiXI/AAAAAAAAAB4/u7iNzANBkVI/s400/chat.png" alt="" id="BLOGGER_PHOTO_ID_5157693870176307570" border="0" /&gt;&lt;/a&gt;This is a topic I thought about for a long time.  My family thought I was just slacking about like a bum but I was really thinking.  The challenge is converting what the player types into function calls -- only some function calls need more information than others and in differing order.  I started with a huge, ugly IF-THEN sequence but quickly hated it.  Plus, it wasn't very clean to expand.&lt;br /&gt;&lt;br /&gt;I knew I wanted a dictionary lookup that would map verbs to functions and I wanted flexible control over which verbs different players could use -- '&lt;span style="font-style: italic;"&gt;equip sword&lt;/span&gt;' vs. '&lt;span style="font-style: italic;"&gt;cast fireball at Chadu&lt;/span&gt;'.  The only common theme is every command begins with a verb.&lt;br /&gt;&lt;br /&gt;I think my solution is both elegant and functional.  I changed the dictionary to map verbs to functions &lt;span style="font-style: italic;"&gt;and parsers&lt;/span&gt;.  So the verb "&lt;span style="font-weight: bold;"&gt;tell&lt;/span&gt;" uses &lt;span style="font-weight: bold;"&gt;parser.dialogue &lt;/span&gt;(extract the recipient and the message) and "&lt;span style="font-weight: bold;"&gt;shout&lt;/span&gt;" uses &lt;span style="font-weight: bold;"&gt;parser.monoloque&lt;/span&gt; (no target, everything is the message).&lt;br /&gt;&lt;br /&gt;Here's a snippet of where the &lt;span style="font-weight: bold;"&gt;Player&lt;/span&gt; class processes a line of input:&lt;br /&gt;&lt;pre name="code" class="py"&gt;cmd = self.get_cmd()&lt;br /&gt;verb, words = split_verb(cmd)&lt;br /&gt;&lt;br /&gt;if self.has_ability(verb):&lt;br /&gt;   parser, function = shared.ABILITY_DICT[verb]&lt;br /&gt;   args = parser(words)&lt;br /&gt;   function(self, *args)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Notice the call to &lt;span style="font-weight: bold;"&gt;self.has_ability&lt;/span&gt;?  We can grant and revoke abilities easily.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-740456312191893024?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/740456312191893024/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=740456312191893024' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/740456312191893024'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/740456312191893024'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2008/01/parsing-commands.html' title='Parsing Commands'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_k-xgcnlJm1E/R5PPP5ARiXI/AAAAAAAAAB4/u7iNzANBkVI/s72-c/chat.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-4333413354280624466</id><published>2008-01-14T10:42:00.000-08:00</published><updated>2008-09-19T11:00:51.377-07:00</updated><title type='text'>More about the Event Scheduler</title><content type='html'>In addition to the classic "main loop", the game will have an event scheduler.  This is a queue for function calls.   You pass it a command and the number of seconds from now in which to run it.  This can be 1 / 1000 of second or years.  Let's say you wanted to give a room some character by telling the players something like "You hear the scurrying of rats deep within the walls."  Now, you don't want it to print &lt;span style="font-style: italic;"&gt;every&lt;/span&gt; time someone enters or it seems canned and loses all effect.  So you could do something like&lt;br /&gt;&lt;pre&gt;event_scheduler.add(600, say_ambient, "You hear the scurrying of rats deep within the walls.")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So now the message will play in exactly 10 minutes.  To make it even better, let's do this:&lt;br /&gt;&lt;pre&gt;def the_rats():&lt;br /&gt;  say_ambient("You hear the scurrying of rats deep within the walls.")&lt;br /&gt;  offset = random.randrange(600,1200)&lt;br /&gt;  event_scheduler.add(offset, the_rats)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We just made a function that, once called, continues to re-schedule itself forever in random intervals between 10 to 20 minutes apart -- and the best part is we can forget all about it.&lt;br /&gt;&lt;br /&gt;Let's say we wanted to implement a four-hour game-day.  We could write functions for noon(), dusk(), midnight(), and dawn() which did things like sent farmers to the fields and turned vampires to dust in the sunlight.   Each one of these would re-schedule itself, just like the_rats() did, but with a four hour delay (14,400 seconds).   So in our start up code we do something like:&lt;br /&gt;&lt;pre&gt;# Call noon right now&lt;br /&gt;noon()&lt;br /&gt;&lt;br /&gt;# call dusk() in 1 hour&lt;br /&gt;event_scheduler.add(3600, dusk)&lt;br /&gt;&lt;br /&gt;# call midnight() in 2 hours&lt;br /&gt;event_scheduler.add(7260, midnight)&lt;br /&gt;&lt;br /&gt;# call dawn() in 3 hours&lt;br /&gt;event_scheduler.add(10860, dawn)&lt;br /&gt;&lt;/pre&gt;And our world begins to spin.&lt;br /&gt;&lt;br /&gt;An even more efficient version would have each daily event schedule the next one to fire in one hour, round-robin style.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-4333413354280624466?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/4333413354280624466/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=4333413354280624466' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/4333413354280624466'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/4333413354280624466'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2008/01/span-stylefont-weight-boldmore-about.html' title='More about the Event Scheduler'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-8342563493429755388</id><published>2008-01-13T10:41:00.000-08:00</published><updated>2008-09-19T10:42:23.076-07:00</updated><title type='text'>Progress Check</title><content type='html'>For the MUD, I have the following features done:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Asynchronous TCP communications -- Multiple Telnet clients can connect, send, and receive.&lt;/li&gt;&lt;li&gt;Event Manager -- Function calls can be scheduled to run (float X) seconds in the future.&lt;/li&gt;&lt;li&gt;Logging -- Log messages get recorded in a text file and displayed on the server's terminal.&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.sqlite.org/"&gt;SQLite &lt;/a&gt;Database storage -- SQLite3 is now part of Python 2.5 so I'm still on my track for my goal of zero dependencies.&lt;/li&gt;&lt;li&gt;ANSI Color Tokens -- This is something I did in my BBS days.  If you want a message to show in color you can enter a simple code that begins with a caret.  ^y is yellow and ^Y is bright yellow.&lt;/li&gt;&lt;li&gt;Word Wrap Function -- This takes a block of text and, given a target column width, formats it for easy reading.&lt;/li&gt;&lt;li&gt;Kicking Idle Clients -- Automatically drops clients who remain inactive for a period of time.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;Here's an example of the word wrapping using four paragraphs from the Declaration of Independence.  The first is the text simply dumped to the terminal:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_k-xgcnlJm1E/R4ptY5ARiVI/AAAAAAAAABo/Pn_yyVVwsm0/s1600-h/Screenshot-jstorch%40localhost:%7E.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_k-xgcnlJm1E/R4ptY5ARiVI/AAAAAAAAABo/Pn_yyVVwsm0/s400/Screenshot-jstorch%40localhost:%7E.png" alt="" id="BLOGGER_PHOTO_ID_5155052997865212242" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Here's the thing run through the wrap_text() function:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_k-xgcnlJm1E/R4ptopARiWI/AAAAAAAAABw/rBjMEDtk6ow/s1600-h/Screenshot-jstorch%40localhost:%7E-1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_k-xgcnlJm1E/R4ptopARiWI/AAAAAAAAABw/rBjMEDtk6ow/s400/Screenshot-jstorch%40localhost:%7E-1.png" alt="" id="BLOGGER_PHOTO_ID_5155053268448151906" border="0" /&gt;&lt;/a&gt;I wanted it book-like and easy to read so we're padding the left and right and indenting the paragraphs slightly.  A way more sophisticated function would break words with hyphenation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-8342563493429755388?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/8342563493429755388/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=8342563493429755388' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/8342563493429755388'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/8342563493429755388'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2008/01/progress-check.html' title='Progress Check'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_k-xgcnlJm1E/R4ptY5ARiVI/AAAAAAAAABo/Pn_yyVVwsm0/s72-c/Screenshot-jstorch%40localhost:%7E.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-218012982026091552.post-5590073653928709630</id><published>2008-01-11T10:37:00.000-08:00</published><updated>2008-09-19T10:40:40.935-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mud'/><title type='text'>MUD!</title><content type='html'>Back in the early 90's I ran a computer BBS -- that's where someone spends thousands of dollars to let other people play with his computer.  I ran a number of games like &lt;a href="http://en.wikipedia.org/wiki/TradeWars_2002"&gt;Tradewars 2002&lt;/a&gt; , &lt;a href="http://www.johndaileysoftware.com/products/bbsdoors/globalwar/"&gt;Global Wars&lt;/a&gt;, and Murder Motel where you had X number of moves to find a weapon, victim, and hiding spot.  It was multi-one-player-at-a-time, but great fun.  Joe and Bill Trebing took pity on some poor kid and invited them into their Tradewars corporation.  The kid thanked them by activating the laser cannons, kicking them out of the company and into space, then blasting them to sparkley dust.&lt;br /&gt;&lt;br /&gt;I wrote a chess game where people could play each other and some utilities like a message tosser that converted from RBBS format to TriBBS.&lt;br /&gt;&lt;br /&gt;Last weekend I started writing a &lt;a href="http://en.wikipedia.org/wiki/MUD"&gt;MUD&lt;/a&gt; in Python.  It will be a text based game that you &lt;a href="http://en.wikipedia.org/wiki/TELNET"&gt;Telnet&lt;/a&gt; into.  Yes, it's extremely retro but makes for an interesting  geek project and, since I use a lot of Python at work, is a good way to brush up on my programming skills.  And, unlike the old BBS games, this will be real multi-player.&lt;br /&gt;&lt;br /&gt;At first, I was going to use a library called Twisted Matrix for networking but then decided to try writing the whole thing using only those libraries that come with Python 2.5.&lt;br /&gt;&lt;br /&gt;The trick is to design it to be non-blocking.  What is blocking, you ask?  Imagine a restaurant with only one employee.  He's the cook, waiter, busboy, dishwasher, and cashier.   In walks Mr. Ficklehead who can never make up his mind.  A blocking waiter has to stand there while Ficklehead reads the menu front to back.  The food burns, the customers don't get seated, people run out on the check -- a mess.&lt;br /&gt;&lt;br /&gt;A non-blocking waiter runs to every tables and goes, "&lt;span style="font-style: italic;"&gt;Haveyoudecidedyet? No? That'soktakeyourtime, I'llbebackinasecond&lt;/span&gt;" and whoosh, moves on. Everything gets a split second of attention.&lt;br /&gt;&lt;br /&gt;Another method is call multi-threading which would be like cloning yourself.  That sounds good until your run into problems like telling three clones to wash the car, change the oil, and drive to the store for groceries.  Keeping any two clones from flopping on the couch to watch reruns of Scrubs is just as tricky as it sounds.  Besides, some of the Python libs are not thread safe to begin with.&lt;br /&gt;&lt;br /&gt;So I'm going with single threaded asynchronous networking.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/218012982026091552-5590073653928709630?l=bogboa.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://bogboa.blogspot.com/feeds/5590073653928709630/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=218012982026091552&amp;postID=5590073653928709630' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5590073653928709630'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/218012982026091552/posts/default/5590073653928709630'/><link rel='alternate' type='text/html' href='http://bogboa.blogspot.com/2008/01/mud-back-in-early-90s-i-ran-computer.html' title='MUD!'/><author><name>Jim</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
