{"id":65,"date":"2008-09-15T15:46:37","date_gmt":"2008-09-15T21:46:37","guid":{"rendered":"http:\/\/blog.blockos.org\/?p=88"},"modified":"2014-09-21T17:08:07","modified_gmt":"2014-09-21T16:08:07","slug":"fireballs-of-fire","status":"publish","type":"post","link":"https:\/\/blog.blockos.org\/?p=65","title":{"rendered":"Fireballs of fire!"},"content":{"rendered":"<p>EEK! 6 months !<\/p>\n<p>It&#8217;s not that I had nothing to write. On the contrary, I have a lot to post. I just didn&#8217;t found the time to write&#8230; Erm.. Let&#8217;s go with a 7 months old stuff.<\/p>\n<p>While working on the tracker, I wanted to add some kind of &#8220;live&#8221; mode. The user could choose which sequence to play, modify it, control volume, add an effect etc&#8230; So I came with the idea to associate a command to a key combo.<\/p>\n<p>Combos are detected by walking down a tree. Each time a button is pressed, we look at the current node and check if one of its children is associated to this button. If that&#8217;s the case, this child becomes the new current node. Otherwise there&#8217;s no matching combo and we reset the current node to the tree root.  If the button pressed didn&#8217;t not changed for a certain amount of time (timeout), we check there&#8217;s a child corresponding to the current button. This way, we can detect combo containing a sequence like &#8220;press A button during t seconds&#8221;.<\/p>\n<p>Let&#8217;s look at a simple example.<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/blog.blockos.org\/wp-content\/uploads\/2008\/09\/combo.png\" alt=\"\" \/><\/p>\n<p>Mkit will successively return :<strong> DOWN<\/strong> &#8211; <strong>DOWN+RIGHT<\/strong> &#8211; <strong>RIGHT<\/strong> &#8211; <strong>0<\/strong> (aka nothing) &#8211; <strong>A<\/strong><\/p>\n<p>We have <strong>0<\/strong> because when you go from <strong>RIGHT<\/strong> to <strong>A<\/strong>, there&#8217;s a moment when you don&#8217;t press any button. And as we check keys every vsync, we necessary get <strong>0<\/strong> (unless if you are Superman on Redbull).<\/p>\n<p>Consider we want to handle the following combos :<\/p>\n<p><img decoding=\"async\" src=\"http:\/\/blog.blockos.org\/wp-content\/uploads\/2008\/09\/comboA.png\" alt=\"\" \/><\/p>\n<p><img decoding=\"async\" src=\"http:\/\/blog.blockos.org\/wp-content\/uploads\/2008\/09\/comboB.png\" alt=\"\" \/><\/p>\n<p><img decoding=\"async\" src=\"http:\/\/blog.blockos.org\/wp-content\/uploads\/2008\/09\/comboC.png\" alt=\"\" \/><\/p>\n<p>The combo tree is very similar to a lexical tree. A node can have 0 to N children but only one parent.<br \/>\n<img decoding=\"async\" src=\"http:\/\/blog.blockos.org\/wp-content\/uploads\/2008\/09\/comboTree.png\" alt=\"\" \/><\/p>\n<p>The tree is store in level-order with the longest path first. This means that you&#8217;ll have to be very careful and store node children by their depth. We have for our example :<\/p>\n<p><strong>Root &#8211; Down &#8211; A &#8211; Down+Right &#8211; Down+Left &#8211; Nothing &#8211; Left &#8211; Right &#8211; A &#8211; Nothing &#8211; Nothing &#8211; Leaf &#8211; A &#8211; B &#8211; Leaf &#8211; Leaf<\/strong><\/p>\n<p>In the current implement, the tree is stored in 2 arrays. The first one contains the index to first child of a node. And the second the key for each node.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">combo_tree : 1, 3, 5, 6, 7, 8, 9, 10, 128 | 2, 11, 12, 128 | 0, 128 | 1\r\ncombo_keys: .db 255, JOY_DOWN, JOY_I, JOY_DOWN | JOY_RIGHT, JOY_DOWN | JOY_LEFT\r\n.db 0, JOY_RIGHT, JOY_LEFT, JOY_I, 0, 0, JOY_I, JOY_II<\/pre>\n<p>The root is always at index <strong>0<\/strong> and we associate the key value <strong>255<\/strong> to it.<br \/>\nThe tree traversal algorithm is very simple. For a given node <strong>i<\/strong>, we know that its children are at indices <strong>j<\/strong> from <strong>combo_tree[i]<\/strong> to <strong>combo_tree[i+1]-1<\/strong>. And that we can reach them only if the current key is <strong>combo_key[j]<\/strong>. If the <strong>bit 7<\/strong> of a value in the children list is set, this means that we have a transition a leaf. The remaining bits are used as an index to a jump table.<br \/>\nThe traversal looks like this:<\/p>\n<p> i \/\/ current node index<br \/>\nfor(j=combo_tree[i]; j<combo_tree[i+1]; ++j)\n    if(combo_key[j] == key)\n        if(combo_tree[j] &#038; 128) \/\/ is it a leaf?\n            fnctl = combo_tree[j] &#038; 127\n            call(fnctl)\n            i = 0 \/\/ Back to root\n        else\n            i = j \/\/ The child becomes the new current node\n        return\ni = 0 \/\/ We didn't find a suitable child. So we jump back to root.\n[\/sourcecode]\n\nThe only thing missing here, is the key timeout and repetition. The key timeout is the number of vsync to wait before going back to root (<strong>COMBO_DEFAULT_DELAY<\/strong> in combo.asm). Note that in current code <strong>combo_tree<\/strong>, <strong>combo_keys<\/strong> and <strong>combo_jumpTable<\/strong> are in RAM. So you&#8217;ll have to initialize them before-hand.<\/p>\n<ul>\n<li><a href=\"http:\/\/blockos.org\/releases\/pcengine\/code\/combo\/combo.asm\"><\/a><\/li>\n<li><a href=\"http:\/\/blockos.org\/releases\/pcengine\/code\/combo\/combo.pce\">[rom]<\/a><\/li>\n<\/ul>\n<p>If you have any question, feel free to leave a comment or go and join the <a href=\"http:\/\/pcedev.blockos.org\"><strong>[PCEdev Forum]<\/strong><\/a>.<\/p>\n<p>Talking about web stuff,  I finally updated the <a title=\"gallery\" href=\"http:\/\/blockos.untergrund.net\/mooz\" target=\"_blank\">gallery<\/a>. Hurray!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>EEK! 6 months ! It&#8217;s not that I had nothing to write. On the contrary, I have a lot to post. I just didn&#8217;t found the time to write&#8230; Erm.. Let&#8217;s go with a 7 months old stuff. While working on the tracker, I wanted to add some kind of\u2026 <a class=\"continue-reading-link\" href=\"https:\/\/blog.blockos.org\/?p=65\">Continue reading<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[3,6],"tags":[27,30],"_links":{"self":[{"href":"https:\/\/blog.blockos.org\/index.php?rest_route=\/wp\/v2\/posts\/65"}],"collection":[{"href":"https:\/\/blog.blockos.org\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.blockos.org\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.blockos.org\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.blockos.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=65"}],"version-history":[{"count":35,"href":"https:\/\/blog.blockos.org\/index.php?rest_route=\/wp\/v2\/posts\/65\/revisions"}],"predecessor-version":[{"id":975,"href":"https:\/\/blog.blockos.org\/index.php?rest_route=\/wp\/v2\/posts\/65\/revisions\/975"}],"wp:attachment":[{"href":"https:\/\/blog.blockos.org\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=65"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.blockos.org\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=65"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.blockos.org\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=65"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}