This thread will show how to create the Yahtzee dice game and keep the game scoring all within the Nextion logic without the need for an external microprocessor.  As we go through the process of creating this game, I will deal with a few techniques that may be useful in your projects.   A special shout out to Steve (indev2) for tripping across the b[.id] component array and to Gerry Kropf for his short and sweet val=1-val boolean toggle.

https://en.wikipedia.org/wiki/Yahtzee

So as this is a dice game, the first thing we will need is 5 dice.
And perhaps two status indicators

k7G30eN0zzT43DhtFN2AE9a2yPnkohSp_g.png

h1.png h1.png(139 Bytes)

h0.png h0.png(138 Bytes)

d6.png d6.png(287 Bytes)

d5.png d5.png(309 Bytes)

d4.png d4.png(280 Bytes

d3.png d3.png(283 Bytes)

d2.png d2.png(262 Bytes)

d1.png d1.png(237 Bytes)

Step 2

So the next thing we need is a score pad for our game. This will require some Labels.
  • On the Upper half, we need Ones, Twos, Threes, Fours, Fives, Sixes.
  • We will need a Subtotal to see if we have hit 63 and score the 35 bonus points
  • and we will need an Upper to hold our total for the upper half.  On the Lower half, we need
  • 3 of a Kind, 4 of a Kind, Full House, Small Straight, Large Straight, Yahtzee, and Chance
  • A Lower Total, an Upper Total, and a Final Score label.

Now we could use Text Components for these 19 Labels.
We would also need 19 Number Labels for the Number that goes to each of these labels

  • these will be set as .xcen Right, and .ycen Up, .isbr False, and .h of 24 and .w of 36
  • these should allow the background to bleed through so .sta is set to crop image.
  • don’t forget to set the .picc to our background (in the HMI this is pic id 6).

We need a pad for our five dice, 5 status if we are to hold that die between rolls.

  • we will need a Yahtzee game name, which could be a picture off to the left
  • we also have two buttons (or touch Hotspots): one to throw the dice
  • and the other for our roll counter (which needs another smaller number component.

But many of these will not need to change, so they can be included into the background

  • remember to set the page0 .sta to image and .pic to our pic id 6.

foSl0pAtoFzUxPDFz9GYDLXNb9In1Rz1uw.png

felt2.png

felt2.png (124 KB)

Step 3

Now that we have a game background that replaced many labels Text Components
I’ll review the components we are left with

  • a small 16px number component for our Rolls remaining
  • one 24px number components for the Final Score
  • two columns of nine 24px number components (18 in total)
  • five picture components for 40×40 for each of the dice placed carefully over the dice spots
  • five picture components for 40×6 for each of the holds placed carefully over the status spots

We will add to this one 72×36 hotspot component to cover the Throw

We will also add some variables to get started with for holding the game logic

  • five variable components for dv1,dv2,dv3,dv4,dv5 for our dice values
  • five variable components for dh1,dh2,dh3,dh4,dh5 for our dice hold (0 no, and 1 hold)
  • one variable component tr for our throw counter.

And from time to time … I maybe toss in another variable component or two.

Step 4

So, it is soon about time to include an HMI file,
I am using the 3.5″ 480×320 Enhanced Nextion model for this project <order it here>
Attached is the yahtzee.HMI file created with the Nextion Editor version 0.40
You can download the HMI and then we can start to get on with the coding.

6JtdWe9dpjE_Y2Q_68CVroG47XPA5-35BA.png

Step 5

The first thing to take care of with rolling our dice will be to have them roll somewhat random. For this we need to add a random factor to have the dice roll.  We also want to initialize the number of dice rolls to three.  So in the page0 Page PreInitialize Event we add

randset 0,65535
tr.val=3

So, although it may be true that for a dice roll we need only six values, 0 to 5,

  • we could have use randset 0,5, and then p3.pic=rand

we can actually get a better randomness from a modulo of the larger value

  • so randset 0,65535 and then p3.pic=rand%6

This also requires that out dice pictures are id 0 for dice 1, id 1 for dice 2 .. id 5 for dice 6
So the ordering of our Pictures is intentional and on purpose,
Changing their order and this will fail, so the obvious would be – do not change their order.

p0.pic=rand%6
p1.pic=rand%6
p2.pic=rand%6
p3.pic=rand%6
p4.pic=rand%6

Now we add this code info the m0 hostpot for throw, but we also want to add animation. Here we will encapsulate the rolling in a 11 step roll and have it slow down towards the end. To do this we will ask the help of the sys2 system variable and the command delay

for(sys2=0;sys2<=10;sys2++)
{
// dice rolls
delay=25*sys2
}
// capture the dice values once the dice stop rolling
// decrement the throw counter

Now although each of our picture values could be accomplished with a series of

dv1.val=p0.pic+1
dv5.val=p4.pic+1

We can also shorten this in a for loop using the b[ ] component array with
This technique uses the format of  b[.id+offset].attribute

for(sys2=0;sys2<=4;sys2)
{
b[sys2+30].val=b[sys2+15].pic+1
}

A little work could also shorten the previous code to roll the dice … but another time.

We also want to ensure that we only throw the dice only if we have a throw remaining
so we need to encapsulate this in an if(tr.val>0) statement

Also we must only roll a die if it is not being held by the player
so each die roll must check its dhX.val before rolling the die.

We finally must add a new variable component mp for make play
which will be set to 1 once a play is valid and cleared to 0 when the play has
been made to prevent multiple scoring using the same play

so our code inside the touch m0 hotspot Pressed Event should  now look like:

if(tr.val>0)
{
  for(sys2=0;sys2<=10;sys2++)
  {
     for(sys1=0;sys1<=4;sys1++)
     {
       if(b[sys1+35].val==0)
       {
          b[sys1+15].pic=rand%6
       }
     }
     delay=25*sys2
  }
  for(sys2=0;sys2<=4;sys2)
  {
    b[30+sys2].val=b[15+sys2].pic+1
  }
  mp.val=1
  tr.val=tr.val-1
  n9.val=tr.val
}

 Step 6

First in order to hold we will touch the dice to do so,
– so this will go in the Pressed Event of each die

Second, it can only make sense to hold a die if there is a roll remaining
– and then only once the dice have had an original throw.

This already implies two nested if statements, and within this we need to:

  • toggle the die hold variable dhX.val
  • set the hold status picture under each corresponding die.

Since we have a 0 or 1 value for the toggle we set off as pic id 7 and on as pic id 8
– this makes the status equal to the hold value + the pic id offset to our status images

p5.pic=dh1.val+7

Credit goes to Gerry Kropf for his shortened binary toggle routine of value=1-value. so

dh1.val=1-dh1.val

This makes the dice hold (die picture p0 Press Event) events

if(tr.val>0)
{
  if(tr.val<3)
  {
     dh1.val=1-dh1.val
     p5.pic=dh1.val+7
   }
}

This will keep the nested if statements, but change the inner for the next four dice
Second die: dh2.val=1-dh2.val and p6.pic=dh2.val+7
Third die: dh3.val=1-dh3.val and p7.pic=dh3.val+7
Fourth die: dh4.val=1-dh4.val and p8.pic=dh4.val+7
and the final fifth die: dh5.val=1-dh5.val and p9.pic=dh5.val+7

Now at this time we will also code the hotspot m1 Pressed Event

  • we need to create a new hotspot m1
  • set the .w attribute to 5 and the .h attribute to 5
  • we will set this out of the way, perhaps to the left of the Ones label
  • in here we will tally the scores.

The Upper Subtotal includes n0 (.id of 1) through n5 (.id of 6)
– this needs to be placed in n6.val

If n6.val totals 63 or above, n7.val  (.id of 7) needs to be set to 35, otherwise it is 0
Then n6.val + n7.val gets written in both n8.val and n18.val

The Lower Subtotal includes n10 (.id of 8) through n16 (.id of 14)
– this needs to be placed in n17.val

Finally if the game is over, if plays remaining == 0 then

  • the values from n17.val and n18.val can be combined
  • this will be placed in n19.val

But since this Final Score isn’t calculated until the game is over

  • we need to create a new variable component pr for plays remaining
  • In the page Preinitialize Event we set this equal to 13  pr.val=13

This will allow our m1 hotspot Pressed Event to contain the code:

sys1=0
sys2=0
for(sys0=1;sys0<=6;sys0++)
{
  sys1=sys1+b[sys0].val
}
n6.val=sys1
if(sys1>=63)
{
   n7.val=35
   sys1=sys1+35
}
n8.val=sys1
n18.val=sys1
for(sys0=8;sys0<=14;sys0++)
{
  sys2=sys2+b[sys0].val
}
n17.val=sys2
if(pr.val==0)
{
  n19.val=n17.val+n18.val
  tr.val=0 // ensure there are no more throws remaining as game ended.
  n9.val=tr.val
  n19.pco=2016
}

This m1 hotspot Pressed Event will be called with the command

click m1,1

This will be done within each of the 13 scoring opportunities … that will be next.

Step 7

 Now if we test our HMI design so far, we can roll up to three roll per play
and we can hold the dice as desired when it is permitted
and we have the code to tally our scores …
But how do we set each of our plays? And ensure it is only used once?
In our page PreInitialization Event we are going to initialize this
By setting the number color .pco attribute to the color 2016
  • we will now know if that play selection is used or unused.
  • we will need to set this when the game starts
  • we will need to set to another color when using the play option

We will also set the color of the Final Score n19 to know the game is still in play

So we append the following to the page PreInitialization Event

for(sys0=1;sys0<=6;sys0++)
{
   b[sys0].pco=2016
}
for(sys0=8;sys0<=14;sys0++)
{
   b[sys0].pco=2016
}
n19.pco=65520

Step 8

So now we are going to deal with scoring our upper 1’s 2’s 3’s 4’s 5’s and 6’s
These are going to be the easiest, all we need to do is

  • check that each die has the play value and if so add it in
  • put that in the play value
  • change the color from 2016 to 65520
  • set the throws remaining back to 3
  • reduce the plays remaining by one
  • update n9 with the rolls remaining
  • clear the dice holders
  • call our click m1,1 to re-tally and update the score.

So our code in the n0 Press Event will look something like

if(mp.val==1)
{
  (n0.pco==2016)
  {
     sys1=0
     for(sys0=0;sys0<=4;sys0++)
     {
       if(b[sys0+30].val==1)
       {
         sys1=sys1+b[sys0+30].val
        }
     }
     n0.val=sys1
     n0.pco=65520
     tr.val=3
     n9.val=tr.val
     pr.val=pr.val-1
     mp.val=0
     for(sys0=0;sys0<=4;sys0++)
     {
        b[sys0+35].val=0
        b[sys0+25].pic=7
     }
     click m1,1
  }
}

This will follow the same pattern for n1 and the 2’s, n2 and the 3’s etc
So I will let you fill it in for the 2’s, 3’s, 4’s, 5’s and 6’s.

Step 9

So I am going to assume you have the upper half completed,
– I will assume you have played 1/2 of it and seen if your bonus works

So the next two easiest to do is Chance and Yahtzee.
Chance is very much like the ones, except all dice are counted
– no need for the if ==1 statement

So Chance n16 Press Event should look like:

if(mp.val==1)
{
  if(n16.pco==2016)
  {
    sys1=0
    for(sys0=0;sys0<=4;sys0++)
    {
      sys1=sys1+b[sys0+30].val
    }
    n16.val=sys1
    n16.pco=65520
    tr.val=3
    n9.val=tr.val
    pr.val=pr.val-1
    mp.val=0
    for(sys0=0;sys0<=4;sys0++)
    {
       b[sys0+35].val=0
       b[sys0+25].pic=7
    }
    click m1,1
  }
}

But Yahtzee needs to make sure that all dice are the same
– so here we will test if they are all the same
– if the are all the same, regardless of the value, award 50 points in n15
So this is most easily accomplished in a large nested if

And Yahtzee n15 Press Event will look like:

if(mp.val==1)
{
  if(n15.pco==2016)
  {
    n15.val=0
    if(dv2.val==dv1.val)
    {
      if(dv3.val==dv2.val)
      {
        if(dv4.val==dv3.val)
        {
          if(dv5.val==dv4.val)
          {
            n15.val=50
          }
        }
      }
    }
    n15.pco=65520
    tr.val=3
    n9.val=tr.val
    pr.val=pr.val-1
    mp.val=0
    for(sys0=0;sys0<=4;sys0++)
    {
      b[sys0+35].val=0
      b[sys0+25].pic=7
    }
    click m1,1
  }
}

Step 10

Okay and now we will deal with the two straights.
For the large straight, the user needs 1-2-3-4-5 or 2-3-4-5-6 and
For the small straight, the user needs 1-2-3-4 or 2-3-4-5, or 3-4-5-6.
The large straight is a bit easier as all dice need to be used.

For this I will assign a twos complement to die value and then check the sum
We will need to create a new variable component nv for this to work
nv.val can only be one of two possible sums to score the 40 points – 124 or 248

So the n14 Press Event should look something like

if(mp.val==1)
{
  if(n14.pco==2016)
  {
    sys2=2
    n14.val=0
    nv.val=0
    for(sys0=1;sys0<=6;sys0++)
    {
       sys2=sys2*2
       for(sys1=0;sys1<=4;sys1++)
       {
          if(b[sys1+30].val==sys0)
          {
             nv.val=nv.val+sys2
          }
       }
    }
    if(nv.val==124)
    {
      n14.val=40
    }
    if(nv.val==248)
    {
      n14.val=40
    }
    n14.pco=65520
    tr.val=3
    n9.val=tr.val
    pr.val=pr.val-1
    mp.val=0
    for(sys0=0;sys0<=4;sys0++)
    {
       b[sys0+35].val=0
       b[sys0+25].pic=7
    }
    click m1,1
  }
}

Now likewise the small straight is valid only for three counts 60,120, and 240.
But here we must only use 4 dice to arrive at the sum.
So I will create another variable component dr for the die removed.
Again if one of these three values are arrived at, then this will score 30 points

So the n13 Press Event should look something like:

if(mp.val==1)
{
  if(n13.pco==2016)
  {
    n13.val=0
    for(dr.val=0;dr.val<=4;dr.val++)
    {
      sys2=2
      nv.val=0
      for(sys0=1;sys0<=6;sys0++)
      {
        sys2=sys2*2
         for(sys1=0;sys1<=4;sys1++)
         {
            if(b[sys1+30].val==sys0)
            {
               if(dr.val!=sys1)
               {
                  nv.val=nv.val+sys2
               }
            }
         }
      }
     if(nv.val==60)
     {
        n13.val=30
     }
     if(nv.val==120)
     {
        n13.val=30
      }
      if(nv.val==240)
      {
        n13.val=30
      }
    }
    n13.pco=65520
    tr.val=3
    n9.val=tr.val
    pr.val=pr.val-1
    mp.val=0
    for(sys0=0;sys0<=4;sys0++)
    {
       b[sys0+35].val=0
       b[sys0+25].pic=7
    }
    click m1,1
  }
}

Step 11

Now we are off to the 3 of a Kind.
Here in order to score the points shown on all five dice, the user needs three to be the same.
So I am going to simply check all five dice against the first, the second and the third.
For this I will initialize my dr variable to 0 as I won’t be using it otherwise
and I if I get a three count, I will tally all five dice and assign it into n10.
We will keep all the other maintenance in mind as well

So the n10 Press Event will look something like:

if(mp.val==1)
{
  if(n10.pco==2016)
  {
    n10.val=0
    dr.val=0
     for(sys0=0;sys0<=2;sys0++)
     {
        sys2=0
        for(sys1=0;sys1<=4;sys1++)
        {
           if(b[sys1+30].val==b[sys0+30].val)
           {
              sys2=sys2+1
           }
        }
        if(sys2>=3)
        {
           dr.val=1
        }
     }
     if(dr.val==1)
     {
       sys2=0
       for(sys0=0;sys0<=4;sys0++)
       {
         sys2=sys2+b[sys0+30].val
       }
       n10.val=sys2
     }
      n10.pco=65520
      tr.val=3
      n9.val=tr.val
      pr.val=pr.val-1
      mp.val=0
      for(sys0=0;sys0<=4;sys0++)
      {
         b[sys0+35].val=0
         b[sys0+25].pic=7
      }
     click m1,1
   }
}

That worked out short than I expected.  So the 4 of a Kind will be the same length.
We merely only check the dice against the first and the second and set it to >=4.
Swap out all n10’s for n11’s and the n11 Press Event will look something like:

if(mp.val==1)
{
  if(n11.pco==2016)
  {
     n11.val=0
     dr.val=0
     for(sys0=0;sys0<=1;sys0++)
     {
        sys2=0
        for(sys1=0;sys1<=4;sys1++)
        {
           if(b[sys1+30].val==b[sys0+30].val)
           {
              sys2=sys2+1
           }
        }
        if(sys2>=4)
        {
          dr.val=1
        }
     }
     if(dr.val==1)
     {
       sys2=0
       for(sys0=0;sys0<=4;sys0++)
       {
          sys2=sys2+b[sys0+30].val
       }
      n11.val=sys2
     }
     n11.pco=65520
     tr.val=3
     n9.val=tr.val
     pr.val=pr.val-1
     mp.val=0
     for(sys0=0;sys0<=4;sys0++)
     {
       b[sys0+35].val=0
       b[sys0+25].pic=7
     }
     click m1,1
  }
}

Step 12

So one final scoring opportunity left to code.  Yes!  The Full House at n12.
Can you figure this one out … awesome … and that should wrap up our Yahtzee Game.
… just kidding folks.  So let’s get started on this one.

The Full House is going to require 3 of a Kind + a Pair, or 5 of a Kind as 3+2 is 5.  It would count. (I would be inclined to use my 5 of a Kind for a Yahtzee at 50 points than for a Full House at 25 points)  But within the five dice values, there are either only one (with 5 of a Kind) or two (with 3 and the pair).  So lets see how we can accomplish this one.

I will probably count the dice that have the same value as the first die, and at the same time use an if statement to find the die position of the second value, and do a count of that value.  Remember there is only one or two dice values in a 3+2 to be awarded the 25 points.

So the code for n12 Press Event should look something like this:

if(mp.val==1)
{
  if(n12.pco==2016)
  {
      n12.val=0
      sys1=0
      sys2=0
      dr.val=0
      for(sys0=0;sys0<=4;sys0++)
      {
        if(b[sys0+30].val==b[30].val)
        {
          sys1=sys1+1
         }else
        {
           nv.val=b[sys0+30].val
           dr.val=1
         }
      }
      if(dr.val==1)
      {
         for(sys0=0;sys0<=4;sys0++)
         {
            if(b[sys0+30].val==nv.val)
           {
             sys2=sys2+1
           }
         }
      }
      if(sys1==5)
       {
         n12.val=25
       }else
      {
         if(sys1==3)
         {
            if(sys2==2)
            {
               n12.val=25
            }
         }
         if(sys1==2)
         {
            if(sys2==3)
            {
               n12.val=25
            }
         }
      }
      n12.pco=65520
      tr.val=3
      n9.val=tr.val
      pr.val=pr.val-1
      mp.val=0
      for(sys0=0;sys0<=4;sys0++)
      {
         b[sys0+35].val=0
         b[sys0+25].pic=7
      }
      click m1,1
  }
}

Final Step

Now if we run what we have accomplished so far, we get a working game of Yahtzee.
So the question becomes, how do we start a new game once the game has ended.
If we place the page 0 command in the n19 Press Event, the game will restart.
But to ensure that this doesn’t occur mid-game …
– check to ensure the plays remaining is 0

So n19 Press Event looks like:

if(pr.val==0)
{
  page 0
}

And there we have it … a working Yahtzee Game all within the Nextion Logic

WwCqBBKKf7toiH5yRRHxkJovOGGzwLhnPg.png

I hope this has been a learning experience and Enjoy,

Patrick GE Martin