Start a new topic

Creating a Yahtzee Game within Nextion Logic

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
(139 Bytes)
h0.png
(138 Bytes)
d6.png
(287 Bytes)
d5.png
(309 Bytes)
d4.png
(280 Bytes)
d3.png
(283 Bytes)
d2.png
(262 Bytes)
d1.png
(237 Bytes)

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
(124 KB)

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 40x40 for each of the dice placed carefully over the dice spots

- five picture components for 40x6 for each of the holds placed carefully over the status spots

We will add to this one 72x36 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.


So, it is soon about time to include an HMI file, 

I am using the 3.5" 480x320 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

HMI
(457 KB)

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

}

Ahh, so we just talked about holding some dice back between throws.

So lets quickly see what code we might need to do this.


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.

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

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.


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

  }

}

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

  }

}

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

  }

}

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

  }

}




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


hmi

1 person likes this
Login or Signup to post a comment