Domino 'All Fives'


(Simon) #1

I have just posted my first Arduboy game up to GitHub, a version of the classic Domino ‘All Fives’.

I am looking for feedback on the game itself (of course) but maybe on ways I can free up some memory to include some sound effects. It is currently hovering at 97% PROGMEM and if I include the sound library it blows the limit.

https://github.com/filmote/DominoesArduboy

More details :

Dominoes ‘All Fives’

All Fives is a popular variant of Dominoes where players the goal of the game is not just to go out, but to make the open ends of the layout add up to 5 (or a multiple of five).

Dominoes Rules

There are many different versions of Dominoes ‘All Fives’. The version I have created is based on the rules that I were taught as a child. Some of the rules can easily be changed by simply altering a constant or two in the code (starting number of tiles or the overall point target, for example) whereas others will require code changes (end of hand scoring or forcing a user to play a bone if they can rather than drawing a bone).

All versions of ‘All Fives’ have the same objective - play the bones in order to score points with the first person to reach a previously agreed target - typically 100 - winning. Bones are played by matching the ‘pips’ on a bone to the open ends of the bones already played. Points are scored if the pips on the end bones that have already been played are a multiple of five. Points can also be scored by playing all of your bones at which point the pip count in your opponent’s hand is added to your score.

The bones are placed face down in the graveyard and shuffled. Each player draws seven tiles and the round begins.

In this version of Dominoes, the human always goes first - from that point on play alternates between the computer and player even between rounds. A player may optionally draw tiles from the graveyard and add to his hand however they must ultimately play a tile to complete his turn. It is possible that no bone can be played from the players hand which would force them to draw a bone from the graveyard. If the graveyard is empty, the player is forced to pass.

The human can play any bone from his hand but typical opening plays include bones that will score points immediately (0:5, 1:4, 2:3, 5:5 or the 6:4). Assume the human plays the 5:5 bone which both scores 10 points due to its ‘pips’ adding to 10 and happens to be a double. The first double played is known as the spinner and as the game progresses forms the centre of a four-way ‘cross’.

The computer will then play a bone. His bone must match the ‘pips’ of the first tile. Let’s assume he plays the 5:3 bone. This does not score any points as the open ends of the tiles add up to 13 points (3 points on the newly placed tile and the original 10 from the first tile). The sum of the open ends is often referred to as the ‘board score’ or ‘board value’.

The human plays again by placing the 4:5 tile on the other side of the original tile. At this point, the board has a value of 7 as the spinner has bones on either side of it and is not considered (hence it is greyed out).

The computer plays the 6:3 tile and scores 10 points. Note that in the image below, the 5:3 tile has been greyed out as it, like the 5:5, plays no part in the game or the scoring.

Play continues with the human playing the 4:4 tile. As it is not the first double played, it behaves like any other bone. Playing the 4:4 results in the score not changing and scoring the player 10 points as well. Note that some versions of ‘All Fives’ would count both ends of the 4:4 to calculate a ‘board score’ of 14.

As both ends of the spinner have been played, the computer can now add tiles from the other two ends. He plays the 5:6 for a ‘board score’ or 16 which results in no score for him.

And play continues until either player has used up all their bones or neither player can play or draw from the boneyard. If a player has emptied their hand, the ‘pips’ from the other player’s hand are tallied, rounded to the nearest 5 and added to the first player’s score. If neither player can play a bone, the difference between the two hands is added to the score of the player whose hand scores the lowest.

Dominoes and a 128x64 pixel screen

In a typical domino game, players add a domino tile (known as a ‘bone’) to an existing one by matching the number of ‘pips’. This results in a long string of dominoes joined end-to-end which would very quickly exceed the available real-estate of the Arduboy screen. This version tackles this issue by removing intermediate tiles and placing them face up in a pile at the right of screen. This allows the player to see what bones he can play against and what bones have been previously played.

Game Play

Game play starts with a basic splash screen.

After pressing the A button, the domino board is shown. The computers score and number of bones in his hand are shown at the top of the screen whereas the human’s score is shown at the bottom. After dealing the bones, the remainder are available for selection on the right-hand side of the screen.

The human plays first. The human’s hand is show at the bottom of the screen with the selected bone highlighted. The player can scroll left to right to locate the bone he wishes to play and select it by clicking the A button. Alternatively, the player can scroll to the graveyard and select a tile to add to his hand.

Control moves to the board where the player must select a position on the board to play. When playing the first tile, only one option is available - the center position. This is selected by clicking the A button again.

The player’s turn is over. The positions on either side of the played tile are now available for the computer to play a bone.

The computer contemplates his next move …

… and plays a bone. Note the number of bones in the computer’s hand has decreased to 6. The animations and dialogues can be skipped simply by pressing the A or B button.

As play continues, bones that are not important to the play are moved to the graveyard and placed face up. In this way, those bones that have been played are still visible.

Once the east and west side of the spinner are played, the north and south positions become available for play.

Tactics

Unlike some variants of Dominoes, ‘All Fives’ is not a pure chance game and there are some tactics involved.
The computer will attempt to (in order) :

  • Play a domino that scores.
  • Play a domino that blocks an end of the play using a bone where this is no other bone that can be played from other player’s hands.
  • Play a domino that reduces the pip count thus preventing other players from scoring highly.

The Code

I have included two major flags in the code as shown below:

#define NO_SOUNDS
#define ANIMATIONS

Altering them to include sounds (by removing the NO_ prefix) or exclude animations (by adding NO_ or some other prefix / suffix) will alter what gets included. As it stands, their are no actual sounds in the application - maybe someone can assist with some appropriate sound effects - but there are animations. Attempting to include both exceeds the available memory - without having even defined any sound effects ! - and will require some additional work to reduce the program size.
Task List

The following is a list of tasks that need to be done, may be done and could possibly be done by myself or with the help of others. I am not precious about the code and would prefer to see it completed than to sit idle …

  • Publish initial code on GitHub
    
  • More testing (obvious)
    
  • Minimise the code in order to enable both animations and sound. Possibilities include compressing the images?
    
  • Adding sound effects.
    
  • Enabling variations of rules through configuration options.
    
  • Creating a compiled version for distribution

Eried's Unofficial Repo :)
(curly) #2

Just a quick thought as I didn’t look too deep into the code but seeing as a domino is just a rectangle with dots all of the dominoes could be from one image (The dot) using horizontal and vertical lines for the sides the code may look a lot more complex but the size would be much smaller if I ever find a steady power source for my computer I’ll play with it a bit


(Simon) #3

Thanks Curly, I have a single image for the blank tile and a number of images for the various pips sequences (1-6). I tried something similar to what you suggested but the code for rendering the various pips in both Normal and reverse images (when a bone is selected) quickly outweighed thesavings of having an array of images (5x8) pixels that I simply render. I think I am looking for a LOT more saving than images to free up enough space for some sounds !


(Scott) #4

The ArduboyTones library produces less code than the ArduboyPlaytune library or any of the other Arduboy targeted sound libraries. Switching to it gives you the best chance of squeezing in simple sound effects.


(Scott) #5

What version of the Arduino IDE are you using to compile? With IDE V1.8.2 your current GitHub code compiles to 90%.

Using the ArduboyTones library takes it up to 92% with the single tone that you play in Splash.ino (I didn’t try it with the ArduboyPlaytune library).


(Scott) #6

Using either of the two digitalWriteRGB() functions instead of setRGBled() will save about 800 bytes.

It won’t save much but you should use arduboy.delayShort() everywhere instead of delay(), for consistency.


(Erwin) #7

Looks quite professionally made :slight_smile:

I uploaded your game to http://arduboy.ried.cl/


(Simon) #8

Thanks @MLXXXp, I have done everything you suggested with the following results:

Compiled on Ard 1.6.9              27,834 bytes
Compiled on Ard 1.8.3              25,872 bytes
Change to digitalWriteRGB()        25,100 bytes
Change to delayShort()             25,080 bytes
Include ArduboyPlaytune.h          26,782 bytes

So after a few modifications, I am able to get the sound library in and save over a 1K !

As you can see, I was on a backward level of the Arduino environment but this did have one good feature … the #includes where relative to the project directory when using #include “whatever”. When I upgraded to 1.8.3, the #includes became relative to the library directory which is just a pain. I moved your libraries into the sketch directory to overcome this.

I am not sure if you noticed, but I added a few methods to your classes to handle the vertical spacing of text and to allow text to wrap between a nominated set of X coordinates. - both left and right I am not sure if this is something you would want to include in your base code - maybe it bloats the code for some marginal use cases.

Cheers, Simon

  /** \brief													
   * Set or disable text wrap mode and wrapping positions.
   *
   * \param w `true` enables text wrap mode. `false` disables it.
   * \param l left hand X position to wrap text to.  Default is 0.
   * \param r right hand X position to wrap text at.  Default is WIDTH
   *
   * \details
   * Text wrap mode is enabled by specifying `true`. In wrap mode, the text
   * cursor will be moved to the start of the next line (based on the current
   * text size) if the following character wouldn't fit entirely at the end of
   * the current line (where the rightmost column value is defined by r).  
   * Wrapping text will reset the X coordinate to l.

   * If wrap mode is disabled, characters will continue to be written to the
   * same line. A character at the right edge of the screen may only be
   * partially displayed and additional characters will be off screen.
   */
   void setTextWrap(bool w, int16_t l, int16_t r);    	       
  
  /** \brief													
   * Set wrapping positions, left and right.
   *
   * \param l left hand X position to wrap text to.  Default is 0.
   * \param r right hand X position to wrap text at.  Default is WIDTH
   *
   * \details
   * Set the left most and right most X positions for wrapping text.
   * Only effective if setTextWrap / textWrap is true.
   */ 
  void setTextWrapPositions(int16_t l, int16_t r);    	

  /** \brief													
   * Set vertical line spacing.
   *
   * \param y amount to increase y coordinate on wrap, default is DEFAULT_VERT_SPACING
   *
   * \details
   * Sets the value by which to increase the text cursor Y value when
   * wrapping text or rendering a line feed.  Only effective if setTextWrap /
   * textWrap is true.
   */
  void setTextVertSpacing(int16_t y);

(Simon) #9

Thanks @eried … however, I have made and am planning to make some changes to the app including adding some sound effects now that @MLXXXp has helped free up some space. What is the process for alerting you to a new version?

When I make my own .arduboy file, will you link to it instead - this will allow updates to automatically flow through.


(Simon) #10

I have posted up a new version with some really basic sounds. What would be lovely for schmuks like me is a collection of ‘open source’ simple sounds like a win sound, a lose sound, crash, gun fire, game introduction … Anyone want to start a library of these? I am guessing a talented musician (not me) could whip up a few hundred of these before bedtime.


(Erwin) #11

If you use github you just need to create a pull request, otherwise with the hex file takes me like ten seconds to update the file


(Scott) #12

Yes, I had to do this but forgot to mention it.


I think it’s too much of an edge case to include it in the standard library, but thanks for offering it. Text wrap at the right edge (back to the left margin) is already supported, which I think is enough for the general population.

I would suggest that instead of including an entire copy of the Arduoby2 library, you inherit the Arduboy2 class as a base for your own class and then just add or override the few variables and functions that you need to.


Unless you plan to use all the space you’ve recovered, I suggest you change arduboy.boot() to arduboy.begin(). This will give you all the standard Arduboy boot features, such as the logo and flashlight. If you get to the point that you’re becoming short on space in the future, you could then switch back to arduboy.boot() and start selectively eliminating boot features, but it’s nice to include them while you have the space.


You should change the arduboy.audio.on() in setup() to arduboy.audio.begin(). This will honour the user’s desired mute state stored in system EEPROM instead of always starting with sound enabled. If you switch to arduboy.begin(), as suggested above, you don’t even need to call arduboy.audio.begin() since arduboy.begin() does this.


You can eliminate your sounds_on flag and just use arudboy.audio.enabled() to test the current mute state.


(Simon) #13

Thanks for the feedback @MLXXXp - I was planning to research how others are sharing EEPROM settings across apps - especially for the sound - and you have answered it before I even asked! Likewise the sounds_on flag which was a dirty solution to the problem to begin with.

The suggestion to override the Arduboy class is a good one - I develop on C# and Java normally, so you would have thought I would have done that already.

Cheers, Simon


(Kevin) #14

Holy wow! I only just learned how to play Dominoes this year, and now it can fit in my wallet! :wink:


(Simon) #15

Thanks @bateske … I love Dominoes and this was an exercise on how to fit a sprawling game into a 128 x 64 screen!