Wednesday, July 16, 2025

Where Again is Arkansas? A Geography Location Game, Part 2: Determining Which State the Player Has Clicked

 

I ended my previous post having developed a mileage matrix of distances between states. In this post, I focus on how to integrate that mileage matrix into the game. When the player clicks on the wrong state, the distance in miles between the center point of both states will be used as a type of accuracy score. For example, it is one thing to confuse Georgia and Alabama, given that they share a border, but quite another to confuse Georgia with Oregon. This would clearly show that the player really has no clue where Georgia is located. 

In order to use mileage matrix, I obviously had to know which two states were being compared. Although I already know the target state given that it was chosen at random by the computer, I needed a way to figure out which state the player clicked on. How to do this was not immediately clear to me. I mentioned in my previous post about I could have created individual images for each of the lower 48 states, which would have allowed me to use LiveCode's intersect function. Although I may revert to that strategy at some point, I wanted to find another way based on the general x and y coordinates of each state. After trying one approach that was good, but not great, it occurred to me to compare of the sum of the distances on the x and y axes between the clicked location and the pair of coordinates for each state. The state that had the lowest sum would be the state clicked on by the player.

I created a new card to test out the approach. Here's a screen shot:


So, why use the sum of the x and y coordinates. Notice how some of the states line up nicely on the vertical axis (e.g., North Dakota, South Dakota, Nebraska, Kansas, Oklahoma, and Texas) and others line up on the horizontal axis (e.g., Tennessee and North Carolina). By summing up the x and y coordinates of the player's click, then comparing this to the absolute distance between all of the states, the state with the lowest difference has the highest probability of being the correct state. Error only creeps in when the player clicks on a spot that is far from the state's middle. The error is less when the state is relatively symmetrical, like Colorado, and more when the state has a non-symmetrical shape, such as Louisiana, Florida, or Michigan (geez, what a weird geography for a state having an upper and lower peninsula configuration). Similarly, the error rate is higher for the smallest states, like Rhode Island and Connecticut. 

Here's the code for the button "What state am I clicking" that does the work:

1:  on mouseup  
2:      
3:    wait until the mouseclick  
4:    put the location of image "USA with labels.png" into varLocationUSA  
5:    put the mouseloc into field "clicked spot"  
6:    put (item 1 of the mouseloc - item 1 of varLocationUSA) into varStateLocationx  
7:    put (item 2 of the mouseloc - item 2 of varLocationUSA) into varStateLocationy  
8:      
9:    //Compute the absolute x and y distance from the mouse click and each of the lower 48 states  
10:    put the number of lines in field "coordinates" into L  
11:    put empty into field "state xy differences"  
12:    repeat with i = 1 to L  
13:     put item 1 of line i of field "coordinates" into varX  
14:     put item 2 of line i of field "coordinates" into varY  
15:     put abs(varStateLocationx - varX) into varTemp1  
16:     put abs(varStateLocationy - varY) into varTemp2  
17:     put item 3 of line i of field "coordinates" into varTemp3  
18:     put varTemp1&comma&varTemp2&comma&varTemp3 into varTemp4  
19:     put varTemp4 after field "state xy differences"  
20:     put return after field "state xy differences"  
21:     sort field "state xy differences" ascending numeric by first item of each  
22:    end repeat  
23:      
24:    //Compare the sum of the absolute values of the x and y and use lowest value to determine the state  
25:    put empty into field "xy sum"  
26:    put the number of lines in field "state xy differences" into L  
27:    repeat with i = 1 to L  
28:     put item 1 of line i of field "state xy differences" into varX  
29:     put item 2 of line i of field "state xy differences" into varY  
30:     put varX + varY into varZ  
31:     put varz&comma&item 3 of line i of field "state xy differences"&return after field "xy sum"  
32:    end repeat  
33:    sort field "xy sum" ascending numeric by item 1 of each  
34:    put item 2 of line 1 of field "xy sum" into varYouFoundTheState  
35:    put varYouFoundTheState into field "target state"  
36:    
37:  end mouseup  

Lines 4-7 note the location of the USA map and then record the x and y of the player's click relative to the USA map's center. (This was explained more fully in my previous post.) The x and y location of the player's click are stored in the variables varStateLocationx and varStateLocationy.

Lines 9-22 compare the x and y location of the player's click to each of the 48 states. Since we are interested in the raw distance between the two points, lines 15 and 16 compute the absolute value of this distance. The results of these comparisons are neatly put into the field "state xy differences." Lines 24-35 take the data in the field "state xy differences" and sums the first two items in line 30, which are the x and y distances from the player's click to each of the states. The results of this simple arithmetic is put into another field - "xy sum" (line 31) - which is then sorted from low to high (line 33). The "winning" state is therefore the one that is found at the top of this field. Line 34 puts the name of this state into the field "target state," thus revealing the identity of the clicked state on the screen.

The reason why I need to sum both the absolute values of x and y is clearly shown if I click on Oklahoma, but a little left of center (just left of the "OK"). This is exactly where I clicked when I captured the screen shot above. The x coordinate of this click could be identifying a bunch of states. In fact, Oklahoma is the sixth state in the list. The margin of error is just too great when using just the x axis. However, when you add the x and y coordinates together, Oklahoma is the clear winner with a sum of 24. Kansas comes in second with a sum of 71, which is not even close.

As always, I use fields to place all of these values so that I can see what is going on with the algorithm even though I know doing so dramatically slows down the processing speed. When I go to integrate this approach into the game's engine, I'll convert the fields into variables just by changing the names. For example, I'll change field "state xy differences" to something like variable "varStateXYDifferences."

Saturday, July 12, 2025

Where Again is Arkansas? A Geography Location Game, Part 1: Initial Design and Build

In the building where I work at the University of Georgia, there is a fairly large map of Georgia hung on a wall in one of the hallways. I often find myself standing and staring in front of it. I do love maps and this one, with an edition date of 1967, is particularly well designed. It seems to strike the right balance of visual appeal and information. It shows all of the counties, cities, and even small communities not commonly found on your mobile phone's map app. I'm particularly fond of checking out all of the 159 counties. Only Texas has more counties than Georgia with 254. Some counties have some amusing names, such as the side-by-side counties of Bacon and Coffee (named after people, not the breakfast items). Some, if not outright controversial, at least raise an eyebrow or two to non-Georgians, such as Jeff Davis county, named after Jefferson Davis, the president of the confederacy during the American Civil War. Of course, quite a few Georgia counties are named after confederate leaders, but naming one after the confederate president seems a bit brash. But, the county was created in 1905 and the era of the Jim Crow south and the narrative of the South's "lost cause" (aka War for Southern Independence or War of Northern Aggression) was in full force. I originally thought the county was named after Davis because he was captured in Georgia after his and his cabinet's escape in 1865 from Richmond, Virginia, but apparently he never touched foot on the land that now occupies the county. (Davis was captured in Irwinville, Georgia.) Anyhow, it is an excellent map and I always enjoy studying it while taking a short break.

I sometimes wonder how well I would do if I were given a quiz on the location of each county. I can generally locate the general region of many of the counties - southwest, far east half way down, etc. - but I know I would fail miserably if 100% accuracy was required. The thought occurred to me that it would be interesting to design a simple game to teach the identification of the counties, but using general, not pinpoint accuracy as the score. And so this project was born.

Rather than focus on the counties of Georgia, I instead settled on USA's 48 contiguous states given that 48 is far fewer than 159 and I suspect learning the location of the "lower 48" would appeal to more people. My apologies to the citizens of the great states of Alaska and Hawaii, but no one seems to have a hard time finding them on a map of the USA, though unfortunately they are usually shown cut away from their natural geography and placed in the general area of northern Mexico. Talk about confusing 4th graders!

The Goal of the Game and How to Measure Success

The goal of the game is simply to locate a given state on a map. Besides being either right or wrong, the game tells you how far off you are in miles if you are wrong as measured from the center point of each state (more about that calculation later). It is this measure of how far off your guess is that I consider to be the most important measure of learning. I've even designed the game so that it chooses just a handful of states (5 is the default for now) for you to locate and the game only ends when you finally identify the location of each. If you are wrong, your score increases by the error mileage plus that state is put back into the queue for you to try again a little later. I keep track of the total number of tries as another measure of success. But, I would consider a low error mileage score with a high number of tries to be evidence of more learning than a high mileage score and a lower number of tries. The reason is that you are demonstrating good awareness of the nation's regions which I take is akin to good estimation skills. For example, knowing that Vermont is somewhere in the New England area and not out west somewhere is what the game is emphasizing.

Creating Helper Apps

Before I could work on the game itself, I needed to build the various elements the game would need to work. Here is a screen shot of a card in my stack that uses several helper apps for these tasks:

Of course, in the final version I will need to delete each state's abbreviation from the map. To create the game, I obviously needed to identify where each state was located on a map. How to do this? One way would be to create individual maps of each state and arrange them jigsaw puzzle-like to form the 48 as a whole. LiveCode has a very useful message handler - MouseWithin - that can detect if the mouse is currently located within the rectangular boundaries of an object. There are also ways to detect collisions between objects on the screen using the intersect function. However, I didn't want to have to create or find 48 separate images of states. I may come back to one of these options later if I'm motivated to refine the game, but for now I'm using a different strategy. I created a helper app to log the screen locations of the approximate center point of each state on a single USA map. 

Thinking Ahead - What If I Need to Move the USA Map to a Different Location on the Screen

Yes, it occurred to me right at the start of the possibility that I would need to move the state map to a different location on the card. I almost never finalize the screen design until the very end of the project. So, logging the screen locations of each state would have to be redone if I moved the USA map. In anticipation of this, I used the center point of the image of the USA map as a calibration point. That is, when I logged the location of each state, I offset the raw screen location by the x and y coordinates of the map's center point. That way, I can move the map to any location on the screen later to line up each state's location perfectly. Here's the code for a button that does the logging:

1:  on mouseup  
2:    wait until the mouseclick  
3:    put the location of image "USA with labels.png" into varLocationUSA  
4:    put (item 1 of the mouseloc - item 1 of varLocationUSA)&comma after field "coordinates"  
5:    put (item 2 of the mouseloc - item 2 of varLocationUSA)&comma after field "coordinates"  
6:  end mouseup   

The image of the USA is titled "USA with labels.png." I put its current location (i.e., the x and y coordinates of its center) in the variable varLocationUSA. I then click on any state and the x and y screen locations of that state are logged by the mouseloc function. I then subtract the x and y coordinates of the map's center point. The resulting x and y of each state is then displayed in the field "coordinates." I then manually typed in the name of the state at the end. The result is a list of the state's coordinates aligned to the map's center point.

Did I Miss Any States?

I obviously had to do this logging of screen coordinates 48 times. In order to make sure I wasn't skipping any states along the way, I created another helper app that compared the names of states I had logged with a list of all 48 states. It then showed me the names of the missing states. Here is the code:

1:  on mouseup  
2:    put empty into the field "missing states"  
3:    put the number of lines in field "all states" into L  
4:    put the number of lines in field "coordinates" into M  
5:      
6:    repeat with i = 1 to L  
7:     put line i of field "all states" into varTarget  
8:     put false into varFoundState  
9:       
10:     repeat with j = 1 to M  
11:       put item 3 of line j of field "coordinates" into varStatesDone  
12:       if varStatesDone = varTarget then  
13:        put true into varFoundState  
14:        exit repeat  
15:       end if  
16:     end repeat  
17:     if varFoundState is true then  
18:       next repeat  
19:     end if  
20:     put line i of field "all states"&return after field "missing states"  
21:    end repeat  
22:      
23:  end mouseup  

Besides the field "coordinates" already mentioned above, the field "all states" has just that, a list of all 48 states. The code compares each line in the field "all states" to the list of states already logged (item 3) in the field "coordinates" (the first two items are the coordinates). I set the variable varStateFound to false by default. If a match is found, varFoundState is set to true, which then triggers a new search with the next item in the field "all states." If no matches are found, varFoundState remains false and that state from the field "all states" is added to the field "missing states." This allowed me to plow through all of the states one-by-one as if I was on some magical car ride, checking now and then to see which states I've missed along the way.

Calculating the Distance Between Any Two States

The task of accurately calculating the distance between any two states is not easy, but it is straightforward. A good approach is to compile the GPS coordinates of the geographic center of each state, then create a distance matrix showing the distance between each state. I also learned about the Haversine formula, which takes into account the curvature of the earth. I decided this was a task perfectly suited for artificial intelligence, so I asked ChatGPT to create such a matrix. It did so, but in kilometers. Of course, despite the rest of the world, we in the USA compute these sorts of distances in miles. So, I asked ChatGPT to do it again, but in miles, and it did so without complaint. (Yes, when it comes to converting to the metric system, the USA is "miles and miles" behind the rest of the world. For some reason, we are fine measuring most soft drinks and wine in liters.) 

I will need to figure out how to efficiently integrate this matrix in my game, but that is a task for the next version of the game.

Appendix

Here are three prompts I used in my conversation with ChatGPT to create the matrix of distances between the states (the first matrix it created was in kilometers):

Calculate a matrix containing the distance between the geographic center coordinates of all of the 48 continental united states using the Haversine formula.

Create a version of this matrix for exporting into Excel.

Create a new version of the matrix in miles that can export to Excel.

Next, I asked ChatGPT for some help with the coding:

Me: How do I import an excel spreadsheet into livecode.

ChatGPT

answer file "Choose a CSV file:" 
if it is empty then exit to top
put it into tFilePath
put URL ("file:" & tFilePath) into tCSVData

Me: Show me an example of how I can find the value of a particular cell in the variable tCSVData and put that value into a variable.

ChatGPT: 

put tCSVData into tData
put line 3 of tData into tLine
put item 2 of tLine into tCellValue
answer "The value in row 3, column 2 is:" && tCellValue

I then put all of this mileage data into a field on a separate card "mileage matrix" (I always prefer fields to variables in the development of a project because it allows me to actually see the data). I then added code that would read the state name from the first item in each identified row and the name of the state in the identified column, then put this information in the field "distance between states". This allowed me to verify that everything was working properly. Below is the code. I'll adapt and integrate this code into the next version of the game.

on mouseup

   put field "mileage matrix" on card "mileage matrix" into varCSV
   put field "dRow" into varRow
   put field "dColumn" into varColumn
   put line varRow of varCSV into tLine //row
   put item varColumn of tLine into tCellValue //column
   put item 1 of line varRow of varCSV into varState1
   put item varColumn of line 1 of varCSV into varState2
   put "The value in row "&varRow&", column "&varColumn&" is:" && tCellValue into line 1 of field
      "distance between states"
   put "This is the distance between these states: "&varState1&" and "&varState2 into line 2 of field
      "distance between states"

end mouseup

Final Note: There is a new version of LiveCode that is AI-enhanced. I don't yet have access to this version of the application.



 


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.