Wednesday, July 5, 2017

Arduino LED driver and button press handler / debouncer: another "Hello, World"

Long story short, I've bought an Arduino for some small home automation (specifically, imitating remote control commands for a portable air conditioner in order to operate it on schedule).

Having run a few simple examples, I realized that whatever my device might be, I'd need some sort of a rudimentary GUI with some buttons to control the device and some LEDs to diagnose the device state. After some testing I realized that I need to solve several problems:
  1. The function digitalRead() only polls instantaneous button state ("is the button pressed now?"); some memory-based logic is needed for the detection of the event "the button was pressed and then released", which is a relevant GUI input. This logic should also filter out contact bounce , otherwise (as I quickly found out) a button press (and/or release) is very prone to being interpreted as several presses.
  2. I wanted to led LEDs blink (so that my device would signal its status without doubling up as a night light) concurrently with doing other tasks (such as the device's main function). 
  3. I wanted the routines to be general enough to scale to several LEDs and buttons concurrently and independently of one another.
Essentially I wanted to implement the two basic tutorials (Debounce and BlinkWithoutDelay) in a robust, encapsulated, scalable manner.

So, here's what I came up with, and decided to share it here in case it is useful for someone else.

Let's first define some global variables and structures:

// button names
#define B_UP 0
#define B_DN 1
#define B_SEL 2
#define B_MOD 3

//global constants
const byte nL=5;
const byte nB=4;
const byte pinL[nL]={11,10,9,6,5}; // should be PWM-enabled pins
const byte pinB[nB]={7,8,2,3};

const byte NOISE = 15; // ms to hit timed event for debouncing
const byte REP_DELAY = 400; // ms to initiate autorepeat
const byte REP_RATE = 100; // ms to regenerate autorepeat

//global variables, initialized
byte thisLED=0;
byte thisMODE=4;
For our array of LEDs we define some working data structures:
//data structures - LEDs
word quant[nL] = {125,125,125,125,125}; // time between ticks
word flash[nL] = {75,75,75,75,75}; // overrides if less than quant;
byte level[nL] = {120,140,100,80,40}; //brightness level 
byte mask[nL] = {B00000001,B00010001,B01010101,B00000011,B11001100};//bitwise mask

word next[nL]; // time of next LED handle event
word nextOff[nL]; // time of next turn-off event
byte cycle[nL] = {1,1,1,1,1}; //current bit in the mask, starting with 00000001, rotated left on every tick
The blinking pattern is defined as follows: every quant[i] milliseconds we define a "tick" for the LED number i; at each tick, we cycle the "current bit number" between 1 and 8 (using the internal variable cycle[i]), and turn the i-th LED either on or off, depending on the current bit in mask[i]. Additionally the LED is turned off on a "quench", when flash[i] milliseconds have elapsed since it was last turned on. Finally, level[i] simply defines the LED's brightness level. This can be illustrated like this:

This approach allows us to create a considerable variety of patterns ranging from simply "pulse N times per second" or "do N short blinks once in a while" to more complicated dash-dot patterns. It has its limitations (mostly related to the number of bits in the mask) but it is more than enough for the kinds of diagnostic output we aim for. Note that level and pattern are independently set for each LED.

By the same token we define some structures for the buttons:
//data structures - buttons
byte raw[nB] = {0,0,0,0}; //raw button state 
byte b_ready[nB] = {1,1,1,1}; //whether button is ready 
byte cooked[nB] = {0,0,0,0}; // filtered button state
byte autorepeat[nB] = {1,1,0,0}; //whether to autorepeat on button xx
byte event[nB] = {0,0,0,0}; //button press event, must be consumed on handle and re-generated on autorepeat

word last[nB] = {0,0,0,0}; // time last pressed

Here the only "parameter" is autorepeat[i], defining whether the button number i should auto-repeat. The rest are internal variables needed for debouncing and handling button press events.

Next we write the handler for the LEDs:

void pollLEDs()
{
  word now = (word)millis();
  for (byte i=0;i<nL;++i)
  {
    if ((int)(now-next[i])>=0) // next tick reached
    {
      nextOff[i] = next[i]+flash[i];
      next[i]+=quant[i]; // move next (and nextoff) forward
      boolean state = ((cycle[i] & mask[i])!=0); 
         // determine current state from mask
      analogWrite(pinL[i], (state)?level[i]:0); \
         // light up or quench according to state
      cycle[i]=cycle[i]<<1; if(cycle[i]==0) cycle[i]=1; 
         // cycle current bit in mask 
    }
    if ((int)(now-nextOff[i])>=0) 
    { // flash time exceeded before next tick
      nextOff[i]+=quant[i]; // just move forward
      analogWrite(pinL[i], 0); // quench
    }
  }
}

We see that it just implements the logic in the diagram above, using next[] and nextOff[] to store the timer values for the next tick and quench respectively. The only trick here is the use of bitwise arithmetic for cycle[] to enable quick comparison against mask[].

You can see that it is easy to make the time span between patterns longer than 8*quant[] without increasing the size of the mask by simply defining a separate period[nL] and then replace next[i]+=quant[i] with next[i]+=(cycle[i]!=1)?quant[i]:period[i]


Next we write the button handler:

void pollButtons()
{
 word now = (word)millis();
 for (byte i=0;i<nB;i++)
 {
   raw[i] = (digitalRead(pinB[i])==LOW)?1:0; // store to avoid repeated calls to digitalRead()
   // if button is ready and LOW recorded, record last pressed time and clear ready
   if (b_ready[i]==1 && cooked[i]==0 && raw[i]==1) {b_ready[i]=0;last[i]=now;}
   // if not ready and still LOW after tolerance, set filtered state, button press detected
   if (b_ready[i]==0 && cooked[i]==0 && raw[i]==1 && (word)(now-last[i]) > NOISE) cooked[i]=1;
   // if state is pressed and button released : clear filtered, restore b_ready, and generate event unless auto repeating
   if (b_ready[i]==0 && cooked[i]==1 && raw[i]==0 && (word)(now-last[i]) > NOISE) 
   {
     cooked[i]=0;
     b_ready[i]=1;
     if (autorepeat[i]<2) event[i]=1; else autorepeat[i]=1;
   }
   
   // handle auto repeat
   if (autorepeat[i]==1 && cooked[i]==1 && raw[i]==1 && (word)(now-last[i]) > REP_DELAY)
   { // initiate auto repeat
      autorepeat[i]=2;
      last[i] = now;
      event[i]=1;
   }
   if (autorepeat[i]==2 && cooked[i]==1 && raw[i]==1 && (word)(now-last[i]) > REP_RATE)
   { // if auto repeating, re-generate event periodically
      last[i] = now;
      event[i]=1;
   }
   
   //handle event once generated
   if (event[i]==1)
   {
     event[i]=0; //consume event
     control(i); //handle event
   }
 }
}

Note that it uses raw[i] to store the instantaneous state of the button number i, whereas ready[i] is a flag indicating whether that button is expected to receive input; it is set initially and cleared the instant the button press is detected (at which time the button timer is set via last[i]). The logic to determine whether the button has actually been pressed (so its filtered state, cooked[i], can be set) is "the button is still pressed some predetermined time after it was pressed initially"; I've used 15 ms as a ballpark and found it to work fine with my buttons. Once cooked[i] is set, the handler listens for the button release; once this happens, it generates an event (via setting event[i]) and return the button to its initial ready state.

For auto repeat support the amount of extension is minimal. We use the same timer to determine whether a certain time has elapsed since the button was pressed, and start generating events repeatedly at regular intervals until the button is released.

We can see from the code and description that this approach filters out the contact bounce both upon press and upon release. I am leaving it as an exercise for you to see why.


To test the implementation I made a "blink machine" quick breadboard circuit with five LEDs and four buttons, which would be used to control the way LEDs blink, like so:

 

The buttons functions are as follows:

  • [SELECT]: Cycle through LEDs to control.
  • [MODE]: Cycle through parameters to adjust:
    brightness, blink frequency, blink duration, blink pattern
  • [UP]: Increase the current parameter for the current LED.
  • [DOWN]: Decrease the current parameter for the current LED.
This corresponds to the following implementation of the event handler control()


void control(byte code)
{
 switch(code)
 {
   case B_SEL:
    thisLED++; if (thisLED>=nL) thisLED=0;
    notify();
   return;
   
   case B_MOD:
     thisMODE++; if (thisMODE>5) thisMODE=1;
     notify();
   return;

   case B_UP: case B_DN:
   {
    switch(thisMODE)
    {
  switch(thisMODE)
    {
      case 1: //level
       if (code==B_UP && level[thisLED]<255) level[thisLED]++ ;
       if (code==B_DN && level[thisLED]>0) level[thisLED]-- ;
       break;
      case 2: //period
       if (code==B_UP && quant[thisLED]<1000) quant[thisLED]+=20 ;
       if (code==B_DN && quant[thisLED]>40) quant[thisLED]-=20 ;
       break;
     case 3: //flash
       if (code==B_UP && flash[thisLED]<quant[thisLED]) flash[thisLED]+=10 ;
       if (code==B_DN && flash[thisLED]>20) flash[thisLED]-=10 ;
       break;
     case 4: //pattern up/down
       if (code==B_UP) mask[thisLED]++ ;
       if (code==B_DN) mask[thisLED]-- ;
       break;
     case 5: //pattern scramble/reset
       if (code==B_UP) mask[thisLED]=(byte)millis() ;
       if (code==B_DN) mask[thisLED]=1 ;
       break;
    }         
   }
   return;
 }
}

void notify() // show currently selected LED and mode
{
    analogWrite(pinL[thisLED],0);delay(100);
    for (byte i=1;i<=thisMODE;++i)
    {
      analogWrite(pinL[thisLED],255);delay(50);
      analogWrite(pinL[thisLED],0);delay(50);
    }
}

Note the fifth mode to quickly scramble and reset the bitwise mask. The auxiliary function notify() is used to visually indicate the currently selected LED and mode. Here, for simplicity and because the interface should be synchronous with user input, I do use delay() (pausing all other operations in the process). Exercise: During the operation of notify(), some of the LEDs may miss a tick. What will happen to their blinking pattern?

Finally to complete the sketch, here are its two main functions, setup() and loop(). Note that these are really short to isolate the button/LED workings from the rest of your code.

void setup() 
{
// initialize pins
  for (byte i=0;i<nL;++i) pinMode(pinL[i],OUTPUT);
  for (byte i=0;i<nB;++i) pinMode(pinB[i],INPUT_PULLUP);
  for (int j=0;j<nL;++j) {analogWrite(pinL[j],255);delay(75);analogWrite(pinL[j],0);delay(150);} // "splash screen"
  Serial.begin(9600); // optional, for debugging
// initialize LED timers
  word now = (word)millis();
  for (byte i=0;i<nL;++i)
  {
    next[i] = now+quant[i];
    nextOff[i] = next[i]+flash[i];
  }
}

void loop() 
{
    // just call the two handlers
    pollLEDs();
    pollButtons();
}

After some debugging the implementation tested fine, but the real use is that pollLEDs() and pollButtons() can be used within any sketch; of course, control() will need to be re-written. The pattern for the LEDs can be altered at runtime by changing the corresponding variables.
Some concluding thoughts:
  • I designed the LED pins to be PWM pins but non-PWM pins may be used just as well; just replace analogWrite with digitalWrite, disregarding level[].
  • LEDs can be any driving circuits that need timed controls. Similarly, buttons can be any form of digital input. If you use analog input, keep in mind that analogRead takes time and a much slower dead-time processing will be needed instead of debouncing.
  • This is really basic, but -- don't use pins 0 and 1 if you use serial communication for debugging. I've spent a few hours debugging until I figured out my debug messages were mimicking button presses.

Friday, June 30, 2017

The saga of an old Sears Craftsman garage door opener

This isn't exactly about curiouser code; rather, it's about curiouser electronics and troubleshooting.

Prologue

We bought a house with a built-in garage with an old (very very old, pre-1997) Sears Craftsman electric garage door opener, which the old owners claimed was defunct but never bothered to replace. Rather than just go and replace it myself, I tried my luck playing with the bundle of low-voltage wires dangling from it - and found that it did work if you connect the right wires.

Better than nothing.

Next, I downloaded the manual and tried searching Amazon for a possible replacement remote. And was lucky again: after a few unsuccessful tries, this little dongle successfully paired with the opener and worked.

Much better than nothing!

However, my curiosity was aroused at this point. The manual mentioned that there was a possibility to attach a pair of optional optical-beam safety sensors to prevent the door from closing if something was in the way, or to reverse the door closing if someone crossed the beam. Since the garage is very tight and I don't want the closing door to crash into my rear bumper (or, heaven forbid, to injure someone), this was definitely a feature I wanted.

Part 1. Research

The easiest and most obvious solution -- to order the safety sensors set as an official accessory from Sears -- was quickly put to an untimely end with a "No longer available" message on the official spare parts website.

The next one to try, less obvious but still easy, would be to order a set of 3rd-party sensors on Amazon (in the same way as I did with the remote) and hope that it works. Alas, no luck this time -- everything I could see on the aftermarket was for newer models, and would not be compatible with my opener.

The real snag was that my opener was "old but not too old" -- recent enough so that optical safety sensors were offered as an optional feature, but manufactured before these sensors were made mandatory. And while this was precisely what made it possible to get it running without the safety beam at all (I might have given up trying had I not been so lucky in the beginning), this was what rendered all newer safety sensors completely incompatible.

The reason? As explained nicely in this video, all newer models are designed in such a way that unless they get a "safe" signal from working sensors, the door won't close; just so that those pesky users won't bypass their faulty (or out-of-alignment or not-having-survived-mediocre-parking) sensors with a piece of jumper wire, the "safe" signal is a pulse train (with about 150 Hz frequency and some 10% duty cycle), which the receiver generates in presence of the sensor signal.

The manner my opener functioned was quite the opposite: it would work in all cases except when the sensor terminals are shorted (presumably by the sensors detecting the beam interruption), in which case the closing would not commence or, if already closing, would reverse.

Finally, I just decided to blindly try my luck with this set hoping I might still get the old kind, but alas, sadly but unsurprisingly, they were of the "pulse train" kind, nicely confirmed with an oscilloscope

Part 2. Simulation

So my job was to somehow translate between the two protocols. Some device (labeled "???" in the diagrams below) had to listen to the sensor output, decide if there was a "safe" pulse train, and then short the opener's sensor terminals if there were no pulses.

  

At this point, many would scream Arduino or even Raspberry Pi ("and your door will be able to post its status on Facebook"), but I quickly decided this would be too difficult, and a massive overkill to boot. I didn't want my door to post on Facebook. I wanted it to stop closing when the beam was crossed. Even this sequel video using an integrated-circuit (555 timer) square wave generator as a "pretty damn complicated piece of jumper wire to bypass the sensor" seemed a bit of an overkill to me (and as it were, resulted in at least one real kill...)

On the face of it, I thought a simple transistor switch might do the trick - if I could have the transistor normally open via some pull-down resistors, and rectify the pulse train in such a way that the signal would offset the opening potential and close the transistor (opening the switch) if pulses are present, at the same time retaining some power on the sensors so that they would resume normal operation if the beam was restored... not obvious. After a few evenings spent in a circuit simulator I found that a single transistor was a bit too unreliable; however a MOSFET or a Darlington transistor pair might do the trick, like so:

 

The one on the right is what I chose to implement as a final circuit. The two terminals on the right connect to the opener in parallel to the sensors: the upper right (positive) to the "white" screw and the white wire from the sensors; the lower right (ground) to the "black" screw and the striped wire from the sensors.

Part 3. Implementation

With a few of Amazon "shipped from China / Hong Kong and arriving several months later" orders, I finally assembled the circuit on a breadboard...


I scrapped the current limiting resistor in the output circuit because the short-circuit current was measured to be about 40 mA anyways, and put two 1M resistors in parallel to achieve 500k. The rest is as shown.
The real wonder? It worked out of the box.
And works still.

Epilogue

The opener is still in operation, sensors included, with the original breadboard circuit loosely hanging off the drywall. A few times it would refuse to close because there was something left in the doorway (I guess that may have saved those items from being crushed).
It isn't seamless, however: after I insulated the door with some styrofoam (making it quite a bit heavier), the gears in the opener did kick the bucket. However I was able to replace them relatively cheaply and quickly.

I still have plans to solder the circuit into something more permanent (and even bought all the necessary supplies for this), but these remain low priority (ain't broken? won't fix it, at least for now).
I still wonder whether I can somehow reverse engineer the operation of "Lock" and "Light" features from the wall control unit. If you happen to have this one lying around, please drop me a line!

   

Tuesday, June 27, 2017

Parameter sweep in any Excel calculation

For the past few years I have dabbled a lot (and I mean a lot) in complex Excel-based quantitative finance. As a result, I would like to share a neat little trick that can be easily applied to any Excel spreadsheet.

Suppose you have a spreadsheet that does some sophisticated calculation for you (say, pricing an autocallable basket option using a Monte-Carlo simulation of a Black-Scholes model). And yes, it works; you change the input parameters and you observe the corresponding change in the output.

Now what you need is the parameter sweep analysis, i.e. you need to determine how your output changes as you vary one or more input parameters - for example, you may want to run a convergence test to see how your resulting price changes as you vary the number of Monte-Carlo simulation paths,

Of course you can do this manually, doing the simple routine "change input, run simulation, copy-paste results, repeat". But for anything more than 4-5 simulation runs his would be painfully slow and dangerously prone to error.

What's worse, if a single simulation run takes around several minutes, you'll be stuck between the need to just stare at your computer doing nothing else (and letting your productivity go to waste) or the temptation to attempt something different in the meantime (and inviting all sorts of multitasking-related errors like "aw, snap, looks like I've run this one twice, and heck, have I pasted this number to the right place?")


This is where a simple VBA macro comes to help. As simple as this:
Sub analysis1()

 Dim in1, out1, out2, out3, here As Object
 Set in1 = Worksheets("Calc").Range("Input1")
 Set out1 = Worksheets("Calc").Range("Output1")
 Set out2 = Worksheets("Calc").Range("Output2")
 Set out3 = Worksheets("Calc").Range("Output3")

 Set here = Worksheets("Report").Range("Out1D")

 While here <> Empty
  in1.Value = here.Value

  Worksheets("Calc").Calculate  ' (or do whatever you need to do to run simulation -- e.g. call some other macro)
  here.Offset(0, 1).Value = out1.Value
  here.Offset(0, 2).Value = out2.Value
  here.Offset(0, 3).Value = out3.Value
  Set here = here.Offset(1, 0)
 Wend
End Sub

Here we will need to define several named ranges: Input1 for the parameter that we will vary, Output1..3 for the calculation results we are interested in, and Out1D where the macro will look for values for input, and where, next to each input value, the result values will be output.

Why named ranges if we can specify cell addresses like.Range("F23") directly? Because it's much more robust coding style. If you decide to change the layout of your spreadsheet later, the named ranges are quite likely to point to correct locations, whereas your addresses will likely change (and your code may wreck some serious havoc on your spreadsheet). At worst (or if you decide to change parameters to vary/look at), all you will need is to re-point the named ranges to new locations rather than go through your code and edit addresses each time.

The added elegance of this example is that you don't need to specify how your sweeping parameter will vary anywhere in the code, nor do you need to count the number of runs. The program will do all of this for you, automatically running for all values you specify below the Out1D range until it hits an empty cell.
Another example will let you handle the dependence on two parameters:
Sub analysis2()

 Dim in1, in2, out1, here As Object
 Set in1 = Worksheets("Calc").Range("Input1")
 Set in2 = Worksheets("Calc").Range("Input2")
 Set out1 = Worksheets("Calc").Range("Output2D")
 Set here = Worksheets("Analysis").Range("Out2D")
 
 For i = 0 To 10
   here.Offset(-1, i + 1).Value = i * 0.01
 Next i

 While here <> Empty
  in1.Value = here.Value
  For i = 0 To 10
   in2.Value = i * 0.01
   Worksheets("Calc").Calculate '(or do whatever you need ...)
   here.Offset(0, i + 1).Value = out1.Value
  Next i
  Set here = here.Offset(1, 0)
 Wend
End Sub

As you can easily see, now we only have one output parameter (Output2D) and two input parameters (Input1, Input2). Similar to before, we look for values for parameter 1 and a place for output under the range Out2D. For the second parameter we have resorted to a simple for-loop (choosing 0, 0.1, 0.2 ... 1) but this is for simplicity and demonstration; arbitrary values for parameter 2 are possible with very little extension that I am leaving as an exercise.

A few words of warning:
  • Macros are ignorant of your spreadsheet editions! If you hardcode an address, or otherwise rely on a specific layout of your workbook, you will need to re-exaamine your code every time you edit the layout. A partial workaround is using named ranges (at least you can freely move them around).
  • Changes made by macros can't be undone! Before proceeding, make sure none of your important data gets overwritten, and do make a back-up copy before you test and debug your work. Luckily Excel will warn you that a file with macros cannot be saved as an .xlsx, prompting you to use .xlsm or .xlsb (or at least the old .xls) instead.
  • Macros won't always run! Make sure you enable them in your Excel security settings, and make sure some pesky anti-virus program won't blindly remove them from your sheets (assuming you weren't actually up to writing malware).


Bonus: There is a very quick VBA way to save an accountant's butt.

Consider this delicate scenario. You are working on a complex spreadsheet prepared a while ago by someone else, and the spreadsheet just won't tie. Soon enough the reason is clear: the person who last worked on the sheet blindly pasted some numbers over formulas to to sweep some of their mistakes under the rug. So you are left with finding all these "cover-ups"... and needless to say, that person left the company on less-than-amicable terms, so there's no one to ask about the spreadsheet structure.


How to flag "all cells where values were pasted over formulas" in a huge spreadsheet?

Consider something like this:

' The conditions may vary. We are interested in finding "all cells that have a number and are not formulas";
' the two most obvious checks would be
' (i) Left(cell.Formula,1) = "=" (i.e. cell's formula begins with "=")
' (ii) cell.Formula <> cell.Value (i.e. its formula is different from its value)
' Neither is totally fool-proof but both work fine.

For Each cell in ActiveSheet.UsedRange
 'some more code may be needed to safely work around cells that contain errors.
 If IsNumeric(cell.Value) And Left(cell.Formula,1) = "=" Then

  ' if it is a formula, everything is OK, dim it
  cell.Font.Color = RGB(128,128,128) 
 Else
  ' if it is a number, make it stand out
  cell.Interior.Color = RGB(255,255,0) 
  cell.Font.Color = RGB(255,0,0) 
 End If
Next cell

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.