tag:blogger.com,1999:blog-2180129820260915522024-03-13T11:39:46.269-07:00BogBoaProgess blog for a spare-time project to create a Multi-User Dungeon in Python.Unknownnoreply@blogger.comBlogger45125tag:blogger.com,1999:blog-218012982026091552.post-9402196878855705522013-02-10T14:37:00.000-08:002013-02-10T14:37:05.194-08:00X84 Telnet ServerI got an email this weekend from Jeff Quast who writes:<br />
<blockquote class="tr_bq">
<span style="background-color: white; color: #222222; font-family: arial, sans-serif; font-size: 13px;">I've been writing a telnet BBS in python since about 2003. It has many similarities in your MUD. Initially we had a hand-crafted simple socket telnet daemon, then later twisted, but when we decided to get rid of twisted for a pure-python solution, I discovered your miniboa source.</span><br style="background-color: white; color: #222222; font-family: arial, sans-serif; font-size: 13px;" /><span style="background-color: white; color: #222222; font-family: arial, sans-serif; font-size: 13px;">It has been modified quite a bit for more advanced telnet negotiations, I encourage you to take a look. You will find NAWS, TTYPE, and even NEW_ENVIRON negotiation. I plan to release x/84 to pypi within a day or so[.]</span></blockquote>
<br />
I really enjoy it when someone dusts off my brain-addled code and manages to hammer it into something useful. You can check out his project here;<br />
<br />
<a href="https://github.com/jquast/x84">https://github.com/jquast/x84</a><br />
<br />Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-218012982026091552.post-45135990893691448192013-01-28T16:21:00.000-08:002013-01-28T16:23:17.843-08:00New Development Server<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEha69UuB8DQAU5DzOLM2OkREuZdyAZKFjPOFJdEqHPnly12fJRK6hn3i4hhq2eA0FrPh6SLNotU6rSmGeIKo19lB0AFU3jABsRCTPPv-g5rI7Z-mAf4nQig-Y1ivjQreBjy3a4uV6BE5TPw/s1600/66486_10151378937605622_234803154_n.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEha69UuB8DQAU5DzOLM2OkREuZdyAZKFjPOFJdEqHPnly12fJRK6hn3i4hhq2eA0FrPh6SLNotU6rSmGeIKo19lB0AFU3jABsRCTPPv-g5rI7Z-mAf4nQig-Y1ivjQreBjy3a4uV6BE5TPw/s320/66486_10151378937605622_234803154_n.jpg" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
I've treated myself to the sparkly-new, dedicated development server pictured above. It's a <a href="http://www.raspberrypi.org/">Raspberry-PI</a>. I'm happy to say my http+websocket server runs really well on it. I'm also pleased to report that the websocket code now works with Chrome, Firefox (aka <a href="http://en.wikipedia.org/wiki/Mozilla_Corporation_software_rebranded_by_the_Debian_project">Iceweasel</a>), and Opera.</div>
Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-218012982026091552.post-42859295262267830232012-07-29T09:57:00.001-07:002012-07-29T10:06:07.102-07:00For the Love of Coffee, Gadgets, and PythonMost geeks are familiar with the <a href="http://www.raspberrypi.org/">RasberryPi Project</a> that set out to make a tiny, hackable, and amazingly capable PC for around $25. I've been following it with great interest.<br />
<div>
<br /></div>
<div>
I have to say that I was really tickled when I came across the <a href="http://moccapi.blogspot.com/">project blog for MoccaPi </a>. He's designing a controller to let him remotely start and stop his coffee maker via Telnet. The coolest part for me is the use of Miniboa for the device's server. <br />
<br />
<i>Miniboa ... on a RasberryPi ... making coffee.</i> How awesome is that?</div>
<div>
<br /></div>
<div>
<iframe frameborder="0" height="396" src="http://www.screenr.com/embed/O7H8" width="650"></iframe><br />
<br />
His project was <a href="http://m.techrepublic.com/blog/european-technology/10-coolest-uses-for-the-raspberry-pi/505">voted #10 coolest use for a RasberryPi on TechRepublic</a>.</div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-35311904108314205682012-03-02T12:52:00.005-08:002012-03-02T13:56:01.984-08:00Desktop LinuxLinux has been my primary OS for about eight years. I am very fond of it and have often made the analogy that sitting down to a Windows box feels as clumsy as having your thumbs removed.<br /><br />I used Redhat, then Fedora, then Ubuntu. It really annoyed me when they hid the kernel boot messages to be more noob friendly or stylish or whatever. My Linux boxes are <span style="font-weight: normal; font-style: italic; ">workstations</span> damn it; show me the <a href="http://en.wikipedia.org/wiki/BogoMips" style="font-style: normal; font-weight: normal; ">bogomips</a>.<br /><br />Then Ubuntu gets stupid. Shuttleworth decides we need windows controls on the wrong side to prepare us for ... <i>something</i>. So I spend a year feeling like an American driving in Britain when using Linux and a Brit driving in the US when using Windows.<div style="font-style: normal; font-weight: normal; "><br /></div><div style="font-weight: normal; "><span style="font-style: normal; ">And that </span><i>something</i> turns out to be <a href="http://unity.ubuntu.com/" style="font-style: normal; ">Unity</a>.</div><div style="font-style: normal; font-weight: normal; "><br /></div><div style="font-style: normal; font-weight: normal; ">And Unity <a href="http://www.opiniond.com/2011/05/5-reasons-why-unity-sucks/">sucks</a>.</div><div style="font-style: normal; font-weight: normal; "><br /></div><div>And my escape route is gone because my beloved Gnome had become <b style="font-style: normal; ">Gnome 3</b> aka <b style="font-style: normal; ">Gnome Shell </b><span style="font-style: normal; ">aka</span><b style="font-style: normal; "> Hunt the Wumpus </b><span style="font-style: normal; ">but</span><b style="font-style: normal; "> </b>I give it a shot for a few weeks because it has the wonderful quality of not being Unity until it strikes me, <i>this sucks too</i>. So I spend an entire weekend trying different distros -- then going back to Ubuntu. Then spending the next weekend forcing XFCE into dressing up as Gnome 2. Which was close but what a weird the-opposite-of-forward step to make?</div><div><br /></div><div>Pretty-please someone fork Gnome 2 and save the Linux desktop.</div>Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-218012982026091552.post-7662892460573599312012-03-02T12:44:00.003-08:002012-03-02T12:47:43.514-08:00Remove the "close window?" prompt from Gnome-Terminal<pre>$ gconftool-2 --type bool --set /apps/gnome-terminal/global/confirm_window_close "false"</pre><br />#get_off_my_yardUnknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-44196094390097664462012-02-26T09:23:00.009-08:002012-02-26T10:30:52.515-08:00The Web ClientLast weekend I got my WebSocket server for NetBoa working again (yay).<div style="text-align: left;"><br /></div><div style="text-align: left;">This weekend I've been laying out the HTML5 and CSS for the browser side. At the moment it looks like this;</div><br /><br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMbtQ1cUKiVZY3j_ZUNJr97aWhbCpHRaBX5A-6Gn_9pCv8Ccs5uytmXBB3cdJHLRIUmraHp3xwGNA2GooXRQogqsk7u_PCzAf9OEqqk9qKSrZhTqVIc-TcSLSkoVHplo9K1fbevLElgGxp/s1600/Screenshot.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 272px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMbtQ1cUKiVZY3j_ZUNJr97aWhbCpHRaBX5A-6Gn_9pCv8Ccs5uytmXBB3cdJHLRIUmraHp3xwGNA2GooXRQogqsk7u_PCzAf9OEqqk9qKSrZhTqVIc-TcSLSkoVHplo9K1fbevLElgGxp/s400/Screenshot.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5713513510318954050" /></a><br /><br /><div style="text-align: left;">It seems obvious now, but it felt like an epiphany when I realized that the project only required <i>a single HTML page</i>. Everything from the login screen forward will be elements dynamically create via Javascript (using the excellent <a href="http://jquery.com/">JQuery</a> lib) talking the server over a WebSocket. Chrome will happily maintain an open WebSocket for days with no activity (I tested it).</div><div> </div><div><br /></div><div>In John Gardner's book, <b>Grendel</b>, a dragon offers up the advice '<i>alternatives exclude</i>'. I'm wrestling with some UI decisions -- screen dimensions vs. info glut. In the shot above, the left panel is somewhat like the <i>main viewer</i> from Star Trek;</div><div><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLUXNt4edXKHWPcKzyeD8i2Ccy6ltzb28TAAQ16UukDMAvo3riCGP43pxUYluU38zS8bb9wMUILKqxfPpfftIXA6CY4K3rD74i43hg3ZFyVHtJ68VHc42-Cb3eG5LLVB4i3Q_9GA405jia/s200/datasday314.jpg" border="0" alt="" id="BLOGGER_PHOTO_ID_5713503952495939074" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 200px; height: 152px; " /></div><div><div style="text-align: center;"><br /></div><div>When the right is like a <i>console;</i></div><div><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH35kKc1oSDOyaWyMCKrgxzEIqrI8-phyphenhyphen-kaKBJ4VjMokZRIyHj8sgs_8ecENoWn21qaqNZdfh6rRI0oIZKuiWic-nnKt-XQzugpoIrCJoBahzk1yz-fb1PTTD_7apiXf4oeoCl3dFaEAz/s200/640px-Transporter_console%252C_23rd_century.jpg" border="0" alt="" id="BLOGGER_PHOTO_ID_5713504394893665714" style="display: block; margin-top: 0px; margin-right: auto; margin-bottom: 10px; margin-left: auto; text-align: center; cursor: pointer; width: 200px; height: 150px; " /></div></div><div>Originally, I had three columns where the far left one containing a kind of galactic IRC. But that was just too much.</div><div><br /></div><div>I'm still trying to decide on a messaging protocol. JSON is great for interfacing with Javascript in the browser but that's precisely where I <b>don't</b> need speed. Given the short life of these message packets it seems rather wasteful to include an extra library just to generate Python objects to be cast into JSON strings a picosecond later. I'm 90% sure that I'll go with text commands and an option to send snippets of HTML.</div>Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-218012982026091552.post-46349361731185270832012-02-20T10:10:00.000-08:002012-02-20T10:35:26.355-08:00Unpacking WebSocket Frames Cont.Using <a href="http://www.wireshark.org/">WireShark</a>, I captured some frames between my browser and <a href="http://websocket.org/echo.html">WebSocket.org's Echo Server</a>. This gave me some data to experiment with without needing a living server.<div><br /></div><div>I mentioned that the protocol in version 13 was very different. Previously, you bookended your packets with \x00 and \xFF and shoved them down the wire. <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> adopted a more formal framing that includes op codes for the frame type (string vs binary && ping vs pong), a continuation flag, and three different data structures for describing payload size. Additionally, payloads from the Client to the Server are XOR'ed with a random 32 bit mask. This mask is included in the client frame. Frames from the Server to Client are sent in the clear. Plus some reserved fields.</div><div><br /></div><div>Yes, it does seem a touch fiddly.</div><div><br /></div><div>Since I'll own both ends of the wire, I should be fine with a server that only uses 'final fragment' frames (no need for messages to span data packets) using text frames. Most likely JSON. I don't plan to support the previous WS drafts since browser updates are darn near seamless today.</div><div><br /></div><div>The output of the previous code is:</div><div><br /><br /><pre>Final Frame: True<br />Masked: True<br />Opcode: 1<br />Payload Length: 28<br />Payload: Rock it with HTML5 WebSocket<br /><br />Final Frame: True<br />Masked: False<br />Opcode: 1<br />Payload Length: 28<br />Payload: Rock it with HTML5 WebSocket<br /><br />Final Frame: True<br />Masked: True<br />Opcode: 1<br />Payload Length: 165<br />Payload: This is a very long message that is longer than 125 bytes so it will be offet with a 16 bit extended payload length and therefore payload len should be equal to 126.<br /><br />Final Frame: True<br />Masked: False<br />Opcode: 1<br />Payload Length: 165<br />Payload: This is a very long message that is longer than 125 bytes so it will be offet with a 16 bit extended payload length and therefore payload len should be equal to 126.</pre><br /></div><div><br /></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-65954023108040655072012-02-20T10:03:00.000-08:002012-02-20T10:10:21.253-08:00Unpacking a WebSocket FrameI'm going to put my comments in the next post because Blogger likes to mangle my code snippets if I try to edit something...<br /><pre name="code" class="py">#!/usr/bin/env python<br /><br />import struct<br />import array <br /><br /><br />## Frames with a 7 bit payload length<br /><br />CLIENT7 = (<br /> b'\x81\x9c\xb3\x8f\x67\x54\xe1\xe0\x04\x3f\x93\xe6\x13\x74\xc4\xe6'<br /> b'\x13\x3c\x93\xc7\x33\x19\xff\xba\x47\x03\xd6\xed\x34\x3b\xd0\xe4'<br /> b'\x02\x20'<br /> )<br /><br />SERVER7 = (<br /> b'\x81\x1c\x52\x6f\x63\x6b\x20\x69\x74\x20\x77\x69\x74\x68\x20\x48'<br /> b'\x54\x4d\x4c\x35\x20\x57\x65\x62\x53\x6f\x63\x6b\x65\x74'<br /> )<br /><br /><br />## Frames with a 16 bit payload length<br /><br />CLIENT16 = (<br /> b'\x81\xfe\x00\xa5\x33\x92\xab\x19\x67\xfa\xc2\x6a\x13\xfb\xd8\x39'<br /> b'\x52\xb2\xdd\x7c\x41\xeb\x8b\x75\x5c\xfc\xcc\x39\x5e\xf7\xd8\x6a'<br /> b'\x52\xf5\xce\x39\x47\xfa\xca\x6d\x13\xfb\xd8\x39\x5f\xfd\xc5\x7e'<br /> b'\x56\xe0\x8b\x6d\x5b\xf3\xc5\x39\x02\xa0\x9e\x39\x51\xeb\xdf\x7c'<br /> b'\x40\xb2\xd8\x76\x13\xfb\xdf\x39\x44\xfb\xc7\x75\x13\xf0\xce\x39'<br /> b'\x5c\xf4\xcd\x7c\x47\xb2\xdc\x70\x47\xfa\x8b\x78\x13\xa3\x9d\x39'<br /> b'\x51\xfb\xdf\x39\x56\xea\xdf\x7c\x5d\xf6\xce\x7d\x13\xe2\xca\x60'<br /> b'\x5f\xfd\xca\x7d\x13\xfe\xce\x77\x54\xe6\xc3\x39\x52\xfc\xcf\x39'<br /> b'\x47\xfa\xce\x6b\x56\xf4\xc4\x6b\x56\xb2\xdb\x78\x4a\xfe\xc4\x78'<br /> b'\x57\xb2\xc7\x7c\x5d\xb2\xd8\x71\x5c\xe7\xc7\x7d\x13\xf0\xce\x39'<br /> b'\x56\xe3\xde\x78\x5f\xb2\xdf\x76\x13\xa3\x99\x2f\x1d'<br /> )<br /><br />SERVER16 = (<br /> b'\x81\x7e\x00\xa5\x54\x68\x69\x73\x20\x69\x73\x20\x61\x20\x76\x65'<br /> b'\x72\x79\x20\x6c\x6f\x6e\x67\x20\x6d\x65\x73\x73\x61\x67\x65\x20'<br /> b'\x74\x68\x61\x74\x20\x69\x73\x20\x6c\x6f\x6e\x67\x65\x72\x20\x74'<br /> b'\x68\x61\x6e\x20\x31\x32\x35\x20\x62\x79\x74\x65\x73\x20\x73\x6f'<br /> b'\x20\x69\x74\x20\x77\x69\x6c\x6c\x20\x62\x65\x20\x6f\x66\x66\x65'<br /> b'\x74\x20\x77\x69\x74\x68\x20\x61\x20\x31\x36\x20\x62\x69\x74\x20'<br /> b'\x65\x78\x74\x65\x6e\x64\x65\x64\x20\x70\x61\x79\x6c\x6f\x61\x64'<br /> b'\x20\x6c\x65\x6e\x67\x74\x68\x20\x61\x6e\x64\x20\x74\x68\x65\x72'<br /> b'\x65\x66\x6f\x72\x65\x20\x70\x61\x79\x6c\x6f\x61\x64\x20\x6c\x65'<br /> b'\x6e\x20\x73\x68\x6f\x75\x6c\x64\x20\x62\x65\x20\x65\x71\x75\x61'<br /> b'\x6c\x20\x74\x6f\x20\x31\x32\x36\x2e'<br /> )<br /><br />def unpack_frame(data):<br /> frame = {}<br /> byte1, byte2 = struct.unpack_from('!BB', data)<br /> frame['fin'] = (byte1 >> 7) & 1<br /> frame['opcode'] = byte1 & 0xf<br /> masked = (byte2 >> 7) & 1<br /> frame['masked'] = masked<br /> mask_offset = 4 if masked else 0<br /> payload_hint = byte2 & 0x7f<br /> if payload_hint < 126:<br /> payload_offset = 2<br /> payload_length = payload_hint <br /> elif payload_hint == 126:<br /> payload_offset = 4<br /> payload_length = struct.unpack_from('!H',data,2)[0]<br /> elif payload_hint == 127:<br /> payload_offset = 8<br /> payload_length = struct.unpack_from('!Q',data,2)[0]<br /> frame['length'] = payload_length<br /> payload = array.array('B')<br /> payload.fromstring(data[payload_offset + mask_offset:])<br /> if masked:<br /> mask_bytes = struct.unpack_from('!BBBB',data,payload_offset)<br /> for i in range(len(payload)):<br /> payload[i] ^= mask_bytes[i % 4]<br /> frame['payload'] = payload.tostring()<br /> return frame<br /><br /> <br />if __name__ == '__main__':<br /><br /> frame = unpack_frame(CLIENT7)<br /> print '\nFinal Frame:', bool(frame['fin'])<br /> print 'Masked:', bool(frame['masked'])<br /> print 'Opcode:', frame['opcode']<br /> print 'Payload Length:', frame['length']<br /> print 'Payload:', frame['payload']<br /><br /> frame = unpack_frame(SERVER7)<br /> print '\nFinal Frame:', bool(frame['fin'])<br /> print 'Masked:', bool(frame['masked'])<br /> print 'Opcode:', frame['opcode']<br /> print 'Payload Length:', frame['length']<br /> print 'Payload:', frame['payload']<br /><br /> frame = unpack_frame(CLIENT16)<br /> print '\nFinal Frame:', bool(frame['fin'])<br /> print 'Masked:', bool(frame['masked'])<br /> print 'Opcode:', frame['opcode']<br /> print 'Payload Length:', frame['length']<br /> print 'Payload:', frame['payload']<br /><br /> frame = unpack_frame(SERVER16)<br /> print '\nFinal Frame:', bool(frame['fin'])<br /> print 'Masked:', bool(frame['masked'])<br /> print 'Opcode:', frame['opcode']<br /> print 'Payload Length:', frame['length']<br /> print 'Payload:', frame['payload']</pre>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-59798264656624140062012-02-19T17:38:00.001-08:002012-02-19T17:47:20.205-08:00WebSocket RFC 6455 HandshakeI think I got the request/response bit figured out (it was actually simpler than the draft 76 method). This is my test using headers from the RFC document:<br /><br /><pre name="code" class="py">from hashlib import sha1<br />from base64 import b64encode<br /><br />WS13_GUID = b'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'<br /><br />REQUEST = (<br /> b'GET /chat HTTP/1.1\r\n'<br /> b'Host: server.example.com\r\n'<br /> b'Upgrade: websocket\r\n'<br /> b'Connection: Upgrade\r\n'<br /> b'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n'<br /> b'Origin: http://example.com\r\n'<br /> b'Sec-WebSocket-Protocol: chat, superchat\r\n'<br /> b'Sec-WebSocket-Version: 13\r\n'<br /> b'\r\n'<br /> )<br /><br />RESPONSE = (<br /> b'HTTP/1.1 101 Switching Protocols\r\n'<br /> b'Upgrade: websocket\r\n'<br /> b'Connection: Upgrade\r\n'<br /> b'Sec-WebSocket-Accept: {}\r\n'<br /> b'Sec-WebSocket-Protocol: chat\r\n'<br /> b'\r\n'<br /> )<br /><br />CORRECT_ACCEPT = b's3pPLMBiTxaQ9kYGzzhZRbK+xOo='<br /><br />def parse_request(request):<br /> req = {}<br /> lines = request.split(b'\r\n')<br /> line = lines.pop(0)<br /> items = line.split(b'\x20',2)<br /> if len(items) != 3:<br /> raise BaseException('Malformed WebSocket Request Method.')<br /> req[b'method'] = items[0]<br /> req[b'request_uri'] = items[1]<br /> req[b'http_version'] = items[2]<br /> for line in lines:<br /> if line:<br /> parts = line.split(b':\x20', 1)<br /> req[parts[0].lower()] = parts[1]<br /> return req<br /><br />if __name__ == '__main__':<br /> req = parse_request(REQUEST)<br /> salted_key = req[b'sec-websocket-key'] + WS13_GUID<br /> sec_ws_accept = b64encode(sha1(salted_key).digest())<br /> assert(sec_ws_accept == CORRECT_ACCEPT)<br /> print RESPONSE.format(sec_ws_accept)</pre><br />Step two is to get the VERY DIFFERENT message protocol working.Unknownnoreply@blogger.com2tag:blogger.com,1999:blog-218012982026091552.post-20689824055009719492012-02-19T15:23:00.000-08:002012-02-19T17:54:15.954-08:00WebSockets<div>Naturally, the Overlords of the Internet have obsoleted my <a href="http://bogboa.blogspot.com/2011/06/websockets.html">WebSocket code</a> for the third time. Hopefully things will settle down now the current draft has been <a href="http://www.ietf.org/mail-archive/web/ietf-announce/current/msg09663.html">submitted as a proposed standard</a> (one step away from an adopted standard). </div><div><br /></div>Pointing my browser (Chromium) at <a href="http://websocketstest.com">http://websocketstest.com</a> tells me that it's ready for <a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a>, so I guess I need to get coding.<div><br /></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-39245368686200259742012-02-18T18:31:00.000-08:002012-02-18T18:42:45.056-08:00Zomborgs<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLoOAQLX3wTC0QKPCGiNgXTk0dHnLnSYRaWHKIvar8HMdryeleburtfgM3XBVGHLAgL_pUqChyphenhyphenLRXSxMB9x41TuKONu-dypkufLx-_bpFC4dTtGsw7DOnrd44qAQoA54egURZSfmVWKz5P/s1600/zombs.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 400px; height: 236px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiLoOAQLX3wTC0QKPCGiNgXTk0dHnLnSYRaWHKIvar8HMdryeleburtfgM3XBVGHLAgL_pUqChyphenhyphenLRXSxMB9x41TuKONu-dypkufLx-_bpFC4dTtGsw7DOnrd44qAQoA54egURZSfmVWKz5P/s400/zombs.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5710668929025164354" /></a><div>Known to use transmat beams to take brains directly from crewfolk skulls.</div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-57957592412886890542012-02-18T08:20:00.001-08:002012-02-18T08:29:59.019-08:00Failure is an Option<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh65XAfMebIrs5wQqAz9F42NqM6J4sDC90wejw15cZJC127DAoyska21ofYMW95BMwG0ChvXvRBP3yEjnMkvMW74KKqV5_ly1ZXW33SAxRaK_ql9D3FknCIr5vm1vKoXZ6JpeCohvw59ZxG/s1600/path3460.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 240px; height: 277px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh65XAfMebIrs5wQqAz9F42NqM6J4sDC90wejw15cZJC127DAoyska21ofYMW95BMwG0ChvXvRBP3yEjnMkvMW74KKqV5_ly1ZXW33SAxRaK_ql9D3FknCIr5vm1vKoXZ6JpeCohvw59ZxG/s400/path3460.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5710512162538791282" /></a><div style="text-align: center;"><span ><u><br /></u></span></div><div style="text-align: center;"><span style="text-align: left; ">This is the icon for a dead player</span><span><u><br /></u></span><br /></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-480626966770364702012-02-17T12:15:00.000-08:002012-02-17T12:25:23.697-08:00Character Work<div>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. </div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizhdEop3UdPYkc5LNtXv3A_fKM8hvK9j1ybbYmONqS8kzHHIuxCZ9GQVnoRiKK8TYyLUiXaa-iFlrxI1EVa1QvYSjyStPqUnpCrFL7qgkPXcK4ljkE2XjmTduii0eW6MpAZMaZqaaeUnxv/s1600/k4.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 193px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEizhdEop3UdPYkc5LNtXv3A_fKM8hvK9j1ybbYmONqS8kzHHIuxCZ9GQVnoRiKK8TYyLUiXaa-iFlrxI1EVa1QvYSjyStPqUnpCrFL7qgkPXcK4ljkE2XjmTduii0eW6MpAZMaZqaaeUnxv/s400/k4.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5710200994923017570" /></a><div style="text-align: left;">This is a <b>Kthogin</b> -- a Lovecraftian inspired hostile. I'm leaning towards giving each race a unique color for easy recognition.</div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-56100512235835579212012-02-12T10:33:00.000-08:002012-02-12T12:59:59.321-08:00Thoughts on SerializationThe problem domain is short and sweet;<div><ol><li>We need to write game data.</li><li>We need to read game data.</li></ol></div><div>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 <b>everything</b> at server start and <i>only performing writes</i> during execution. That would cut our blocking problem in half but removes our ability to modify data externally.</div><div><br /></div><div>The model in my head calls for very little of the<b><i> R </i></b>in <a href="http://en.wikipedia.org/wiki/Relational_database_management_system">RDBMS</a>. Heck, MUDs have managed very well for decades using flat files on systems with less processing power than your dishwasher.</div><div><br /></div><div>Lastly, given that this hobby-coding, I have the luxury of asking, 'is the solution fun?' Let's set a couple goals;</div><div><ol><li>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.</li><li>Simple to install. Freely available software that does not require a DBA to manage.</li><li>Simple to operate. No fretting dirty caches, scheduled housekeeping , etc.</li><li>I would prefer back-ups and restores to be file copies. Tar'ing a directory or two is fine.</li><li>Some external method to futz with the data while the game is running. </li></ol></div><div>Flat files are certainly doable (and made crazy easy with Python's <a href="http://docs.python.org/release/2.5/lib/module-pickle.html">Pickle module</a>). 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.</div><div><br /></div><div>A lightweight dbms like <a href="http://www.sqlite.org/">SQLite</a> would work except for goal #3. Wrapping Python <a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a> in SQL statements is soul-destroying tedious. Yeah, I could tap a ORM like <a href="http://www.sqlalchemy.org/">SQLAlchemy</a> but let's look at that new sexy, NoSQL...</div><div><br /></div><div><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhYenYpnK_CF-wG6OnffbpMldbdOQuoHlxhbN1v7goeYf-bbHVmefSDFsHpmvbZw5bS3std0Aoc3tzVxusyUUSQ-WihYxsdwuZaFom7grLf-EGaAnNSrd3H56x82UBkVNKwIYg9I6l1XU-x/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; " /></div><div><br /></div><div> </div><div> </div><div>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.</div><div><br /></div><div>Python holds that same appeal for me. It's a big toy. Another one I've found is <a href="http://redis.io/">Redis</a> -- a dead simple NoSQL data store. The distinguishing feature is all data is held in-memory which makes it wicked fast.</div><div><br /></div><div>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<i> in my program</i>? And who wants to hold <b>everything</b> in memory whether you need it or not?</div><div><br /></div><div>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 <i>any moment</i> without fear of corrupting it. The author also provides a really nice CLI tool with history and auto-complete similar to a Linux terminal.</div><div><br /></div><div>We're pushing goal #2 a bit since it requires installing three packages, <a href="http://redis.io/download">Redis</a>, <a href="https://github.com/antirez/hiredis">Hiredis</a>, and <a href="https://github.com/andymccurdy/redis-py.git">Redis-py</a> but they all loaded with minimal fuss. Hiredis needed the Python development libs you can get on Ubuntu using:</div><div><br /></div><div><span class="Apple-style-span">$ sudo apt-get install python-dev</span></div><div><br /></div><div>...to be continued</div>Unknownnoreply@blogger.com3tag:blogger.com,1999:blog-218012982026091552.post-70481793873273394172011-06-27T18:53:00.000-07:002011-06-28T11:49:30.238-07:00Crawling a Graph<div>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 <i>collection</i> of sets. Each set contains either a group of linked sectors or a single orphan sector.</div><div><br /></div><div>Here's a diagram of crawling from sector 12:</div><div><br /></div><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_qpa6qXM6Gs6tG30MSbwC8kIXSKKXhvVb1ZSf9ubToU-ssgy2vfWxjS6Sl-r_gyEz-lG_WSzcA0Fs8RDf27oeUsT5eG4k1UjkFFZnwcY6MhyphenhyphenITEWzCdgvBFRgJcL2fWLL9WzC6lN-Jmlc/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" /><br /><div><br /></div><div>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: <div><br /></div><div><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjzC4tIKLefj9wnmNpWidhOn7JsDxQraZZ6Z_3oVaLrAFIDqstjtDIXKnRzrjY4TTIgYID4B33iefJqoFa_jzV1Ot_Rg_u2awg8NWyk2b5f8bRhad-D8sPBhRVZW2Bjio_LmmFnentA9lJk/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" /></div><div><br /></div><div>Lastly, I got my<a href="http://en.wikipedia.org/wiki/A*_search_algorithm"> A* path finder</a> 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<i> use which ever is greater</i>. 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. </div><div><br /></div><div><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd6sYj-sz_xJwS8MrElyjk4Imrphwvu-drCDnRoU7J1yeQCWhdttmDo6o_dOuaszhOIUgEFLfofwvxNQO8dkWNcl15QKP4nUwgw0Ma30zDr7unPqmbdnOlhFjR5rfrKZiezi92KhtiY2Nl/s1600/astar.png" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><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="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd6sYj-sz_xJwS8MrElyjk4Imrphwvu-drCDnRoU7J1yeQCWhdttmDo6o_dOuaszhOIUgEFLfofwvxNQO8dkWNcl15QKP4nUwgw0Ma30zDr7unPqmbdnOlhFjR5rfrKZiezi92KhtiY2Nl/s400/astar.png" border="0" alt="" id="BLOGGER_PHOTO_ID_5623086777765068098" /></a><br /></div><div><br /></div><div>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.</div><div><div style="text-align: left;"><br /></div></div><div style="text-align: left;">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.</div></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-80435797318103206192011-06-25T18:56:00.000-07:002011-06-28T10:13:32.342-07:00Visualizing Data<div style="text-align: left;">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 <b>SEE</b> this hooey.</div><div style="text-align: center;"><br /></div><div style="text-align: left;"><a href="http://en.wikipedia.org/wiki/Scalable_Vector_Graphics">Scaled Vector Graphics</a> are an <a href="http://en.wikipedia.org/wiki/XML">XML</a>-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 <a href="http://en.wikipedia.org/wiki/Xhtml">XHTML</a> 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.</div><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDNS_jysFo6whSntpR3nqnX7TXD7wjtFN6X20SOnB9A7tl3UoeadSuTdqg0yJB-5zPTmuNmwINmJS3bSP8dw7lm7CppqZ3e1cocVXkGho2PIq0dnh5CONkumsj1nh_vDo1ZzMdjD0JV9mD/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" /><br /><div>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 <i>hub</i>, which will always get 6 exits. More on using this to cure orphanism later. <div><br /><div>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:</div><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwJaWN9L-qAqhSqvuLabgd8HzWEzSgI6QWm4SJGJ1w7BeuJkZHmoEjXZdBT4XGa1wA-M4HsYZcUHCCwHjuEvEjj6tdwOrYWWRnR_Ebq0NVC93qkHVjuUYK89rYfgdMrC5cb0yNEorQQUYd/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" /></div></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-65397705870140483072011-06-25T08:48:00.000-07:002011-06-25T09:28:39.336-07:00The Universe is a Diamond<div style="text-align: left;">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 <a href="http://en.wikipedia.org/wiki/Trade_Wars">Tradewars</a>.</div><div style="text-align: center;"><br /></div><div>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.</div><div style="text-align: center;"><br /></div><div>Let's say we want each sector to link up to six others, proving a movement pattern like hex paper;</div><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjEUO-IWrIAmYbXmJ0AedMn1WAMzzfYds-iyLeLdCe182sKNDWWuPzYXzXlL-VrX5k4rOZQrg-rJlAimrHeNLnLWMuQSkJQwP_UZea8qoRCPBwGZKuAS7K_Ls06w_AEMZGBEVPP9biEbCss/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" /><div style="text-align: center;"><br /></div><div>Internally, I plan on using a bog-standard XY array. Why? Because it <i>feels</i> like it would make things like pathfinding and NPC distribution easier. We'll see.</div><div style="text-align: center;"><br /></div><div>So let's say we're in Sector 54 (yellow) and can move to six adjacent sectors (blue):</div><div><br /></div><div style="text-align: center;"><span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEidHL6DJQ84X7j8Ier0r2MAPhJxNO69kqh2v5ZDuBTNLQR9UVOiiilr1VPcWjBAFaYCM8ioywUCgzSrk11UpaJZ7P2ubKVVNPT35zlxpw3RgpzzOkpmsg9HEaamU8mmSCVxxpeG0zWD5rLN/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; " /></span></div><div>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;</div><div><br /></div><div><span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjAgSLHyw_dc97AU_p2GseYD53vW4XimrbfJrWuj1yb-MrdajQ4uS2H9TOOE2ji7QNHvVRJxWUoVBm11xZ11AkQXowl27Kykv6QLhVSs4dKZjfHt-xyT0iR-LXcIKCSTxYXEMiuJB_V3gIb/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; " /></span></div><div><span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "><br /></span></div><div>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.</div><div style="text-align: center;"><span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "><img src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9ICQtP0O_JdEJhDdIN2IkJhH1BnOOhyphenhyphenFJOJ-wVbD7_33pr5Y583L5XP0msytNP_bCm4wxO-OsEhpkd3Kn8bl5-naC3CnV3Qgl1RZGBt3F7kfgIP1P9lgxTLUpz9VvUadV_NzO3_TApXIs/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; " /></span></div><div><span class="Apple-style-span" style="color: rgb(0, 0, 238); -webkit-text-decorations-in-effect: underline; "><span class="Apple-style-span" style="color: rgb(0, 0, 0); ">Virtual hex paper. Next step is to break all the links into twisty little passages -- not alike.</span></span></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-1682367472609382652011-06-17T17:49:00.000-07:002011-06-17T18:26:11.747-07:00Python 3I've been ignoring Python 3K. <div><br /></div><div>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. <br /><br />A while back someone filed a bug that Miniboa didn't work with Python 3. "Get off my lawn", I thought to myself.<br /><br />To switch gears for a second -- I've found that deleting big chunks of code (<span style="font-style:italic;">murder your darlings</span>) 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'. <br /><br />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:<br /><pre name="code" class="py">>>> '\x20\x20'[0] == '\x20'<br />True<br />>>> b'\x20\x20'[0] == b'\x20'<br />False<br /></pre>Following the <a href="http://lucumr.pocoo.org/2010/5/25/wsgi-on-python-3/">advice of Armin Ronacher</a>, 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.</div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-44515033568195969592011-06-15T17:02:00.000-07:002012-02-19T16:17:50.936-08:00WebSockets<div><b><br /></b></div><div><b><br /></b></div><div><b><br /></b></div><div><b><br /></b></div><div><b><span><span>UPDATE</span>: THIS IS CODE FOR AN OLD VERSION OF THE WEBSOCKET PROTOCOL HANDSHAKE AND IS NO LONGER VALID. </span></b></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>Yes, long hiatus. Hi.</div><div><br /></div><div><a href="http://en.wikipedia.org/wiki/WebSockets">WebSockets</a> 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.</div><div><br /></div>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:<br /><br /><pre name="code" class="py">#!/usr/bin/env python<br />#------------------------------------------------------------------------------<br /># netboa/websocket/sanity_check.py<br /># Copyright 2011 Jim Storch<br /># Licensed under the Apache License, Version 2.0 (the "License"); you may<br /># not use this file except in compliance with the License. You may obtain a<br /># copy of the License at http://www.apache.org/licenses/LICENSE-2.0<br /># Unless required by applicable law or agreed to in writing, software<br /># distributed under the License is distributed on an "AS IS" BASIS, WITHOUT<br /># WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the<br /># License for the specific language governing permissions and limitations<br /># under the License.<br />#------------------------------------------------------------------------------<br /><br />"""<br />Wrote this to test that I'm correctly generating the responses for draft76<br />websockets.<br />"""<br /><br />import struct<br />import hashlib<br /><br /><br />## From the draft76 docs:<br /><br />REQUEST1 = (<br /> 'GET /demo HTTP/1.1\r\n'<br /> 'Host: example.com\r\n'<br /> 'Connection: Upgrade\r\n'<br /> 'Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n'<br /> 'Sec-WebSocket-Protocol: sample\r\n'<br /> 'Upgrade: WebSocket\r\n'<br /> 'Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n'<br /> 'Origin: http://example.com\r\n'<br /> '\r\n'<br /> '^n:ds[4U'<br /> )<br /><br />CORRECT_RESPONSE1 = "8jKS'y:G*Co,Wxa-"<br /><br />REQUEST2 = (<br /> 'GET /demo HTTP/1.1\r\n'<br /> 'Host: example.com\r\n'<br /> 'Connection: Upgrade\r\n'<br /> 'Sec-WebSocket-Key2: 1_ tx7X d < nw 334J702) 7]o}` 0\r\n'<br /> 'Sec-WebSocket-Protocol: sample\r\n'<br /> 'Upgrade: WebSocket\r\n'<br /> 'Sec-WebSocket-Key1: 18x 6]8vM;54 *(5: { U1]8 z [ 8\r\n'<br /> 'Origin: http://example.com\r\n'<br /> '\r\n'<br /> 'Tm[K T2u'<br /> )<br /><br />CORRECT_RESPONSE2 = 'fQJ,fN/4F4!~K~MH'<br /><br />def parse_request(request):<br /> req = {}<br /> segments = request.split('\r\n\r\n', 1)<br /> assert(len(segments) == 2)<br /> header, payload = segments<br /> lines = header.split('\r\n')<br /> line = lines.pop(0)<br /> items = line.split('\x20',2)<br /> assert(len(items) == 3)<br /> req['method'] = items[0]<br /> req['request_uri'] = items[1]<br /> req['http_version'] = items[2]<br /> for line in lines:<br /> parts = line.split(':\x20', 1)<br /> assert(len(parts) == 2)<br /> req[parts[0].lower()] = parts[1]<br /> req['payload'] = payload<br /> return req<br /><br />def get_word(key):<br /> digits=''<br /> spaces=0<br /> for char in key:<br /> if char.isdigit():<br /> digits += char<br /> elif char == '\x20':<br /> spaces += 1<br /> assert(spaces)<br /> assert(digits)<br /> keynum = int(digits)<br /> assert(keynum % spaces == 0)<br /> wordval = keynum / spaces<br /> assert(wordval < 2 ** 32)<br /> return struct.pack("!I", wordval)<br /><br />def create_token(req):<br /> key1 = req.get('sec-websocket-key1', None)<br /> assert(key1 is not None)<br /> word1 = get_word(key1)<br /> key2 = req.get('sec-websocket-key2', None)<br /> assert(key2 is not None)<br /> word2 = get_word(key2)<br /> salt = req.get('payload', None)<br /> assert(salt is not None)<br /> assert(len(salt)==8)<br /> return hashlib.md5(word1 + word2 + salt).digest() <br /><br />req = parse_request(REQUEST1)<br />print create_token(req) == CORRECT_RESPONSE1<br /><br />req = parse_request(REQUEST2)<br />print create_token(req) == CORRECT_RESPONSE2<br /></pre><br /><br /><br /><div>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).</div><div><br /></div><div>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. </div>Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-218012982026091552.post-90735099843700849912011-04-22T09:07:00.000-07:002011-04-22T09:08:56.149-07:00Chrome WebSocket Protocol Update<div>Just making a note here; </div><div><a href="http://blog.chromium.org/2010/06/websocket-protocol-updated.html">http://blog.chromium.org/2010/06/websocket-protocol-updated.html</a></div>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-53069330942993492022010-08-22T10:53:00.000-07:002010-08-22T12:00:00.913-07:00HTTP ServerIn 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.<br /><br />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.<br /><br />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 <span style="font-style: italic;">501 Not Implemented</span>.<br /><br />Here's my index.html rendered on Chromium:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqNrnL9rL6u6X5BRDhGDcdBWwJIN5FLQEge_lxtkMXHjHQWnhjAFpD0v8hzyM1-Ob6_jRnrKfpcjSwxW4vDpCfVNR76NEzTVMbQTMmGRGi_YP63SJZsX8sdkmpaVLv2b-FdSzp8twPT7HJ/s1600/Screenshot-Hello+World+-+Chromium-1.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 210px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqNrnL9rL6u6X5BRDhGDcdBWwJIN5FLQEge_lxtkMXHjHQWnhjAFpD0v8hzyM1-Ob6_jRnrKfpcjSwxW4vDpCfVNR76NEzTVMbQTMmGRGi_YP63SJZsX8sdkmpaVLv2b-FdSzp8twPT7HJ/s400/Screenshot-Hello+World+-+Chromium-1.png" alt="" id="BLOGGER_PHOTO_ID_5508306358646475970" border="0" /></a>This actually represents three separate HTTP GET requests; the index.html file, the skull image, and the tiny skull favicon shown on the tab.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGy6-goTBMScBYjmLLlboFCWAXkL7u5o5T1zxVxDz47IhqqOZK9JLagoFPwIs9AihFXXXC1NXa5OluIcK6Bck9G-895OPMTOiiAy7HQPLiQOqire5r-YEIxJb6e-ndRwTG7KlHZ1-cBAjh/s1600/Screenshot-view-source:http:--localhost:7777-+-+Chromium.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 210px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGy6-goTBMScBYjmLLlboFCWAXkL7u5o5T1zxVxDz47IhqqOZK9JLagoFPwIs9AihFXXXC1NXa5OluIcK6Bck9G-895OPMTOiiAy7HQPLiQOqire5r-YEIxJb6e-ndRwTG7KlHZ1-cBAjh/s400/Screenshot-view-source:http:--localhost:7777-+-+Chromium.png" alt="" id="BLOGGER_PHOTO_ID_5508306498494183570" border="0" /></a><br />The skull is a compressed SVG, to get Chromium to display it I had to add Content-encoding parameter to my HTTP Response;<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZSjk7NjFz9Jy7PVfA8I_mNxxCzDOnwILBocJGkOgEMdi236CxtgXAl6Xtlps7CcGjiBE-ruBpAJ9oTWjvctd7EDFzADjNhpmf2rG3VaoM5LswjYOkQVPRj5fLCkJza6tl5fxMy8Lvss6X/s1600/Screenshot-Developer+Tools+-+http:--localhost:7777-.png"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 274px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZSjk7NjFz9Jy7PVfA8I_mNxxCzDOnwILBocJGkOgEMdi236CxtgXAl6Xtlps7CcGjiBE-ruBpAJ9oTWjvctd7EDFzADjNhpmf2rG3VaoM5LswjYOkQVPRj5fLCkJza6tl5fxMy8Lvss6X/s400/Screenshot-Developer+Tools+-+http:--localhost:7777-.png" alt="" id="BLOGGER_PHOTO_ID_5508306580063223186" border="0" /></a>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-29592904478711108702010-08-22T10:18:00.000-07:002010-08-22T10:53:02.197-07:00NetboaI've started the total re-write of my asynchronous network library <a href="http://code.google.com/p/miniboa/">Miniboa</a>. The new code, in addition to being cleaner, is different in several ways.<br /><br /><span style="font-weight: bold;">What's New</span><br /><ul><li>No longer Telnet-centric; my aim is to keep connections generic and wrap them in protocol handlers.</li><li>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.<br /></li><li>Added hooks for coroutine-based, character-at-a-time protocol handlers.</li><li>No longer line-based; the new API has <span style="font-weight: bold;">get_ch() </span>to get one character or <span style="font-weight: bold;">get_input()</span> to retrieve the entire input buffer. This is support users that want to write features like text editors and command completion.<br /></li><li>New event; <span style="font-weight: bold;">on_input()</span> that is called when the data is received from the client.</li><li>When run under Linux, it uses the <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/epoll.4.html"><span style="font-weight: bold;">Epoll</span></a> API introduced in Linux kernel 2.5.44. All other platforms user<span style="font-weight: bold;"> Select()</span>. </li></ul>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.Unknownnoreply@blogger.com4tag:blogger.com,1999:blog-218012982026091552.post-45369766139436535142010-05-02T18:38:00.001-07:002010-05-02T19:16:01.650-07:00The Plague that struck AzerothI ran across some old screen-shots from <a href="http://en.wikipedia.org/wiki/Corrupted_Blood_incident">an event that occurred in the MMO World of Warcraft on September, 13 2005</a> 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, <span style="font-style: italic;">and massively contagious</span>, regardless of where they went.<br /><br />This is what the main Horde city of Orgrimmar looked like;<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPvZ0Yli2_QFHsWkDQm1kl9BDF4AMPjqL4YR-WcC4290s52pXkRpiJoBiF90AOyl4JLUfDeqSxRRbMqjdnucSRCNSP4WXSL58-xSHUCkN6cM6s8_v4NILYcDUqc4lOFpurgyx5lC5CR_BU/s1600/WoWScrnShot_091605_214228.jpg"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPvZ0Yli2_QFHsWkDQm1kl9BDF4AMPjqL4YR-WcC4290s52pXkRpiJoBiF90AOyl4JLUfDeqSxRRbMqjdnucSRCNSP4WXSL58-xSHUCkN6cM6s8_v4NILYcDUqc4lOFpurgyx5lC5CR_BU/s400/WoWScrnShot_091605_214228.jpg" alt="" id="BLOGGER_PHOTO_ID_5466855072406086946" border="0" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHvtlzZzdKVHEGI04Y0W3zAoSnEEyV6qd82bH4QTaiMEX-5nI4S9C3Ommz-x2F4EUJi5xrhTBqJw9Iz0-fByx7Lce5kxbRVuJn1g_X-CjmJqqTMbq_MsAbq7PxMHegngBxecvlR8GCWl_7/s1600/WoWScrnShot_091605_213911.jpg"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHvtlzZzdKVHEGI04Y0W3zAoSnEEyV6qd82bH4QTaiMEX-5nI4S9C3Ommz-x2F4EUJi5xrhTBqJw9Iz0-fByx7Lce5kxbRVuJn1g_X-CjmJqqTMbq_MsAbq7PxMHegngBxecvlR8GCWl_7/s400/WoWScrnShot_091605_213911.jpg" alt="" id="BLOGGER_PHOTO_ID_5466855061291882834" border="0" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHYhZFWdTUZIR9tnBTKlbhLfx2vmvi1Dw8YNqpk0UIBouzfxXsUfOg4B8gW0YperoFTb678kWCUzNjKFuugMiF0l6yG0P2J5rRn2UFhN52MNVRhgmnjKfP1EpJ8VzRJssQhdm1Oh7esbls/s1600/WoWScrnShot_091605_213727.jpg"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHYhZFWdTUZIR9tnBTKlbhLfx2vmvi1Dw8YNqpk0UIBouzfxXsUfOg4B8gW0YperoFTb678kWCUzNjKFuugMiF0l6yG0P2J5rRn2UFhN52MNVRhgmnjKfP1EpJ8VzRJssQhdm1Oh7esbls/s400/WoWScrnShot_091605_213727.jpg" alt="" id="BLOGGER_PHOTO_ID_5466855055368569522" border="0" /></a><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6Lu_bTJEcxiXlbFrbcxIZnWR2YIsMxcgI0dliVMiJBwOjG3CPGHLcyAHadLooSeO-Z8o7fyR9L30h2wCjgteB1mHhyf9uzpZSjQE2-FmnZFCtUvV_7cqZt8CX6lLP7ilWrpSxpoCeq5hl/s1600/WoWScrnShot_091605_213620.jpg"><img style="display: block; margin: 0px auto 10px; text-align: center; cursor: pointer; width: 400px; height: 320px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg6Lu_bTJEcxiXlbFrbcxIZnWR2YIsMxcgI0dliVMiJBwOjG3CPGHLcyAHadLooSeO-Z8o7fyR9L30h2wCjgteB1mHhyf9uzpZSjQE2-FmnZFCtUvV_7cqZt8CX6lLP7ilWrpSxpoCeq5hl/s400/WoWScrnShot_091605_213620.jpg" alt="" id="BLOGGER_PHOTO_ID_5466855041833221378" border="0" /></a>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.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-9126560016307070372010-04-19T17:03:00.000-07:002010-04-19T17:11:59.782-07:00Not Actually DeadTo 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.Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-218012982026091552.post-58420232742636022282010-03-21T09:49:00.000-07:002010-03-22T05:34:11.763-07:00Thinking about Miniboa 2.0In the early 90s, my hobby was running a computer bulletin board system (BBS) in Virginia called<span style="font-style: italic;"> Grendel's Place</span>. I started with a free package called <a href="http://en.wikipedia.org/wiki/RBBS-PC">RBBS</a> and then bought a license for the more spiffy <a href="http://en.wikipedia.org/wiki/TriBBS">TriBBS</a>. Like the joke said; <span style="font-style: italic;">a sysop is someone who spends thousands of dollars to let other people use his computer</span>.<br /><br />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.<br /><br />BBSes had a wealth of <a href="http://en.wikipedia.org/wiki/Category:Door_games">Door</a> 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 <a href="http://en.wikipedia.org/wiki/TradeWars_2002">Tradewar 2000</a> and <a href="http://en.wikipedia.org/wiki/Solar_Realms_Elite">Solar Realms Elite</a> 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.<br /><br />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 <a href="http://www.johndaileysoftware.com/products/bbsdoors/globalwar/">Global War</a>:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmNFm8B2PucCpfT0FC-AgOAAaBmPDRTXQ81SJiEblM9kcRtleW1iAxwag9w8vlJHAAe-JRf5RzS0HWrIFtOz6e0r6kAnTgYs3QJhh3-LbTEpVJFTO4OZIIM8aZwqNnh7rCopWl2BRFL5qZ/s1600-h/img-screen_gwarafrica.gif"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 250px;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgmNFm8B2PucCpfT0FC-AgOAAaBmPDRTXQ81SJiEblM9kcRtleW1iAxwag9w8vlJHAAe-JRf5RzS0HWrIFtOz6e0r6kAnTgYs3QJhh3-LbTEpVJFTO4OZIIM8aZwqNnh7rCopWl2BRFL5qZ/s400/img-screen_gwarafrica.gif" alt="" id="BLOGGER_PHOTO_ID_5451206479525040290" border="0" /></a><br />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.<br /><br />Telnet clients, on the other hand, are <span style="font-weight: bold;font-size:130%;" >horrible</span>.<br /><br />They are <span style="font-weight: bold;">horrible</span> 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.<br /><br />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.<br /><br />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; <span style="font-weight: bold;">client.get_cmd()</span> and <span style="font-weight: bold;">client</span>.<span style="font-weight: bold;">send()</span>.<br /><br />Whew, that's too much background eh?<br /><br />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. <br /><br />David Beazley, author of the excellent <a href="http://www.amazon.com/gp/product/0672329786/">Python Essential Reference</a>, gave a lecture entitled <a href="http://www.dabeaz.com/coroutines/">A Curious Course on Coroutines and Concurrency</a> at PyCon 2009. You can watch a video of it here:<br /><br /><a href="http://blip.tv/file/1996136/">Part 1</a><br /><a href="http://blip.tv/file/1995823/">Part 2</a><br /><a href="http://blip.tv/file/1995872/">Part 3</a><br /><br />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.Unknownnoreply@blogger.com0