Friday, May 3, 2024

Lloyd's Name Identifier Helper App for the EQ Web Sort Tool

Although the name of this blog is Learning LiveCode, this project has all been about relearning LiveCode. I didn't learn anything new with LiveCode in this project, but I found myself having to remember or relearn things I had previously learned.

The main programming task here is very straightforward... look for a string of text, then search for another string that relates to the first and display the results. LiveCode easily handles this type of list processing. Once that was accomplished, I added a few other functions that also needed some relearning.

Why the Need for a Helper App?

Ok, so what was this project all about? I have been studying, teaching, and using a research approach called Q methodology for about 13 years. I've written in previous posts about an app I built called Lloyd's Q Sort Tool (click here for a list of project posts). My app works great, but it must be downloaded and installed on a Macintosh or Windows computer. This is no problem when I use Q in my teaching because I ask students to download and install the app at the beginning of the term so we can use it throughout the course. However, for a typical Q research project each participant does one and only one Q sort. They don't want to download and install anything. Instead, they just want a Web link where they can go and complete the Q sort online. As it turns out, I've been doing more and more of these "one and done" Q projects lately. As a result, I've begun using a wonderful web-based Q sort tool created by Shawn Banasick called EQ Web Sort Tool. It's a marvelous tool with an outstanding design and I've been encouraging my students to check it out. The data are stored in a JSON file on a Google Firebase database. I then import the JSON file in a specialized statistical software package for analyzing the Q sort data that Shawn also built called Ken-Q (the version I use is the desktop edition called KADE). The data are factor analyzed to produce clusters of Q sorts that are related (correlated) in some way. 

Here is a part of a JSON file containing the data for a single person. I've highlighted two text strings in green. The second is obviously my name. The first is just part of a random code assigned to my data. (I've also highlighted a string of numbers in yellow for reasons I will explain below.) The task I need LiveCode task to do is find the random code and then return the real name associated with it:

"-NvrE75qmAgUnhCDock-": {
    "column4_1": "no response",
    "column4_2": "no response",
    "columnN4_1": "no response",
    "columnN4_2": "no response",
    "dateTime": "19/4/2024 @ 12:14:31",
    "nneg": 9,
    "nneu": 9,
    "npos": 15,
    "partId": "Lloyd Rieber",
    "projectName": "Faculty Viewpoints of WEIT Strategic Planning",
    "randomId": "faadcd78-29e",
    "sort": "-1|1|-4|-1|2|2|1|0|0|4|-4|-3|-2|3|-2|0|2|0|0|-1|-1|-1|1|-3|-2|1|4|2|3|-2|1|3|-3",
    "timeLanding": "00:00:07",
    "timePostsort": "00:00:24",
    "timePresort": "00:04:45",
    "timeSort": "00:22:18",
    "urlUsercode": "not_set"
  },

Ken-Q produces an Excel file that includes a factor loadings chart that shows the random code, but not the individual's name. However, when I'm analyzing the data I need to know the name of the person associated with the Q sort. Ok, no big deal, all I need to do is manually scan the JSON file to find the name of the person that matches the random code. I can then add a column to include the person's name, as shown here:


(And yes, I'm often mistaken for a cartoon character.)

This plan works fine when I'm back in my office after collecting the data and I have plenty of time to do this work. However, I've been using Q sorts in faculty workshops. This means I'm analyzing the data while the faculty are on a short break. After the break, I use the results to design a faculty discussion based on the results. So, I need to associate the name with the code very quickly with 100% accuracy. I don't know about you, but I don't always work well under pressure. 

So, how does my app help? I paste the JSON file into one field and the list of identifying codes from Ken-Q in another field. I click a button and the app finds the corresponding name. Total time, including the time it takes to copy and paste, is about 60 seconds with accuracy guaranteed. Here's a screenshot:



A Couple of More Features

After I conduct the workshop, I sometimes find that a person wants to have a copy of their responses to the Q sort. Although that data is included in the JSON file, it is not in a form that is easily interpreted. That's the string of numbers I've highlighted in yellow above. Each number in that string corresponds to the rating the person gave each statement in order of the statement. So, I added the option to show each statement and the rating each participant gave to it. 

To do this, I must first copy and paste the Q sort statements in the window on the right in the screen shot above, then click on the button "Display Individual Q Sorts." This brings up a second page with the names of the participants copied from the first page (shown below). All I need to do is click on any of the participant's names on the left and their Q sort ratings are shown:


To do this, I had to remember how to create a clickable list. Luckily, it's a short lesson:
  • Add a text field (scrollable or not)
  • Enable "List behavior" and "Multiline hilites"
Then, add the code for what should happen when a line is clicked. Here is the key bit of script that uses the property "hilitedLine:

put the hilitedLine of me into varLine

This puts the number of the line clicked into the variable of your choice (varLine, in my case). Then, I can easily put whatever is in that line into another object, such as a variable or field:

put line varLine of me into [ some variable or field name ]

I also added the option to sort the Q sort list in order of the rating or the statement number. I had to relearn how to do this with radio buttons. In short, you add two radio buttons and name them. Then, you group them together and make sure the option "Hilite one radio button at a time" is enabled. Then, you add script to each radio button using the property "hilite" to determine if the radio button is selected: 

if the hilite of me is true then [ do something ]

I also added the option to increase or decrease the text size. I had to look up the correct property to use: textsize

1:  on mouseup  
2:    put textsize of field "q sort" into varTSZ  
3:    add 1 to varTSZ  
4:    set the textsize of field "q sort" to varTSZ  
5:  end mouseup  

This script notes the current size of the text, then adds 1 to it. It's sister radio button subtracts one from it. (The default text size was 12.)

Finally, I added a text field to the far right that displays the grid statement pattern. The version of KADE I've been using for a few years auto-detects the grid pattern, but Shawn has updated KADE and the newest version no longer auto-detects the pattern. Interestingly, since I created the Q sort I should know the grid pattern easily, but one's memory is short and the time it takes to look it up is longer than you would think. The script looks at the Q sort results (for Popeye, in this case, but it could be anybody) and simply counts the number of ratings, such as two 4s, three 3s, etc. If I switch to Shawn's new version of KADE, this will save me many minutes.

Final Thought

The last thing I'll say is that I was working on the Q sort the night before the faculty workshop and I started making this app around 10 pm. I realized at that moment how little time I was going to have during the break the next day to connect the random code to each person's name, so I launched LiveCode to build the basics of this app. It only took about 20 minutes despite my sleepy state. 

Sunday, December 31, 2023

Race Car Math Estimation Game: Part 2

As I was playing with the initial prototype I had a flashback to circa 1983, appropriate given the 1980s inspiration for this game, namely Estimation Baseball, which I created at about the same time as I explained in my previous post. Back then, one of the major publishers of educational software was Plato. Jerry Morris, the principal of the school where I was teaching (Bluewater Elementary School in Bluewater, New Mexico) had purchased a variety of educational software titles for our school's five Apple II+ computers, including several titles published by Plato. During my flashback I recalled playing a clever math race car game for a single player. The game began with having the player take a "trial lap." I don't recall all the details, but basically the computer kept track of how long it took you to complete the lap while answering math problems correctly. Then, the race began with your race car pitted against a race car "driven" by the computer. The clever part is that the computer's car used the player's time during the trial lap. So, you were already being pushed to improve on your own ability. If you won the race, that new time was then used by the computer's race car in the next race.

Design Changes

This flashback motivated me to make two major changes to the design of my race car game. 

First, instead of this being a game for two players, it is now a single player game. I've incorporated the idea of the computer's race car being driven by the player's best time.

The second major design change is to move away from the three estimation thresholds that were inspired by the estimation baseball game to indicate an estimation that was close enough for a single, double, triple, or home run. Instead, I just use the accuracy of the estimation, again measured as a percent of the correct answer, to move the car. So, even if the estimation was way off, say only 1% of the correct answer (e.g., an estimate of 10 for the correct answer of 1000), your race car would move just a tiny bit. The farthest the car will go if answered perfectly is half as long as the race track. The race was won, of course, when one of the race cars crossed the finish line, but a variable also kept track of the number of laps (i.e., tries) it took to get there. The computer's race car would move according to the lowest (best) number of tries taken so far in the same increments.

Video Demonstration of This Second Prototype

Here is a short video demonstration of the current prototype of the game:


In this video, the player completes a total of four races. The first race is the trial lap, so the computer's race car does not move. Notice that it took the player 5 laps (aka tries) to complete the race. The data from the trial lap is then transferred into the field "best try" (labeled as "best so far" on the screen). (Programming note: These fields, obviously, will not be displayed when the game is finished. They are shown to help me program the game. At some point, I'll probably use variables instead of fields to keep track of this data.) 

On the second race, the player took 4 laps to complete the race. The computer's race car mimics the number of laps and estimations of the player's trial lap, which was 5 laps. The player wins the race.

On the third race, the player didn't do so well. After four laps, the player had not yet reached the finish line. But, the computer's race car finished the race in four laps - again mimicking the performance of the player during the second race.

For the fourth race, notice that the data in the field "best try," doesn't change. That's because the player's best try so far was in race two. For this fourth race, the player's performance improved and only needed three laps to finish the race. The computer's race car - again mimicking the performance of the player during the third race - would have needed another lap. So, the player won the fourth race.

Analyzing the Code

Just like in version 1.0 of the game, the most important code is in the button "Next Lap:"

1:  global varCar1x, varCar2x, varNum1range, varNum2range, varNum1, varNum2, varLap1, varLap2, varLap3  
2:  global varDistance1, varCar2Lap  
3:    
4:  on mouseup  
5:      
6:    //reset fields  
7:    put empty into field "num1 field"  
8:    put empty into field "num2 field"  
9:    put empty into field "answer field"  
10:    put empty into field "percent field"  
11:    put empty into field "display answer"  
12:      
13:    //This will determine how fast car2 will get to the finish line  
14:    put the number of lines in field "best try" into varCar2Tries  
15:      
16:    //put random(varNum1range) into varNum1 - I'll activate this after all testing is done  
17:    //put random(varNum2range) into varNum2 - I'll activate this after all testing is done  
18:    put 10 into varNum1 //I'll delete this and next line after all testing is done  
19:    put 10 into varNum2  
20:      
21:    put varNum1 into field "num1 field"  
22:    put varNum2 into field "num2 field"  
23:      
24:    put varNum1 * varNum2 into varAnswer  
25:    Ask "what is the answer?"  
26:    put it into varResponse  
27:    put varAnswer into field "display answer"  
28:    put varResponse into field "answer field"  
29:      
30:    //compute absolute percentage of correct answer  
31:    put abs(varAnswer-varResponse) into varDistance  
32:    put varAnswer-varDistance into varDistance2  
33:    put round((varDistance2/varAnswer),2) into varPercent  
34:    put varPercent into field "percent field"  
35:      
36:    put 50 into varDistance1 //Note: This variable is actually initialized in the card script; I put it here temporarily during testing.  
37:      
38:    //Move the player's race car a distance that depends on the accuracy of the player's guess   
39:    put (varDistance1*varPercent)+varCar1x into varCar1x  
40:    if varCar1x >= 100 then put 101 into varCar1x //This keeps the car from going far to the right after crossing the finish line.  
41:    //From version 0.1 - I may revert back these, so I'm keeping them for now  
42:    //if varPercent>=.95 then put varLap1+varCar1x into varCar1x  
43:    //if varPercent>=.80 and varPercent<.95 then put varLap2+varCar1x into varCar1x  
44:    //if varPercent>=.75 and varPercent<.80 then put varLap3+varCar1x into varCar1x  
45:      
46:    //move the race car  
47:    set the moveSpeed to 200  
48:    move button "car1" to ((varCar1x*8)+100),70 // in 2 seconds without waiting  
49:      
50:    //move the second race car  
51:    if field "best try" is not empty then  
52:     add 1 to varCar2Lap //Note: This is initialized to 1 in the card script  
53:     put item 1 of line varCar2Lap of field "best try" into varPercent2  
54:     put (varPercent2)+varCar2x into varCar2x  
55:     move button "car2" to ((varCar2x*8)+100),160// in 2 seconds  
56:    end if  
57:      
58:    //show the key data to help with troubleshooting (to be deleted later)  
59:    put (varDistance1*varPercent)&comma&varCar1x&comma&location of button "car1"&return after field "data"  
60:      
61:    If varCar1x>=100 then  
62:     if field "best try" is empty then wait 2 seconds  
63:     answer "You win!"  
64:     move button "car1" to 1110,70 in 1 second  
65:     if field "best try" is empty then  
66:       put field "data" into field "best try"  
67:     end if  
68:     if the number of lines in field "data" < the number of lines in field "best try" then  
69:       put field "data" into field "best try"  
70:     end if  
71:     opencard  
72:    end if  
73:      
74:    If varCar2x>=100 then  
75:     answer "Sorry, you lost this race! Try again."  
76:     opencard  
77:    end if  
78:      
79:  end mouseup  

The code is clearly just an updated version of the code from version 1.0, so I'll only go over some of the key changes.

First, lines 30-34 take the player's answer to the math question and determines the accuracy of the answer as a percent. 

In line 36 the variable varDistance1 is used to set the maximum distance the race will travel. That is, if the answer is exactly correct, the car will only go half (i.e., 50) the length of the race car track. I can easily change this as needed after getting feedback from the game play.

Line 39 computes the distance the player's race car will go. 

Lines 46-48 move the player's race car. You'll notice that I've commented out the modifier "in 2 seconds without waiting" in line 48. This was how I moved the race car in version 1. Instead, I now move the race car according to the "speed limit" denoted in line 47.

Lines 50-56 move the computer's racing car using either the player's time for the trial lap or the player's best time. Line 51 is used to determine if the player just completed the trial lap. If it's empty, then they have not completed the trial lap yet. If it is not empty, they have. So, if field "best try" is empty, then the computer's race car does not move. Recall from version 1 that I had been keeping track of the player's race results in the field "data" (labeled as "testing data" on the screen). When the player completes the trial lap, the data from the trial lap is automatically transferred into the field "best try" because, obviously, the player's only lap is their best time .... so far. This all happens in lines 61-72.

Line 61 checks to see if the player has crossed the finish line yet: Is the position of car1 (defined as the car's "nose") equal or greater than 100? If not, then the rest of the code is skipped and the player has to press the "Next Lap" button for the next lap. If the player has crossed the finish line, then line 65 checks to see if the field "best try" is empty. If it is, then the player just completed the trial and the race data is transferred from the field "data" to the field "best try." If this is not the trial lap, line 68 checks to see if the number of lines in field "data" is less than the number of lines in field "best try." If it is, then the player outscored their best time and their new data is now the "best time" data and it is transferred to field "best try," replacing the data that was there previously.

Notice that the opencard command is triggered in line 71 if the race is over. This resets both race cars back to their starting positions and re-initializes a bunch of variables.

Lines 74-77 are only triggered if the computer's race car crosses the finish line before the player's race car. In this case, the player loses that race and again the opencard command is triggered, this time with line 76. The player is now challenged to try a little harder in the next race.

Questions about the Game Play

As this point, I'm uncertain if the game play will be fun using this strategy of number of laps needing to win any single race. Currently, I have it set so that the player wins if there is a tie. That is, if both the player and the computer takes the same number of tries (i.e., laps) to cross the finish line, then the player wins. If the math problems are difficult, it will be virtually impossible to obtain exact answers. So, depending on the difficulty of the math problems, I really don't know how many laps it will take to win a race. Only feedback from game play will determine this.

Some ideas I have now include automatically increasing or decreasing the challenge of the game as the game play warrants. For example, if the player set a personal best score by accident, such as by coincidentally getting the exact or almost the the exact answer several times in a row, they may never be able to repeat this performance and beat this time. I could add an algorithm that checks to see how many times the player has either won or lost in a row, then adjust the game difficulty after a certain number of wins or losses has been encountered.

I'm also not sure if the animation is appropriate for interesting game play. As you saw in the video demonstration, first the player's race car moves, then the computer's race car moves. I had played around with both cars moving simultaneously, but this sometime led the computer's race car finishing before the player's race car with the same number of laps. Although the animation felt right, I would have to change the design of the game away from using the total number of laps to determine the winner of a race. But, that might be what I need to so. So, I may revert back to this strategy at some point.

I'm also not sure of the right language to use in the game. So far, I've been using the word "lap" to mean try. I don't know much about racing, but each try doesn't seem like a lap to me. I also referred to the first race as the trial lap, so that's an obvious contradiction. I need to work on this.

Next Steps

Not surprisingly, there are an assortment of design changes that I need to do. Here are a few of the most pressing.

The Need for a Timer

Something that is obviously missing from the game is a timer for each math question. Recall that the educational value of this game is to learn how to make good, quick estimations. If the player has unlimited time for each math problem, then of course they can just work out the correct answer. Instead, the player needs to be pushed to come up with an estimation as quickly as they can. In the original game of Estimation Baseball, I think I gave the player about 9-12 seconds to enter an answer, with 3-4 seconds for each "strike." The amount of time given to the player is an important design decision. It can also be something that varies throughout the game. More or less time could be given depending on how well the player is doing.

Review the Future of the "Next Lap" Button

I need to consider if I should continue to let the player decide when the next race will start, or make it automatic, but with the option to pause the race. I see value in both approaches.

Improve the Graphic Design and Add Sound Effects

Right now, it's obvious to me which race car I drive and which race car the computer drives, but I doubt it would be so obvious to anyone else playing the game. I need to design the game to not only make this clear, but also to add some excitement by having the player choose and name their race car. 

If you watched the video above, you probably were expecting lots of race car sound effects, such as the "vroom, vroom" of the race car engines being throttled. Sorry for your disappointment! But, I realize there are so many ways for appropriate sound effects to really enhance the game play. 

Review and Enhance the Game Play

As already mentioned above, I need to seriously consider how to optimize the game play of this game. I need to fully capitalize on the race car metaphor here. Beyond the sound effects already mentioned, I can envision a grandstand of spectators who cheer and applaud when a race is won, or who voice disappointment when a race is lost. These are tricky design decisions. I don't ever want to do anything other challenge and engage the player. A close race that ends in defeat can be a very motivating event to try to do better. But, constantly losing can be a recipe for a failed game. I remember well how the Plato software challenged me to keep playing - and to keep doing mathematics. Even though the Plato software was meant for elementary school students, I remember enjoying the challenge of the game even as an adult. That's something special that I hope to recreate in this game.






Sunday, December 17, 2023

Race Car Math Estimation Game: Part 1

I began a new project to create a mathematics game for elementary school students to develop estimation skills. The inspiration for this game came a few weeks ago as I was getting ready to teach a class in our doctoral design thinking course. I had dusted off an old Apple IIe computer that I had in my office to show to the class. I thought they might find it interesting to see what was the state-of-the-art technology around 1983 or so. 

I had two goals. The first was just to have the class get a good sense of the design of the machine's interface design (e.g., keyboard entry only and no internet) and the state of graphics and text. The second was to show the class some computer games I had designed back when I was an elementary school teacher to see if they agreed that the design of the games - however old - was still good. As I like to say, "good design, like good music, stands the test of time." Back in the early 1980s I taught myself how to program the Apple IIe using the BASIC programming language (which was built-in to the IIe). I had programmed a bunch of games and simulations to help my students learn and enjoy some vexxing ideas, such as fractions - the bane of all 4th or 5th graders. I showed the original version of Mineshaft, a game to learn fractions. Mineshaft remains probably the best educational game I've ever designed.

Another game I demonstrated was one I had forgotten about. It was called "Estimation Baseball." (I had mentioned this game in a post way back in December 2016 when I was taking inventory of my career's design work up to that time.) As the name suggests, the game used a baseball theme for two players. The computer would "pitch" a difficult math problem, such as 983 X 317, and the student had to about 10 seconds to enter their best guess as to the answer. The computer would beep every 3 or so seconds (akin to strikes). If no estimation was entered, the student "struck out." Estimations that were way off resulted in "ground outs" or "fly outs." However, the player could hit singles, doubles, triples, or home runs if their estimation was good - it all depended on how good the estimation was. I don't remember the cut-offs for the estimations, but it was something like 95% accuracy for a home run, 85% for a triple, etc. I remember the students really enjoying the game. Students had no time to to work out the exact answer, so a math problem like 983 X 317 needed to be quickly transformed into something like 1000 X 300, or 300,000, which is about 96% accurate resulting in a home run. I also built different "leagues" into the game for different skill levels, from "little league" to "hall of fame." Estimation games, like this, get away from the stress and drudgery of finding the one and only one correct answer and instead have students really explore and discover mathematics in their own way,

Initial Design of the Race Car Estimation Game

I continue to this day to think that estimation remains a very important math skill. (How quickly can you come up with 20ish% tip on that restaurant bill?) I decided to build a new version of Estimation Baseball using LiveCode. But, instead of baseball I thought I'd use a race car theme for the game. (I've learned that there are a lot of people who really don't care for baseball and don't know the rules.) My idea is two players compete by moving their race cars forward by estimating math problems. The better the estimate, the further the race car will go.

It's been awhile since I last blogged about the development of a LiveCode project over time and I thought this would be a good candidate. So far, I've spent only about two hours on the project. Here's a screen snapshot of the first rough prototype:



The field "data" on the left (labeled as "Testing Data" on the screen) is temporary - it provides me with quick feedback on whether the underlying variables are being computed correctly. The first number in each row shows the horizontal position of the car on the race track, which is numbered from 0-100. The next two numbers show the x,y position of the car on the screen.

The button "Opencard" does just that - it opens the card, which restarts the game from the very beginning.

I actually made a lot of progress in a very short time. So far, only one of the race cars moves, but it does so based on the accuracy of the estimation to a math problem. For testing purposes, I'm using very simple multiplication problems where each number (the "multiplier" and the “multiplicand," to be exact) is a random number between 1 and 10. Actually, I've just been using 10 X 10 for my testing so far to make it easy for me to troubleshoot the algorithms I've programmed.

Looking at the Code

The majority of the code is in the button "Next Lap." However, there is also some code in the card script to initialize the various game variables and empties any fields on the screen:

1:  on opencard  
2:      
3:    global varCar1x, varCar2x, varNum1range, varNum2range, varNum1, varNum2, varLap1, varLap2, varLap3  
4:      
5:    put empty into field "percent field"  
6:    put empty into field "num1 field"  
7:    put empty into field "num2 field"  
8:    put empty into field "answer field"  
9:    put 10 into varNum1range  
10:    put 10 into varNum2range  
11:      
12:    put 0 into varCar1x  
13:    put 0 into varCar2x  
14:      
15:    set the location of button "car1" to 100,70  
16:      
17:    put 20 into varLap1  
18:    put 10 into varLap2  
19:    put 5 into varLap3  
20:      
21:    put empty into field "data"  
22:      
23:  end opencard  

Here's the code in the button "Next Lap:"

1:  global varCar1x, varCar2x, varNum1range, varNum2range, varNum1, varNum2, varLap1, varLap2, varLap3  
2:    
3:  on mouseup  
4:       
5:    //reset fields  
6:    put empty into field "num1 field"  
7:    put empty into field "num2 field"  
8:    put empty into field "answer field"  
9:    put empty into field "percent field"  
10:      
11:    //put random(varNum1range) into varNum1 - I'll activate this after all testing is done  
12:    //put random(varNum2range) into varNum2 - I'll activate this after all testing is done  
13:    put 10 into varNum1 //I'll delete this and next line after all testing is done  
14:    put 10 into varNum2  
15:      
16:    put varNum1 into field "num1 field"  
17:    put varNum2 into field "num2 field"  
18:      
19:    put varNum1 * varNum2 into varAnswer  
20:    Ask "what is the answer?"  
21:    put it into varResponse  
22:    put varResponse into field "answer field"  
23:      
24:    //compute absolute percentage of correct answer  
25:    put abs(varAnswer-varResponse) into varDistance  
26:    put varAnswer-varDistance into varDistance2  
27:    put round((varDistance2/varAnswer),2) into varPercent  
28:    put varPercent into field "percent field"  
29:      
30:    //put varCar1x into message  
31:    if varPercent>=.95 then put varLap1+varCar1x into varCar1x  
32:    if varPercent>=.80 and varPercent<.95 then put varLap2+varCar1x into varCar1x  
33:    if varPercent>=.75 and varPercent<.80 then put varLap3+varCar1x into varCar1x  
34:      
35:    //move the race car  
36:    move button "car1" to ((varCar1x*8)+100),70 in 2 seconds  
37:      
38:    //show the key data to help with troubleshooting (to be deleted later)  
39:    put varCar1x&comma&location of button "car1"&return after field "data"  
40:      
41:  end mouseup  

For now, I'm using the convenient "Ask" command to get the student's estimation answer, shown in line 20. I'll change that later to an input field on the screen.

Lines 31-33 show the code to determine the percentage correct of the student's estimation. For now, I'm using the cutoffs of 95%, 80%, and 75%. These may change as I need to tweak the game play in future versions. Notice lines 17-19 in the card script above. These determine just how far the car will go for each percentage - right now these are 20, 10, and 5. I also expect these to change as the game design progresses.

Final Thoughts

I'm optimistic that elementary school students will find this to be a fun game. I think it's a good example of embedding mathematics into game play, in contrast with the common strategy of sugarcoating mathematics with a game context.




Thursday, October 12, 2023

Creating a 4-Pointed Dice App to Play the Royal Game of Ur

Want a free copy of this project? Go to nowhereroad.com/ur for the Macintosh or Windows version.
Note: Neither app is officially certified by Apple or Microsoft, so you'll need to follow instructions on the web page for how to circumvent the security blocks.

I am again teaching the Design Thinking course this semester for doctoral students. This week is game night. I invite everyone to bring in a favorite, family friendly game to share. We then spent about 75 minutes playing games while eating pizza and snacks followed by a discussion about which games are the most fun to play and why. At some point, usually in the weeks to follow  we'll extend these ideas and experiences to games in education. But, the goal this week is just to play some familiar games and revisit the experience of enjoying games and, mostly importantly, seeing if the "play phenomenon" reveals itself.

I like to take advantage of game night to encourage people who have never played chess to consider trying it out. I set up a couple of chess boards and send people some recommendations on short videos with the basics of the rules. A lot of adults who have never played chess often consider it "too late" to learn a game like chess. Off course, this is not true. A few people usually take me up on the offer.

This year I'm also introducing everyone to possibly the oldest game for which we know the basic rules, namely the Royal Game of Ur. The games dates back at least to about 4500 years. Tom Scott, one of my favorite YouTubers, made an excellent and very entertaining video introduction to the game featuring Irving Finkel of the British Museum: 

The Wikipedia entry for the game is also a good place to learn more about it (Irving Finkel is likewise featured in the entry).

An interesting part of the game is the use of 4-pointed dice. Two of the four points are painted a different color from the other two. Consequently, each die gives you a 50% chance of getting a colored point. You add up the number of colored points facing up, to determine how many spaces you can make on your next move. The result is a number somewhere between 0 and 4. Unfortunately, I don't own any 4-pointed dice. I've been using a fairly creative hack involving a traditional pair of 6-side dice. I throw the dice and simply subtract the two numbers, resulting in an answer from 0 to 5. It's important that a result of 0 is a possibility. The highest possible number can only be four, so if you throw a 6 and a 1 resulting in 5, you simply throw the dice again. This hack works well enough, but I have a feeling that the probabilities of the possible answers are very different from the original game.

So, late on a Friday afternoon, I decide to quickly make an app with LiveCode to simulate the throwing of the 4-pointed dice. This is probably the simplest little project I've ever written about on this blog. I had a functional prototype in about 15 minutes showing a string of four numbers - either 1s or 0s - each time a button is clicked. But, I really wanted to keep the experience in the same feeling of the game itself. So, I took an extra 90 minutes or so to improve the graphic design and tweak the interface. Here's a screen shot of a roll of the dice showing a total result of 2:


I know you can't read it, but the text at the bottom provides minimal directions: "Directions: Click anywhere in the window or press the space bar for a new roll." So yes, to roll the dice, you either just click inside the app window or press the Space Bar. There is an invisible button covering the entire area of the app's screen.

As simple as this project was to produce mathematically, creating the visual illusion of the dice forced me to relearn a basic skill, namely how to "skin" a button. That is, how to apply a graphic to a button.

Dissecting the Code

Here is the code for the button titled "Throw the Dice" - this button covers the entire screen and is not visible:

1:  on mouseup  
2:      
3:    hide button "die1"  
4:    hide button "die2"  
5:    hide button "die3"  
6:    hide button "die4"  
7:      
8:    put random(2)-1 into varResult1  
9:    put random(2)-1 into varResult2  
10:    put random(2)-1 into varResult3  
11:    put random(2)-1 into varResult4  
12:      
13:    put varResult1 into field "result1"  
14:    put varResult2 into field "result2"  
15:    put varResult3 into field "result3"  
16:    put varResult4 into field "result4"  
17:      
18:    if varResult1 = 1 then set the icon of button "die1" to 1023   
19:    else  
20:     set the icon of "button die1" to 1012  
21:    end if  
22:      
23:    if varResult2 = 1 then set the icon of button "die2" to 1023   
24:    else  
25:     set the icon of "button die2" to 1012  
26:    end if  
27:      
28:    if varResult3 = 1 then set the icon of button "die3" to 1023   
29:    else  
30:     set the icon of "button die3" to 1012  
31:    end if  
32:      
33:    if varResult4 = 1 then set the icon of button "die4" to 1023   
34:    else  
35:     set the icon of "button die4" to 1012  
36:    end if  
37:      
38:    //show correct image  
39:    put varResult1 + varResult2 + varResult3 + varResult4 into varScore  
40:    put varScore into field "score"  
41:      
42:    wait .5 seconds  
43:    put .2 into varWait  
44:    show button "die1"  
45:    wait varWait seconds  
46:    show button "die2"  
47:    wait varWait seconds  
48:    show button "die3"  
49:    wait varWait seconds  
50:    show button "die4"  
51:      
52:  end mouseup  
53:    

Having the Computer Compute the Result: Not Used in the End

The non-graphical version I completed in 15 minutes actually told the user the score of the toss in addition to show the list of 1s and 0s representing the result of each die. In the end, I decided against showing this mathematical results because not only was it not necessary, I felt it interfered with the game play. Players can obviously see for themselves the result just by looking at the dice, just as they would if the dice were physical objects. But, I left that code in for the heck of it. Here are the lines of code that now don't serve any function:

Lines 13-16 reveal the numerical result of each die - either a 1 or a 0 - in four separate fields.

Line 39 adds up each of the four results and put the final answer in the variable varScore. Line 40 displays varScore in the (now invisible) field "score."

Let's now take a look at the lines of code that remain necessary for the app to work.

Throwing the Dice and Getting the Visual Result

There are four buttons that represent each of the four die titled simply die1, die2, die3, and die4. These buttons serve no other function other than displaying the correct graphic, either with a blank point or a colored point. That may seem like an odd use of a button, but it works well.

Lines 3-6 hide the four buttons.

Lines 8-11 computer a random number from 0 to 1. The random function actually doesn't start with 0, so a command such as Random(2) will return either a 1 or a 2. So, all I did was subtract one from the output to get 0 or 1. Four random numbers are computed, one for each die using the variables varResult1, varResult2, varResult3, and varResult4.

Next are four groups of code that all work the same, but for each of the four die. So, let's dissect just the first group for die1, namely lines 18-21. Each group is a classic IF/THEN/ELSE construction. If the result of "die1"

Line 18 says that if varResult1 is a 1, then set the icon of button "die1" to 1023. Line 20 says if the result is not 1 (well, it must be a ), then set the icon to 1012. This is the code to skin the button with a pre-existing graphic stored somewhere in the stack. In this case,  there is a second card that stores the following images:


There are four images on this card, but only the top two are used. The others were examples I decided not to use. All objects in LiveCode are automatically assigned an ID number upon creation. The ID for the top left graphic is 1012 (no colored point) and the top right graphic has the ID of 1023. The button and the image have exactly the same dimensions, so the button takes on that graphical "skin." 

Lines 42-50 show the final results. Line 42 pauses for a half second before the four buttons, hidden a moment ago, before moving forward with revealing the results. Yes, the goal is to provide just a taste of anticipation. Lines 44, 46, 48, and 50 show each of the buttons in sequence, but with a very small pause between each, again trying to recreate some of the fun tension of the dice being thrown with the player taking a moment to scan the results. I wasn't sure how long a pause to provide, so rather than having to update four separate lines with a different wait time, I created the variable varWait to hold the wait time. This allowed me to play around with different values to get what I thought was the right time.

Allowing the User to Press the Space Bar Instead of Clicking the Button

As I tried out the app from the perspective of someone playing the Game of Ur, I thought the experience would be enhanced by letting the user just press the Space Bar instead of pressing the mouse button. It's a simple, but much better approach to the interface design. However, this was another skill I had to relearn as well. Luckily, I just looked up one of my old LiveCode projects to see how I did it previously. This code is place on the Card's script:

1:  on keydown theKey   
2:    Global isReading   
3:    if isReading = 0 Then   
4:     if TheKey = space Then   
5:       send mouseup to button "throw the dice"   
6:     end if   
7:    End If   
8:    if TheKey <> space Then   
9:     pass keydown   
10:    End If   
11:  end keydown   

Lines 4 and 8 says that if the key being pressed is the space key, then "send mouseup" to the button "throw the dice." This is equivalent to the user doing the same thing.

Adding Just One More Thing

I was about to publish this post, but thought I should wait until after game night to do so, just in case I needed to change or fix something. Of course, one needs to always be careful of "creeping featurism." That is, continually adding features to the point that you ruin the app. All worked well on game night, but it occurred to me at one point that dice don't line up neatly in a row after you throw them. So, I thought one more feature would add to the game experience, namely to have the dice appear at random spots on the screen.

This sounds simple enough, but there are traps to avoid. For example, one must be careful with random number generators. What would happen if, by chance, the computer chose the almost exact same location for two of the die? Well, you likely wouldn't be able to see the result of one of the die. So, I had one of those interesting little math problems to solve - where to randomly place each of the four die so that it is guaranteed that all four dice would clearly show their result.

To solve this, I graphed out the problem. The app's screen size is a convenient 400X800 with the origin located in the top left corner of the app window (with apologies to René Descartes). So, I decided to keep each die in their vertical "lane" of 200 pixels. With that in mind, the lane for each die, in order, is the following:

  • 0-200
  • 200-400
  • 400-600
  • 600-800
(Yes, I'm ignoring the 1 pixel overlapping at the edges of lanes.)

Each die's dimensions are 150X150 pixels, so it is important that the center of each die (which officially marks the location of any LiveCode object) is at least 75 pixels away from the center.

So, here is the grid I sketched out on a scrap of paper: 


The fundamental algorithm turned out to be the following:

   set the location of button "die1" to random(40)+X,random(210)+75

The width of the "display lane" for each die is 50 pixels. This was derived by subtracting 75 from both the left and right of the total width of the die's lane: 200-75-75=50. So, the left edge of each display lane is X. For die1 it's 0 because it is on the far left. X for the second die is 275, (200+75). X for the third die is 475 (400+75). Finally, X for the fourth die is 675 (600+75).  

Consequently, here is the code for moving each of the die to a random location:

   set the location of button "die1" to random(50)+75,random(210)+75
   set the location of button "die2" to random(50)+275,random(210)+75
   set the location of button "die3" to random(50)+475,random(210)+75
   set the location of button "die4" to random(50)+675,random(210)+75

Yes, yes, a rather impressive command of elementary school mathematics. (Well, I was a 5th grade teacher for awhile after all.)

Here is a screenshot of one throw of the dice:


Final Thoughts

This was a fun project with an impulsive motivation to do it. As is typical, getting a prototype to work functionally takes very little time in comparison to designing the user and graphical interface. Of course, the most time spent on this project was the time it took to write this blog post. 

Oh, one last thing... I also worked out the probabilities of each of the possible results of throwing four 4-pointed dice:

  • 0: 1 in 15
  • 1: 4 in 15
  • 2: 5 in 15
  • 3: 4 in 15
  • 4: 1 in 15
I think knowing this contributes to the strategy of the game.

Monday, June 19, 2023

Lloyd's Screencast Teleprompter App



 "Video is the new PowerPoint," or so the saying goes, even though I'm a Google slides guy myself. I make a lot of videos for my teaching and most consist of me providing narration for a collection of slides. I use the "shelf life time ratio" to determine how much planning should go into each. That is, I do very little planning for a video I intend a specific audience to watch just once. For these, I'm basically "shooting from the hip" and making things up as I go. In contrast, a video with an intended long shelf life - videos I plan on using semester after semester - requires a lot of planning up front. The ratio is simply the longer the shelf life, the more planning that is required.

For long shelf life videos, I long ago learned to write scripts, at least for the very beginning of the video and key sections. Viewers will get turned off quickly if the first 30 seconds of the video is of a bumbling and stammering professor who isn't sure what he wants to say. But where to display one's scripts or notes while making the video? Sometimes I taped the script to the computer screen. Yet, I've made too many videos where it is painfully obvious that I'm reading something. You can see my eyes moving back and forth and you can slowly see my gaze go from top to bottom. I've tried posting small script portions on a stand or box sitting just behind my computer as close to the camera as possible. These results are better, but I'm forced to have a bunch of cuts as I trade out each new portion. I'd much prefer to be known as "one take Rieber." I've been struggling with this issue for almost 20 years. So, it kind of shocks me that it took me this long to have the idea to build my own teleprompter for screencasts.

For the record, I have tried using third party teleprompters in the past for videos where I'm being filmed without a computer. I've never been too comfortable with them. It's hard to get the speed right and it's a challenge of where to put them in relation to the camera. (You must realize that I have no budget and I am my own "video crew," so I'm constantly jerry-rigging up the equipment.) But, at least 97% of the videos I make are screencasts - me narrating a set of slides or some other application, with me in a small video window off to the side. What I really need is a small, compact teleprompter that can be displayed on the screen near the computer's camera with plenty of screen space remaining for the window I wish to film. It seemed like a perfect LiveCode project.

My Third Design Works Like a Charm

I'm very happy with the design of the app, but I had two painful failures at the start. I'll get to those later, but allow me to show you the current design. I'm still working on the graphic design, but my field tests of the functionality have gone very well. Here is a short video demonstration:

Here are some of the main design principles of the app:

  • Controlling the speed of the teleprompter needs to be easy and unobtrusive, so that the user could change the speed while narrating with virtually all attention devoted to the meaning of the text.
  • The teleprompter needs to consume very little screen space.
  • The teleprompter needs to be able to be positioned as close to the computer camera as possible so that it appears that the user is looking at the viewer.
  • The text size needs to be under the user's control.
  • The size of the teleprompter window needs to be under the user's control.
  • The text can be pasted into the teleprompter and then edited during the rehearsal of the narration.

Of these, the first is the most important. I needed a way to let the user alter the speed of the teleprompter easily throughout the narration, stopping it when necessary and even backing up if the need arose. Controlling the teleprompter speed needed to be as easy as possible requiring very little cognitive demands. I found that the best way to do this was through the use of mouse hovering over the control buttons with the teleprompter speed increasing or decreasing as the mouse moved right or left. This means that no mouse clicking is needed, the sound of which would likely have been picked up by the microphone.

Code Examples

The most important code is in the two green buttons. Here is the code for the one on the right, the one that propels the text in the prompter forward:

1:  global varScrollPosition   
2:  on mousewithin  
3:    set the locktext of field "prompter" to true  
4:    if varScrollPosition > the scroll of field "prompter"+2 then exit mousewithin  
5:    put item 1 of the mouseloc into varMouse  
6:    put ((varMouse-700)/25)+5 into varScrollSpeed    
7:    add varScrollSpeed to varScrollPosition  
8:    set the vScroll of field "prompter" to varScrollPosition  
9:  end mousewithin  
10:  on mouseleave  
11:    set the locktext of field "prompter" to false  
12:  end mouseleave  

The variable "varScrollPosition" controls the scrolling of the field "prompter." The variable "varScrollSpeed" controls the speed of the scrolling. The only tricky part of this code is shown in line 6. If you watch the video, you'll notice that the scrolling rate increases as the hovering mouse moves toward the right of this button. I wanted the range of scrolling speed to be from 5 to 25 pixels where 5 is very slow and 25 is very fast. The left edge of this button is 700 pixels to the right of the app window. So, I had to subtract 700 before dividing the location of the mouse by 25. This created a nice sliding scale from 0 to 25. I then added 5 so that the 5 would be the minimum scrolling speed when hovering over this button.

varScrollPosition needs to be a global variable so that the position of the scroll bar is maintained throughout the session.

I locked the text during scrolling, but unlocked it when not scrolling. This allows the user to edit the script while rehearsing.

Line 4 determines if varScrollPosition goes beyond the bottom edge of the field.  I couldn't find a system variable to tell me the range of the scrollbar, but I found system variable "scroll" that returns the current scroll position. It tops out once the scroll bar hits the bottom. So, the variables varScrollPosition and scroll are virtually the same until the scrolling reaches the bottom of the scroll bar. So, I plugged in this little formula to stop increasing varScrollPosition once it is more than 2 pixels more than scroll. 

The other options were all very simple to code. As one example, here's the code for the button that increases the vertical height of the teleprompter field:

1:  on mouseup  
2:    put the height of field "prompter" into varHeight  
3:    put the top of field "prompter" into varTop  
4:    add 10 to varHeight  
5:    set the height of field "prompter" to varHeight  
6:    set the top of field "prompter" to varTop  
7:  end mouseup  

This adds 10 pixels to the height of the field. I keep the top of the field fixed, so as the height increases, the length is added to the bottom.

All of the other options were equally simple to code to manipulate the format of the teleprompter field.

My First Two Designs Were a Disaster

I really like the simplicity of the final design in the way that it controls the system variable "vScroll." It contrasts mightily with my first two attempts. I had either forgotten or didn't know about the system variable "vScroll." So, I had concocted some very heavy handed approaches to getting the scrolling to work. I'm not going to even try to give the details, but here is a brief explanation.

I began by breaking down the script into individual characters. Line breaks would be added anytime a period was detected. I also figured that the user would want to insert line breaks at various places, so I added a search for the forward slash "/". I figured only three lines of the script would need to be shown at one time, so I created three variables, varSentence1, varSentence2, and varSentence3 to contain each. The idea was to create a type of cascading approach where the fourth sentence would become sentence 3, which would become sentence 2, which would become sentence 1. This would continue until the end of the script. I actually got it to work, but it was very hard to read as the sentences jumped abruptly from line to line. 

I could go on as there was more ugliness in these first two designs. Now you might think I'm embarrassed by such poor initial designs, but no, far from it. I've learned that the best designs don't appear magically at the first go. They take time to reveal themselves. The only way to finally get them to show up is to start on the journey and then always be on the lookout when you finally stumble across them. Yes, good - even great designs - almost always start off with failure.



Sunday, May 7, 2023

AI: The Future of Programming?

Like just about everyone else, I've been exploring ChatGPT to figure out if it is a curse or a blessing. All of the experts seem to agree it's both. At universities like mine, there has been quite a bit of gnashing of teeth over the worry that students will use it to cheat. This is a real concern, though of course cheating didn't start with ChatGPT. Still, it is a bit unnerving to type a simple request into ChatGPT, such as "Write a 3 paragraph essay on what were the major causes of the American Civil War" and then watch the AI engine produce a very convincing essay neatly matching your content and length requirements. 

Of course, worries about the impact of new technologies on education and society are not new. My first encounters with educators' worries about technology were with the advent of the calculator and then spellchecking. I also recall reading some historical literature claiming that Gutenberg's printing press took away our memories. Somehow, we have grown use to these cognitive aids and don't question their value anymore. Besides, I think education's attempts to either thwart or stall the use of a new technology is best characterized as a "cat and mouse" game. In the end, the mouse always wins. So, rather than look to how to stop the technology at education's door, I think a better approach is find ways to harness it to support students and other educational aims.

Education has also long been ambivalent about what Donald Norman referred to as "knowledge in the head" versus "knowledge in the world." That is, we seem to think that the purpose of education is to require students to commit to long-term memory everything they would ever need to know or do. A better approach is to look at ways to form partnerships between what one knows and what one can get from interactions with the world. Anyone who ever put their car keys on top of the grocery list to make sure they didn't forget to take it as they walked out the door in the morning understands the value of knowledge in the world. One can go to bed relaxed about not needing to remember to take the list in the morning because when you are running late the next day and reach for your car keys, the list will be there to remind you. 

LiveCode and AI

Alright, enough philosophy, let's get down to business. The purpose of this post is to share how the AI of ChatGPT has helped me to do some programming. Amazingly, ChatGPT knows all about LiveCode. A couple of weeks ago I tried it out by requesting a bunch of examples, such as "Write a program that sorts a list of the 50 states in alphabetical order using the LiveCode programming language." ChatGPT wrote out some very nice code for me to then insert into buttons and cards. However, I already knew how to do that task. I tried some trickier requests, such as "Write a stopwatch program that counts the seconds and allows the user to pause the counting at any time using the LiveCode programming language." ChatGPT again had no trouble doing so, and it even gave me a nice explanation of the various handlers it created in the code (see the appendix at the bottom of this post for the ChatGPT output). Of course, I had to know how to create the buttons and cards and also know how to put the code provided by ChatGPT into them.

So, wouldn't it be nice if there was an AI program that could be embedded into LiveCode that not only could write the code, but also create all of the various LiveCode objects with the code already comfortably nestled inside? Well, that's exactly what the LiveCode company is wanting to do by eventually offering a new programming platform called Xavvi. I recommend watching the video by Kevin Miller as he explains why and how LiveCode needs to move in this direction. He repeated a stark prediction that in about 10 years there will be two types of companies - those that have embraced AI and those that are no longer in business. It's unclear if the LiveCode company will be able to muster the resources to pull this off, but I hope they succeed. (They are running another fundraising campaign to support this move.) 

My Encounters this Weekend with ChatGPT

This past weekend, I had my own epiphany that gives support to Kevin's opinion of the need to move in the direction of using AI to support app development. Interestingly, it doesn't involve LiveCode, but PHP. I needed to do some updating of my web site that supports my Q sort project, now in it's eighth year. I built a Q sort app for the project with LiveCode, but the supporting web site uses PHP and mySQL to store instructor credentials, Q sort definitions, Q sort activities, and Q sort data. However, I've never been very conversant with PHP. When I began my foray into creating database-driven web sites, I used the built-in PHP server behaviors of Adobe Dreamweaver. (Please don't laugh. It was really wonderful.) I was able to build and maintain full CRUD (Create, Retrieve, Update, and Delete) web sites very quickly with very little need to know PHP code. Of course, I had to sometimes tweak the code or learn how to extend the code, but I could easily find some help and examples online. But I never learned PHP well enough to code in it myself from scratch and I was fine with that. My goal was to be productive and I didn't have the time to learn how to code in PHP from scratch. Unfortunately, Adobe took away all of those behaviors from Dreamweaver a few years ago, so I've been faced with coding PHP on my own. Trouble is, I only poke around in PHP every few months or even years. So, each time I've largely forgotten most of what I had learned previously. And I really needed to get those updates for my Q sort web site done now. Alas, what to do?

Well, I decided to see if ChatGPT could help, and help it did. I was able to deploy several new web pages that successfully connected to the mySQL database with code written entirely by ChatGPT. The hard part was figuring how how to appropriately prompt ChatGPT to do it. That is, what instructions did one need to give it? Here is one example of a simple request I gave ChatGPT that resulted in perfect code for a completely new web page that retrieved data from the database and displayed it in a table:

Write the HTML code for a web page with PHP from a mySQL database DBLIVECODE using mySQL from the table qsort_data with the database username NAMENAME and the password is PASSWORDPASSWORD . Put the database in a html table. 

Here's the code that ChatGPT generated, which I was able to easily copy and paste into a web document (with the private information clearly altered in all caps):

 <!DOCTYPE html>  
 <html>  
 <head>  
      <title>MySQL Table to HTML Table</title>  
 </head>  
 <body>  
      <?php  
           // Database connection information  
           $host = 'localhost'; // or IP address of the server  
           $dbname = 'DBLIVECODE'; // name of the database  
           $username = NAMENAME; // database username  
           $password = 'PASSWORDPASSWORD'; // database password  
           // Create a new MySQLi object  
           $conn = new mysqli($host, $username, $password, $dbname);  
           // Check for errors  
           if ($conn->connect_error) {  
                die("Connection failed: " . $conn->connect_error);  
           }  
           // Define the query to retrieve data from the table  
           $sql = 'SELECT * FROM qsort_data';  
           // Execute the query  
           $result = $conn->query($sql);  
           // Check for errors  
           if (!$result) {  
                die("Query failed: " . $conn->error);  
           }  
           // Display the data in an HTML table  
           echo '<table>';  
           echo '<tr><th>Column 1</th><th>Column 2</th><th>Column 3</th></tr>';  
           while ($row = $result->fetch_assoc()) {  
                echo '<tr>';  
                echo '<td>' . $row['q_topic'] . '</td>';  
                echo '<td>' . $row['q_code_activity'] . '</td>';  
                echo '<td>' . $row['q_data'] . '</td>';  
                echo '</tr>';  
           }  
           echo '</table>';  
           // Close the database connection  
           $conn->close();  
      ?>  
 </body>  
 </html>  

Again, the only hard part was figuring out what exactly to tell ChatGPT to do. Obviously, some knowledge of databases, PHP, and HTML was required on my part in order to get ChatGPT to give me what I needed. But, only a little trial and error was necessary to get ChatGPT to deliver the goods. Over the weekend, I made other requests to ChatGPT, such as as for code to update specific database tables and records. 

I'll treat AI like I treat GPS

ChatGPT and I are now "partners" in web development. However, I don't want to just blindly follow what it gives me. I want to learn as I go with the hope of putting some of the knowledge gained in my head and not just in the world. I think this relationship will be similar to how I use GPS apps on my phone. I always want to know where I'm going and will never just follow a GPS blindly. I like to map out my route to get the big picture of of my trip. But when I'm in a big city (like I was in Chicago a few weeks ago), it is wonderful to have the GPS guide me moment-to-moment in taking key exits and getting in the correct lanes as I anticipate making the next turn. I just hope that ChatGPT doesn't lead me into a blind alley.

Thursday, May 19, 2022

A Metaphor for Iterative Design: A Livecode Project to Demonstrate the Sierpinski Gasket

I had the good fortune to be invited by the students and faculty of the Learning Sciences and Mathematics and Science Education doctoral programs at Boğaziçi University in Turkey to participate in a seminar on design-based research (DBR). DBR has become a particularly important research approach in the field of learning, design, and technology. I decided to use my 30 minute presentation on May 11, 2022 to focus on the importance of being a designer when engaging in DBR. That is, I think it important to take on the identity of a designer. When this happens, you take on a different view of the world that will influence and support your research.

The Importance of Iteration in Design

An important aspect of all design, including DBR, is iteration. The everyday definition of iteration is simply to repeat a process or procedure. But, I prefer the mathematical definition of iteration because I think it is closer to how iteration needs to be practiced in DBR. According to Wikipedia, iteration in mathematics is "the process of iterating a function, i.e. applying a function repeatedly, using the output from one iteration as the input to the next."

The key idea here is that the output of one iteration is used (at least partially) as the input of the next iteration. I think this distinction is important for DBR because it means you revise your intervention based on what you just learned from trying it out. Implementing this principle well leads to profound insights which a designer can use in the revision process. In this process you stop, reflect, and make explicit - by writing down - what you learned. This is followed by making a conjecture that if the design is revised in such and such a way, then something better will happen the next time the design is tested (e.g. more learning, more motivation, etc.) If no improvement is found, then that conjecture is refuted and a new conjecture must be constructed. All of this is being documented along the way in DBR. These conjectures should be based in the formulation and evolution of a local or "humble" theory generated by the designer/researcher. This is the heart of the DBR process. 

A Fun Example of Iteration

Rather than just talk about this on May 11, I thought I'd show a simple mathematical example as both a literal (it really shows iteration in mathematics) and metaphorical example (within DBR) of iteration. Consider the following:

  1. On a sheet of paper, draw three dots anywhere you like. These become the vertices of a triangle. I've drawn these in red in the figure below.
  2. Pick a random spot on the paper and draw another dot. I've drawn this in black on the left side of the figure below.
  3. Pick one of the three red dots at random. Let's say the bottom-right red dot was chosen.
  4. Draw a dot at the midpoint of the black dot and the randomly chosen red dot.
  5. Repeat steps 3 and 4 using the previously drawn black dot for step 4.


What do you think you will get after lots of dots, say 1000, 5000, or 10,000?

Some people guess that the piece of paper will just get filled with dots. Others guess that the triangle formed by the red dots will just fill up. Another guess is that a circular pattern will emerge.

The actual result is quite surprising. Here is a short video of a LiveCode project I built to demonstrate this example:



(I don't know about you, but I find the erasing of the dots almost as mesmerizing as the emergence of the design.)

Here is the result drawn with 5000 small dots:

This is a special fractal known as the Sierpiński Gasket. It infinitely repeats the triangular shape. That is, if we were able to keep drawing an infinite number of dots with an ever-increasing fine point, the shape would continue to repeat itself to infinity no matter how much we zoomed in or magnified any portion of the figure. It is a most surprising result from such a simple set of rules.

But, my point in the seminar was not to teach anyone about fractals, but rather to use this example in an entertaining way to make some important points, metaphorical though they might be to process of iteration within DBR:

  • The process of iteration, though a seemingly simple idea, leads to insights of otherwise hidden patterns or deep structures for how to revise the design of a learning intervention.
  • The process of iteration can bring order and understanding to the otherwise complex activity of designing for learning.
  • Despite the apparent sense of order, there is still an infinite range of possible outcomes for our designs. That is, the experience of any one person when they use one's design will be unique for that person. 
I could probably come up with lots of more metaphorical lessons given that I never "met-a-phor I didn't like," but like all metaphors, there are limits before the metaphor breaks down. Obviously, iteration within DBR is not a mathematical function, but I hoped this example would pique the audience's curiosity and interest in the principles above.

Building the Project in LiveCode


This is actually a project I built about 30 years ago using HyperCard, the ancestor of LiveCode. My original HyperCard program harnessed the graphics tools to actually draw dots on the screen. That approach still works in LiveCode, but it is frustratingly slow. So, I redesigned the approach to copy and paste the graphical object of a black dot within a loop that follows the rules above.

Here's the LiveCode interface:


On another card titled "resources" I created two graphic objects for the two black dots used in the program (medium and small) using the oval graphic tool:

The three red dots are also drawn with the oval graphic tool and are titled "triangle1," "triangle2," and "triangle3". The "Start" button contains the code to draw the fractal:
1:  global gp,gx,gy,bx,beginy,x,y,  
2:  global varLoops, varPointName  
3:    
4:  on mouseUp  
5:    put 0 into field "counter"  
6:    put field "loops" into varLoops  
7:      
8:      
9:    wait 1 second  
10:      
11:    //Game Board  
12:    --choose first "starting point" at random   
13:    put the random of 1000 into x   
14:    put the random of 500 into y   
15:    copy graphic varPointName on card "resources" to this card  
16:    hide it  
17:      
18:    set the location of it to x,y  
19:    show it  
20:    put x into gx   
21:    put y into gy   
22:      
23:    //Play the Game  
24:    Repeat with i = 1 to varLoops  
25:     if i < 101 then  
26:       put i into field "counter"  
27:     else  
28:       if (i/100)-trunc(i/100)=0 then put i into field "counter"  
29:         
30:     end if  
31:       
32:     --choose one of the three "game points" at random   
33:     put the random of 3 into gp   
34:     if gp = 2 then   
35:       put item 1 of the location of graphic "triangle2" into bx  
36:       put item 2 of the location of graphic "triangle2" into beginy  
37:     end if   
38:       
39:     if gp = 1 then   
40:       put item 1 of the location of graphic "triangle1" into bx  
41:       put item 2 of the location of graphic "triangle1" into beginy  
42:     end if   
43:       
44:     if gp = 3 then   
45:       put item 1 of the location of graphic "triangle3" into bx  
46:       put item 2 of the location of graphic "triangle3" into beginy  
47:        
48:     end if   
49:       
50:     --draw the midpoint of the "starting point" and "game point"   
51:     put (bx+gx)/2 into x   
52:     put (beginy+gy)/2 into y   
53:     put round (x) into x   
54:     put round (y) into y   
55:     copy graphic varPointName on card "resources" to this card  
56:     set the name of it to varPointName&i  
57:     set the location of it to x,y  
58:       
59:     wait 1 ticks  
60:     --make the midpoint the new "start point" and repeat   
61:     put x into gx   
62:     put y into gy   
63:       
64:    end repeat   
65:      
66:  end mouseUp  

Here are brief explanations of key lines of code:

Lines 6 takes the number the user enters and stores this in the variable "varLoops." This determines how many dots will drawn on the screen.

Lines 11-21 draw the first black dot at a random point on the screen. The coordinates of this dot's location are stored in the local variables gx and gy.

Lines 24-64 is the repeating loop that does all of the work.

Lines 24-28 count up the number of dots drawn so far and displays this on the screen. The act of updating the field with this number can slow the whole program down considerably, so I figured out some code to only show the numbers 1-100, then increments of 100 thereafter.

Line 33 chooses a number from 1 to 3 at random. Lines 34-48 takes this random number and chooses one of the three red dots accordingly.

Lines 51-57 figure out the midpoint of the randomly chosen red dot and the last black dot and put a new black dot at that location. Line 55 copies and pastes the black dot from the card "resources" to this card, then moves it to the coordinates of the midpoint. Notice, by the way, that line 56 sets the name of this newly copied and pasted graphic object as "varPointName&i". The local variable i is chosen at the start of the repeating loop in line 24 as the counter for the loop. The "&" is the concatenation symbol which is used to "join" two sets of characters. So, the 56th dot copied and pasted will have the name "varPointName56." This is important because the "Erase" button will use these names to erase each dot in sequence.

Line 59 slows down the drawing of the dots just a little. A "tick" simply 1/60 of a second. I wanted the fractal to "reveal itself" quickly, but not too quickly, so as to keep the audience wondering what was being revealed. Slowing down the drawing of the dots further might be a good idea, depending on the audience and context.

Notice that lines 35-36, 40-41, and 45-46 refer to the location of each of the three vertices. I made each vertex "grabbable" so that I could move each anywhere I wanted on the screen. Here is the simple code embedded in each red dot that makes this possible:

on mousedown
   grab me
end mousedown

Finally, the "Erase" button simply deletes all of the graphics objects containing each of the dots. Here is the code for the button "Erase:"

1:  global varLoops, varPointName  
2:    
3:  on mouseup  
4:    put 0 into field "counter"  
5:       
6:    repeat with i = 1 to varLoops  
7:     put varPointName&i into varName  
8:     delete graphic varName  
9:    end repeat  
10:      
11:    delete graphic varPointName  
12:      
13:  end mouseup  

Lines 6-9 repeat for as many dots were drawn on the screen, except for the very first dot. Each is erased at line 8. Recall that i is the counter for the repeat statement, so if there are 100 dots, dot 56 will get erased on the 56th loop. The dot at that loop would be named "varPointName56" at that point in line 7, which is then stored in the local variable varName. Line 8 commits the deed and deletes that graphical dot. What makes the resulting animation mesmerizing, I think, is that the dots disappear in the same order in which they first appeared.

Final Thoughts


There are lots of possible "next projects" using the above code as a foundation. What would appear if I use 4, 5, or 6 vertices instead of 3? I'm sure the answer is already known and published somewhere by a mathematician and all I have to do is Google it. But, it is fun to "play mathematician" occasionally to find out for myself. I would also like to explore further the idea of using the polygon tool to create this figure. Then, I could play "connect the dots," perhaps with a random color for each line segment. That sounds like a nice blending of mathematics and art.

I end with thanking the students and faculty of the Learning Sciences and Mathematics and Science Education doctoral programs at Boğaziçi University in Turkey for inviting me to share some thoughts about design-based research. It gave me a good excuse to do some computer programming in LiveCode for fun.