Saturday, March 26, 2016

Lloyd's Weekend STEAM Project Using LiveCode

It was Friday around 5 pm after a long day of meetings and I again had the urge to do something creative as I ended the week. I had seen a compelling video on the Physics & Astrophysics facebook page a few days earlier that captured my attention and the thought occurred to me that I could recreate it with LiveCode. That became my weekend project. I think this is a nice example of a small STEAM (science, technology, engineering, arts, and mathematics) project. I'm a big advocate of adding the arts to STEM initiatives because, as humans, we yearn for beauty and there is much beauty in mathematics and science.

Here's an animation of my project:

I titled the project "circle within circle" because that's exactly what it looks like. However, amazingly, the red marbles are moving in straight lines, not curves. Here is a second animation to prove it:


I See the "A," But Where is the Rest of "STEM"?


The challenge to creating this project - and what was most fun - was figuring out where each marble should start and end. The circle has a radius of 200 pixels with its center located at the screen location of 400,300. (It's important to remember that the 0,0 origin of the computer screen is not the center of the screen, but the top left corner. And, unlike the traditional cartesian coordinate system, the vertical y-axis is positive in the downward direction.) It's easy enough to figure out where the marbles should start and end on the vertical and horizontal lines, but what about those at the various angles, such as 45 degrees? Ah, this calls for a quick refresher of sine and cosine. If just the mention of these two math concepts brings back horror-filled days from high school geometry, then it is high time you revisit them and see them instead as things of beauty. Here's a nice illustration of what they are all about:



As you can see, the cosine is simply the horizontal distance from the center of the circle to the point on the circle matching a given angle. The sine is just the vertical distance. Notice the right triangle that gets formed with the hypotenuse shown in red depicting the angle. We can use the good 'ol Pythagorean Theorem - a2 + b2 = cto figure out the lengths of the horizontal and vertical lines. And voilĂ , we have the x and y coordinates.  Most people are a little surprised that a 45 degree angle does not produce x and y coordinators of half the distance of the radius, but instead the distance is .707 of the radius.

Here's an illustration of my LiveCode screen that shows how to figure the starting point for the marble at the 45 degree mark:



Using a radius of 200, the length of the x and y (horizontal and vertical) is 141.4 (i.e. 200 X .707). Figuring the x coordinate is straightforward: 400 + 141.4, or 541 rounded to the whole number. Calculating the y coordinate is a little trickier because of that pesky detail of the 0,0 origin being in the top left corner of the screen. We actually need to figure out how to much to add to the top boundary of the circle, which is 100. So, we have to subtract 141.4 from 200 to get 58.6. We add 58.6 to 100 to get a y coordinate of 159 (again rounded to the nearest whole number).

I then had to use the same logic to figure out all of the other locations for the remaining marbles given their respective angles. Yes, lots of great math going on here! 

My use of a computer programming language (i.e. LiveCode) is the technology part of my STEAM project. And, I'll admit I don't have any engineering or science components yet, but I'm sure you could come up with a few ideas. What comes to my mind is how an internal combustion engine works - the pistons are going in straight lines, which then turn the crankshaft:


But It Doesn't Look Quite Right


Now, you might say: "Lloyd, those marbles aren't exactly creating a circular motion. There is a bit of a hump. And I took a look at the video link you provided above - great background music, by the way - and its animation looked like a perfect circle." Alas, you are correct, and I'm a little befuddled as to how to smooth out the hump. I thought it might have something to do with the timings, but I don't think so. Of course, you might see immediately what needs to be done to make the marbles seemingly move in a perfect circle, so do remember to email me after you figure it out. (Below is my code to help you.)

But hey, this was only a weekend project and I think there is just as much beauty in my imperfect version. 


   put 2 into varTotalSeconds  
   put .25 into varHoldSeconds  
   repeat until the mouseclick  
    //motion 1  
    move graphic "ball1" to 400,500 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball2" to 324,486 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball3" to 258.6,441 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball4" to 216,376 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball5" to 200,300 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball6" to 216,224 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball7" to 258.6,158.6 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball8" to 324,116 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    //motion 2        
    move graphic "ball1" to 400,100 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball2" to 476,116 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball3" to 541,158.6 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball4" to 584,224 in varTotalSeconds seconds without waiting     
    wait varHoldSeconds second  
    move graphic "ball5" to 600,300 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball6" to 584,376 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball7" to 541,441 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
    move graphic "ball8" to 476,484 in varTotalSeconds seconds without waiting  
    wait varHoldSeconds second  
   end repeat  

Thursday, March 17, 2016

The Best and Worst of LiveCode Troubleshooting

I am very behind in writing about my various LiveCode projects, particularly my Q Sort project. So I'll use this post to write about a few experiences I have had lately. Yes, this blog post is all about the art and science of troubleshooting. I'll start with a shining example, and then close with a problem that I remain unable to resolve. However, this problem has nudged me into a new direction for my Q Sort tool that really was inevitable, namely moving from an FTP approach to using text files to store data to the use of a mySQL database.

Troubleshooting Example 1: The Case of the Missing Data


In almost all of my field tests using my Q Sort tool, I had been experiencing an unusual problem of missing data. The problem was very spotty. The data from most responses were fine, but there would always be a handful (i.e. 2 or 3 out of 20 people) with missing data. Most of the time, only one data point would be missing, but it was possible that 2, 3, or even 4 data points could be missing. At first, I thought it might be some artifact of the FTP process, such as some data getting "lost in transmission," but that seemed like a weak and unlikely theory.

Also, another person who completed a Q sort - a doctoral student in my department - was also adamant that a statement she rated one way was subsequently rated a different way when she reviewed her data. No one else had ever noted this before, so I knew it was possible she was just mistaken or confused. However, she sounded very sure of herself, so I was careful to note her observation because if she was right, then the accuracy of all the Q sort data would be in question. (I now think she was right. The fix I explain here solves that problem too.)

It wasn't clear to me how to go about troubleshooting this problem, given the rareness of its occurrence. I had never experienced the problem myself in all of my testing. My first idea was to set aside an hour in my design studio course at the University of Georgia and have the students do several Q sorts in a row with the hope that the problem would appear in someone's data. Then, I could compare the data uploaded to the web server with the data saved to their local computer. However, I solved the problem on my own in a relatively short amount of time.

My Solution: Make All Data Visible


To help figure this out, I made a copy of the Q Sort tool just for troubleshooting. I turned off all of the FTP and other data storing commands and made visible the key hidden fields that collected and managed the data while the Q sort was in progress. As I played with doing a bunch of Q sorts, I discovered the problem.

Basically, here is what was happening. Two statements in adjacent slots would be appropriately "locked" into their slots. Then, the user would move one of these statements into the other slot. The just emptied slot would correctly then register "empty." But, if I then moved the other "locked in" statement into the now vacated slot, both slots would register that statement. The other consequence to this would be that if all other slots were filled, the "Submit" button would be activated, thus allowing the user to then quit and upload their data. This problem wouldn't occur, or would resolve itself if it had, if the person moved one of these statements back to the main area of the screen. So, the problem only occurred under these fairly rare situations.

To fix this, I added some code to the "on mouseUp" script in the button "statement template" to check for duplicate statement numbers in the field "slot contents." Even this was a little tricky as I had to make sure that the correct slot showed where the statement now resided. To do this, I first added this code to look for EVERY occurrence of the button number and replaced it with "empty":

    //First check for duplicate statement numbers in field "slot contents" (the cause of the missing data problem) - February 21, 2016  
    //I adapted this code from the button "Check for Duplicates" from the Q sort analysis tool  
    put the number of lines in field "slot contents" into L  
    repeat with i = 1 to L  
      put item 1 of line i of field "slot contents" into varName  
      if SN = varName then  
       put "empty" into line i of field "slot contents"  
      end if  
    end repeat  
   
The key was to put this code BEFORE this existing line of code (near at the end of the mouseUp script) because this line then puts the button's location into the correct slot:

put SN into item 1 of line varSlotNumber of field "slot contents"

In summary, if a duplicate was found the appropriate one would remain but the other would be replaced with "empty."

Improving the Usability


But, I wasn't finished. As I played with the program I could sense a usability problem. As I moved statements previously locked into a slot to an adjacent slot, it was often not obvious which statement was no longer "locked" into a slot. I decided the best thing to do would be to "snap" the unrated statement out of the "slot zone" to the original starting x screen coordinate, but leaving it at the current y screen coordinate. To do this, I adapted the script that already existed in the "To far left" button and added it to the custom function "checkForEmptySlots" on the card "sort":

    //First check for duplicate statement numbers in field "slot contents" (the cause of the missing data problem) - February 21, 2016  
    //I adapted this code from the button "Check for Duplicates" from the Q sort analysis tool  
    put the number of lines in field "slot contents" into L  
    repeat with i = 1 to L  
      put item 1 of line i of field "slot contents" into varName  
      if SN = varName then  
       put "empty" into line i of field "slot contents"  
      end if  
    end repeat  


A key idea was to put this script near the end of the function and right before other script that checked to see if all statements were accounted for. If all statements were accounted for, only then would the "Submit" button be activated.

Note this important line (line 7):

 if not intersect (graphic "statement background", button locStatementName) then next repeat  

This line is important because I only wanted to "snap" out of the way those statements that were in the "slot zone."

The result is now a very clear user interface that shows which statements are "locked" into place and which are not.

Troubleshooting Example 2: The Case of the Deleted Data


OK, great. I solved a thorny problem all on my own. Just as I was finishing my victory lap, complete with champagne, another recurring problem surfaced. This problem remains unresolved and really has me stumped: Data that I know were correctly FTPed to my web server have subsequently vanished. I really have no explanation for this. It has happened occasionally and rarely. The first few times it happened, I blamed myself for just not remembering deleting the data. This was a reasonable explanation because in my early field trials, I would occasionally wipe the text file clean on my server. However, the last time it happened, I know I did no such thing.

To be honest, I know I haven't been using the preferred LiveCode libURL commands, such as libURLftpUploadFile, for the data transfer (here is a link to a good tutorial). Instead, I've simply been using the simple "put" command in tandem with the URL function:

 put field "ftp scores"&return after URL varUploadSite  

However, I don't see how this is at all related to the problem given that I know the data were in fact saved to the web server. So, I remain stymied. Perhaps someone reading this blog post with more experience than I knows what to look for. If so, I hope they will contact me.

But, as I mentioned at the start of this blog post, my inability to solve this problem has persuaded me to abandon the FTP method altogether and instead use a mySQL database for all data storage. I knew I would eventually make the jump to using a real database, but there was a certain charm and elegance to just using simple text files for all data storage. Interestingly, I reread one of my own blog posts from last year to relearn how to do this titled "Desperately Seeking Middleware." I hope to finish this transition to mySQL in the next week or so.

Final Thoughts


These two examples are good reminders of how coding can bring the "thrill of victory" and the "agony of defeat." Just when I thought I was rather clever in figuring out one problem, another jumps up and bites me. No worries, though, as this is all part of the learning experience. One day, I know I'll figure out that deleted data problem, or bump into someone who can help me.