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.



Saturday, April 16, 2022

I Made a Wordle Cheating Program (by Mistake)

I recently was a guest speaker in an educational game design class at Old Dominion University. In preparation, I thought I'd check out Wordle given that it has been very popular recently. The "official" Wordle site is at the New York Times and it only provides a new word once a day. However, here is a good online version where you can play unlimited games.

The game's simplicity intrigued me - the graphic design just involves letters and simple colored boxes with a very simple user interface. It's quite a contrast when compared to other popular online games involving 3D graphics and virtual worlds. According to Wikipedia, Wordle was developed by Josh Wardle who eventually sold the game to the New York Times for "an undisclosed seven-figure sum." Hmm, I said to myself. I once created a word game called "Crack the Code." Maybe I have a seven-figure sum game I need to release to the world. Heck, I'd settle for a six-figure sum sale. As it turns out, I did "release this to the world" back in 2013 in a blog post. Unfortunately, my game is not much fun to play. How do I know? Well, when I asked people to play it and asked them how they liked it, they replied "Sorry, Lloyd, it's just not very fun to play." Pretty solid evaluation data there. The question of "What makes a game fun to play?" is actually a very interesting and profound question. So, it's worth thinking about why Wordle is so captivating.

Interestingly, even though I have much interest in games, I do not identify myself as a "gamer," mainly because I'm legitimately afraid of getting addicted and I just don't have time for that. Well, after playing Wordle for a short time, I was hooked. As I played it, it was clear to me that the first and second words you enter at the start of the game are very important. It turns out there are lots of web sites and videos that propose the best words to use. I've settled on "audio" as my favorite first word given that it contains so many vowels. Other favorites are "radio" and "audit." Here's an interesting video that provides some interesting statistics and advice for choosing a first word - bottomline: don't pick "fuzzy."

As I continued to play the game, I began to wonder how to determine the best way for myself to choose the first and second words. Given that the game is based on text processing, I thought this would be a perfect LiveCode project. So, last Sunday afternoon, I spent about two hours to quickly produce such a LiveCode program. A day or two later I thought this would be a good program to share with others on this blog, so I spent another two hours or so revising the user interface so others could use it. (As is typical, the time spent on the user design interface takes as long - and usually longer - than the programming for the software itself.)

Here's a screen shot of the interface:

I'll explain how this works in a moment, but the important point I want to make is my sole purpose in creating this app was only to gather statistics on the best words to choose at the beginning of the game. However, it turns out that my app also is the perfect tool to cheat at Wordle! The reason is that my program continues to whittle down the possible word matches as you go. Consequently, I can pretty much just choose a word at random at each turn of the game and win. As someone quickly asked me when I told them about this, doesn't that take the fun out of the game? The answer is yes. Of course, for me, I continue to find it quite exhilarating to see my little program chug along to beat the game.

Obviously, one needs a good list of five letter words from which to search for good and bad matches. I used the word list available at this site: https://eslforums.com/5-letter-words/

However, I really need to find a more complete word list because after using my app to play Wordle, I found my list was missing these common words: torte, motel, moron, spank.

Ok, here's a short video of me using the program as I played a round of Wordle.


The main code that makes this all work can be found in the "Search Word List" button. Here's the code:

1:  on mouseup  
2:    put empty into field "matches"  
3:      
4:    put the number of lines in field "word list" on card "resources" into L  
5:      
6:    repeat with i = 1 to L //word list loop  
7:     put false into varSkipWord  
8:     put line i of field "word list" on card "resources" into varWord  
9:       
10:     //Check first for matched letters in correct spot  
11:     put line 1 of field "first" into varFirstMatch  
12:     if field "first" is not empty then  
13:       if char 1 of varWord <> varFirstMatch then next repeat  
14:     end if  
15:     put line 1 of field "second" into varSecondMatch  
16:     if field "second" is not empty then  
17:       if char 2 of varWord <> varSecondMatch then next repeat  
18:     end if  
19:     put line 1 of field "third" into varThirdMatch  
20:     if field "third" is not empty then  
21:       if char 3 of varWord <> varThirdMatch then next repeat  
22:     end if  
23:     put line 1 of field "fourth" into varFourthMatch  
24:     if field "fourth" is not empty then  
25:       if char 4 of varWord <> varFourthMatch then next repeat  
26:     end if  
27:     put line 1 of field "fifth" into varFifthMatch  
28:     if field "fifth" is not empty then  
29:       if char 5 of varWord <> varFifthMatch then next repeat  
30:     end if  
31:       
32:     //Second, check for letters to exclude  
33:     put the number of lines in field "exclude" into M  
34:     repeat with j = 1 to M //use excluded letters loop  
35:       put line j of field "exclude" into varLetterToExclude  
36:       if varWord contains varLetterToExclude then   
37:        put true into varSkipWord  
38:        exit repeat //no need to keep looking, just exit out  
39:       end if  
40:     end repeat //END use excluded letters loop  
41:       
42:     //Third, check for letters to include  
43:     put the number of lines in field "include" into N  
44:     repeat with k=1 to N //use included letters loop  
45:       put line k of field "include" into varLetterToInclude  
46:       if varLetterToInclude is not in varWord then  
47:        put true into varSkipWord  
48:        exit repeat //no need to keep looking  
49:       end if  
50:     end repeat //END use included letters loop  
51:       
52:     //Fourth and last, check for matched letters but not yet in the right spot  
53:     put line 1 of field "notfirst" into varNotFirstMatch  
54:     if field "notfirst" is not empty then  
55:       if char 1 of varWord = varNotFirstMatch then next repeat  
56:     end if  
57:     put line 1 of field "notsecond" into varNotSecondMatch  
58:     if field "notsecond" is not empty then  
59:       if char 2 of varWord = varNotSecondMatch then next repeat  
60:     end if  
61:     put line 1 of field "notthird" into varNotThirdMatch  
62:     if field "notthird" is not empty then  
63:       if char 3 of varWord = varNotThirdMatch then next repeat  
64:     end if  
65:     put line 1 of field "notfourth" into varNotFourthMatch  
66:     if field "notfourth" is not empty then  
67:       if char 4 of varWord = varNotFourthMatch then next repeat  
68:     end if  
69:     put line 1 of field "notfifth" into varNotFifthMatch  
70:     if field "notfifth" is not empty then  
71:       if char 5 of varWord = varNotFifthMatch then next repeat  
72:     end if  
73:       
74:     if varSkipWord is false then put varWord&return after field "matches"  
75:    end repeat //END word list loop  
76:      
77:    put the number of lines in field "matches"&space&"matches" into field "matches label"  
78:      
79:    //Compute Statistics  
80:    put the number of lines in field "matches" into varMatchesCount  
81:    put line 1 of field "guess" into varGuess  
82:    put the number of lines in field "matches" into varNumerator  
83:    put the number of lines in field "word list" on card "resources"into varDenominator  
84:    put varGuess&comma&varMatchesCount&"("&round((varNumerator/varDenominator) * 100,3)&"%"&")"&return after field "statistics"  
85:      
86:  end mouseup  

I put a comment at the start of each important block of code. The key variable is varSkipWord. This variable will determine whether or not each word in the list of over 2500 words should be skipped. If skipped, the word does not match any still available word to try in the next guess. In line 7, I set this to false, meaning that unless something tells the computer otherwise, keep the word as a possible match - that is, don't skip it. At various places in the code, you'll see the code "put true into varSkipWord," such as in line 37. If varSkipWord remains false by the time the code reaches line 74, then the word is added to field "matches," which shows all of the words that are viable possibilities for the next guess.

In conclusion, this little project was perfect for LiveCode. Too bad I ruined my own Wordle game experience. I hope I don't ruin it for you.