Friday, August 7, 2015

Why most WYSIWYG composers (and editors) suck

Whenever you need to write something that contains anything more complicated than plain text, you usually have a WYSIWYG-style editor at your disposal, be it authoring a simple cover letter in MS Word or using post composer on this blog.

WYSIWYG stands for "what you see is what you get" and means that you have simple text-styling tools (bold, italic, font size/color, etc.) at your disposal. It is usually a good thing to have - not because what you see in the composer is what you finally get (this is, generally speaking, false: your blog post will be affected by your template, the viewer's browser and many other factors; even in the telltale Word getting things exactly as you want them is not always easy), but because it is a time saver: it is often faster to press a toolbar button than to manually write HTML tags every time you need them.


But WYSIWYG has a huge caveat in that it encourages users to omit the most important step in text formatting: logic. Usually, any text can be broken down into logical blocks: here is the "main text", these sentences are "more important" or "less important", here is a "heading", this here is a "footnote", this is a "caption", and so on. And it is these logical blocks that we "decorate": make their text smaller / larger, bold/italic/underlined, set a different color / background, etc.

You've probably already guessed what I am getting at: styles. Now the (disappointing) truth is that any document the slightest bit more complicated than your garage sale ad will need styles. Unless you're writing something like a cyber-punk hippie gothic new age underground pamphlet, you'll want your logic blocks look the same throughout your document -- or many documents in case of a blog.

The caveat about WYSIWYG composers is that they direct your attention away from styles and towards text decoration. Most of you would still have an "implicit style table" in your memory, and would try to stick to it. This might work -- for a fraction of people who really deserve to have "paranoid attention to detail" in their résumés. The rest of us will inevitably have different  look to their logically identical fragments -- ranging from slightly different to markedly different and all the way to "you must have been daydreaming as you were writing this" different. 

And that's not the worst of it. Suppose you wrote half your big document (say a Ph.D. thesis in maths) and the formatting requirements were updated, urging you to change the font and coloring of all lemmas in your text (yes, all 118 of them). Or you wrote a bunch of blog posts and discovered that your quotations look ugly as they are, black on yellow centered, and should instead be green on off-white beige and left-aligned. If you had used your styles correctly, the change would be a few minutes. Otherwise, you're in for a night of miserable and thankless drudgery. 

This is why the first thing I did before I even started the blog was adding this to the template:

<!-- CUSTOM STYLES FOR CODE ITEMS-->
<style>
  .mycode {font-family:"Consolas", "Monaco", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace; 
           background-color:#E8E8E8;}
  .myaux {font-size:80%; color:#666699;background-color:#FFFFF0}
  .myinline {color:#999999;}
</style>
saving myself a whole lot of work in the future. Yes, I need to set the custom classes in the HTML mode, but it is way better than having to set all attributes manually (and rely on my less-than-ideal memory) each time I use them - not to mention that I need to go to the HTML mode to enter the code snippets anyway.

Thursday, August 6, 2015

jQuery Tetris

Following the previous exercise with jQuery, I wanted to do something more complicated, such as the classic Tetris game. Unlike some other examples where jQuery is primarily used for visualization and the game itself is implemented "traditionally" using a tile matrix, I was interested in using the powers of jQuery to program the game mechanics directly. (Discalimer: I was also interested in writing a working game as quickly as possible, and I implemented ideas on the go, so I apologize if some of the code will look unpolished.)

So, let us, again, construct some skeleton interface:
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>jQuery tetris</title>
  <style>
  span {
    
    float: left;
    position:absolute;
    width:20px;
    height:20px;
  }
   span.element {     background-color: #3f3;   }
   span.backdrop {     background-color: #ff3;   }
   .box{position:absolute;background-color: #ffe;
        top:50px;left:0;
        width:500px;height:500px;}
  </style>
    
</head>
<body>

    <div id="container" class="box"></div>

    <button id="bLeft">Left (A)</button>
    <button id="bRight">Right (D)</button>
    ----
    <button id="bRotLeft">Rotate Left (Q)</button>
    <button id="bRotRight">Rotate Right (W)</button>
    ----
    <button id="bDown">Down (S)</button>    
    <button id="bGround">Ground</button>    

 
<script src="jquery.js"></script>
Here we reserve the class element to denote the tiles (squares) in the current shape that we can control, and backdrop will be all tiles that have fallen in place.

Let's begin by writing some function that will generate a random Tetris shape. (I did all the testing using just one shape and then added the easiest kind of generator I could think of, so it is rather makeshift and does not ensure equal probability of all shapes. Still, it has the advantage of doing the job without switch-case constructs and copy-pastes):
<script>
  
function spawn(oX,oY,kind) {
 var cauldron='<span class="shape"> </span>';
 var potion='<span class="element"> </span>';
 var d1=kind%3; var d2=parseInt((kind%9)/3); var d3=parseInt(kind/9);
 return $(cauldron).appendTo($("#container")).css({left:oX+"px",top:oY+"px"})
  .append($(potion).css({left:(0)+"px",top:0+"px"}))
  .append($(potion).css({left:(0-20)+"px",top:0+"px"}))
  .append($(potion).css({left:0+(((d1==0)||((d2==d1)&&(d3!==0)))?20:(((d3==0))?0:-20))+"px",top:0+(((d1==0)||((d2==d1)&&(d3!==0)))?0:((d1==1)?20:-20))+"px"}))
  .append($(potion).css({left:-20+((d2==0)?-20:0)+"px",top:0+((d2==0)?0:((d2==1)?20:-20))+"px"}));
}  
As you can see, it places four tiles with class element into a container with class shape whose only purpose is to allow relative positioning of the tiles within the shape.

Now let us program the functionality of the Left and Right buttons. This is conveniently done using the .offset() method:
$("#bLeft").click(function(){var pos=current.offset().left;
                             if(posLeftmost()>0){current.offset({left:pos-20})}})  
                             
$("#bRight").click(function(){var pos=current.offset().left;
                             if(posRightmost()<480){current.offset({left:pos+20})}})  

function posLeftmost(){var min=600;$(".element").each(function(){var here=$(this).offset().left;if (here<min) {min=here} }); return min}
function posRightmost(){var max=0;$(".element").each(function(){var here=$(this).offset().left;if (here>max) {max=here} }); return max}

where posLeftmost() and posRightmost() are two auxiliary functions to determine the position of the left-/rightmost tile in the shape, so that we cannot move the shape out of bounds. These functions do a simple max/min search through the positions of all tiles in the current shape.
Moving down is implemented similarly, but here we need to check whether the already-fallen pieces block the movement of the current shape:
$("#bDown").click(function(){var pos=current.offset().top;if (shapeCanMoveDown()) {current.offset({top:pos+20})}})  

function auxIsFree(pos){var isFree=true;
                        $(".backdrop").each(function(){if (($(this).offset().top==pos.top + 20)&&($(this).offset().left==pos.left)){isFree=false}});return isFree}
function shapeCanMoveDown(){var canMove=(posLowest()<530);
                            $(".element").each(function(){if (!auxIsFree($(this).offset())) {canMove=false}})
                            return canMove}
function posLowest(){var max=0;$(".element").each(function(){var here=$(this).offset().top;if (here>max) {max=here} }); return max}

As we see, we just search through the backdrop using .each() to see whether any backdrop tile occupies the space beneath each tile of the shape.
Strictly speaking, left/right motion also needs this type of checking, which I did not bother to implement because it is totally analogous. We'll see below how this omission opens a way for "pass-through-wall" type cheats.

Now, if the shape cannot move down any more, the rules dictate that it should freeze in place. To do this, we implement another function that simply moves all the element's tiles to the backdrop (and we tie it to the Ground button for testing purposes):
function cement(){$(".element").removeClass("element").addClass("backdrop")}                               
$("#bGround").click(function(){cement();current=spawn(240,40,Math.floor(Math.random()*18))})                       


Rotating shapes is a bit more tricky. We make use of the relative placement of element's tiles in their container, manipulating their CSS position attributes:
$("#bRotRight").click(function(){current.children().each(function(index){
        posX=parseInt($(this).css("left"));
        posY=parseInt($(this).css("top"));
                                $(this).css({left:-posY,top:posX}) }) })

$("#bRotLeft").click(function(){current.children().each(function(index){
        posX=parseInt($(this).css("left"));
        posY=parseInt($(this).css("top"));
                                $(this).css({left:posY,top:-posX}) }) })
Again we did not bother to do any bounds checking. One way of doing this would be to call auxIsFree() on all the shape tiles after rotation and unconditionally rotate in the opposite direction should any of the calls return false.


What remains to be done logic-wise is the removal of filled lines from the backdrop. Perhaps too straightforward, my idea was to loop through the backdrop line by line (iterating y-coordinate) bottom to top and:
- if there are "too many" tiles on the current line, remove such tiles from DOM, and
- for all tiles above the current line, move them down in a similar fashion as we did with the element.
The easiest way is via the .filter() method, like this:
function posTallest(){var min=1000;$(".backdrop").each(function(){var here=$(this).offset().top;if (here<min) {min=here} }); return min}

function rowCount(pos){return $(".backdrop").filter(function(){
                               return $(this).offset().top==pos}).length}

function backdropPROCESS(){var tallest=posTallest();
       for (pos=550 ; pos>=tallest;pos-=20)
                           { if (rowCount(pos)>=25) {
                             $(".backdrop").filter(function(){return $(this).offset().top==pos}).remove();
                             $(".backdrop").filter(function(){return $(this).offset().top<pos})
                              .each(function(){$(this).offset({top:($(this).offset().top)+20})});
                              pos+=20; //a mortal sin here but this is the easiest way to make an iteration repeat itself
                             }
                            }}

This is the only place we ever need a for-loop in the entire game. I am pretty sure I could do without hard-coding y-coordinates but could not wait to get a functional game.

Finally, let us add the main controller for the game, launching it when the document's DOM is ready:
function TetrisLOOP(){
 var speed=500;
 var pos=current.offset().top;
 if (shapeCanMoveDown()) {current.offset({top:pos+20});setTimeout(TetrisLOOP,speed)}
 else {
  cement(); 
  backdropPROCESS();
  if (posTallest() > 150) {current=spawn(240,40,Math.floor(Math.random()*27));setTimeout(TetrisLOOP,speed)} 
  else {$("#container").css("background","#ffcccc").append($("<h1> Game over </h1>"));
    $(".backdrop").css("background","red");
    $("#bGround").removeAttr('disabled');}
  }

$( document ).ready(function() {
 $("#bGround").attr('disabled','disabled');
 current=spawn(240,40,Math.floor(Math.random()*10));
 var mainLOOP=setTimeout(TetrisLOOP,1000);
});

and a (very primitive) code block to enable WSAD-style keyboard control:
$(document).keypress(function(event){switch(event.which)
  {case 97:$("#bLeft").click();break;
   case 100:$("#bRight").click();break;
   case 115:$("#bDown").click();break;
   case 113:$("#bRotLeft").click();break;
   case 119:$("#bRotRight").click();break;}
  })

And we're all set - enjoy! Here is the link to the complete code for your experimentation.
Since I was yearning to get a functional jQuery Tetris as quickly as I could, I blatantly ignored all the "design" elements (grid, bordered tiles, varying colors etc.), as well as purely gameplay-ish issues such as displaying the next shape, scoring, and varying speed/levels. All of this can be implemented rather trivially. There are also a number of bugs stemming from the absent movability checks for left/right/rotate operations. I leave it to the interested reader to see what "cheat issues" this can cause and how to correct them. :D



Wednesday, August 5, 2015

My jQuery "Hello World"

Here is a small example of a simple "Hello World" program that I wrote when getting familiar with jQuery (and, largely, JavaScript itself for that matter). 

To stand out from the crowd of programs that just display "Hello World" on the screen, the program helps the user say the real hello to the real world. For a country of choice, it checks the Timatic database to determine how easy it would be to actually travel there, based on the user's nationality and country of residence.

So let's construct some skeleton interface:

<html>
<head>
    <meta charset="utf-8">
    <title>jQuery Hello (real) World</title>
  <style>
  
  iframe { position: absolute;
   left: 0;
   top: 100px;}
  .large{background-color: #ffffee;width:650px;height:400px;float:left;}
  form{ font-family: "Arial"; float:left;}
  input{width:40px;}
  </style>
</head>
<body>
<form id="dynamicForm">TIRV:</form>
    
<iframe id="timatic" class="large" src="https://www.timaticweb.com/cgi-bin/tim_client.cgi?ExpertMode=TIHELP&user=OMITTED&subuser=OMITTEDB2C"></iframe>

where the <iframe> element will contain the result of the Timatic query. Let's then define two functions that perform the query:

<script src="jquery.js"></script>
<script>
   
function sendquery() {
$("#timatic").attr("src","https://www.timaticweb.com/cgi-bin/tim_client.cgi?ExpertMode="+formstring()+"&user=OMITTED&subuser=OMITTEDB2C")
}

function formstring() {
 return "TIRV/"+$("input").map(function(){if (this.value!=="") {return this.id+this.value}}).get().join("/")+"/";
}

We note that the function formstring() refers to the <input> elements but there are none in the document. We will populate the interface from the script using jQuery methods:
$( document ).ready(function() {
//dynamically generate form 
var tokens=['NA','AR','DE','TR'];
tokens.forEach(function(token) {
 $("#dynamicForm").append($('<label for="'+token+'"> '+token+': </label>'));
 $("#dynamicForm").append($('<input id="'+token+'" type="text" onchange="sendquery()">'));
} )
});
</script>
</body>
</html>

That's it. These few lines create four input fields along with their respective labels and event handlers, so the sendquery() is called whenever the fields are changed. Note that the key strings in the Timatic query (NA/AR/DE/TR) are used for the element's ID, which allows to generate the query string automatically in a one-liner formstring() (see line #27) rather than construct it manually from four input fields. It also allows for easy scalability should there be more fields in the query string. (Timatic gurus: feel free to challenge yourselves to add functionality for a health (TIRH/TIRA) query that includes embarkation country (EM) and recently visited countries (VI) fields.

Here's an example:

Checking if a programmer from Belarus with a US residence can say "Hello World" in Canada while transiting Georgia
Note that the above examples won't work as quoted because I've stripped the credentials needed to query the Timatic Web service - sorry folks but I do not want to bust my Timatic access. However there are many (mostly airline and IATA) websites out there that offer Timatic service (albeit with a much more complicated interface), and access credentials could be easily fished by inspecting the source code of those websites.