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.