- Background
- What is Pocket Casino?
- Hardware Connections
- The Software
- Project Structure
- hardware_connections.h
- rng.h/.c
- buttons.h/.c
- buzzer.h/.c
- What is the purpose of select_prescalar_and_set_ocr()? Usually the clock prescalar is configured as part of the timer configuration. How often is it changed?
- What’s the relationship between clock prescalar and frequency? Do we have to ensure the PWM frequency is set to a specific value/range?
- Crash course on array-to-pointer decay
- main.c
- ui.h/.c
- Showcase
- FAQ
- 1. How does the user "scroll" through the menu using the two buttons?
- 2. In slots, how are the custom glyphs generated?
- 3. There four separate files for each game. Each game requires updating the LCD. Should this responsibility fall on each game file or a different file like a LCD manager file?
- 4. What’s the complete list of icons that can appear in Slots?
- 5. There are 5^3 = 125 unique combinations for Slots? What’s the probability of a jackpot?
Background
Do you have a gambling addiction?
Do you wish to experience the thrill of the casino anywhere, anytime?
Well, now you can with Pocket Casino!
What is Pocket Casino?
All it takes to create the game is to pair an LCD with 2 buttons and a buzzer. It’s amazing how we can organize these components and with an idea develop a functional product.
Pocket Casino comes with 4 adrenaline-inducing, addictive games designed to waste your time keep you hooked for hours. Some are chance-based whilst others test your skill. All the games are easy to learn and can be played in quick rounds, encouraging repeated gameplay.
- Slots
- Coin Flip
- Hi or Low
- Dice (Craps)
I’ve gone to great lengths to recreate the authentic casino experience. Pocket Casino has a fully-functional betting system, pay-out system and addictive tunes.
What’s missing?
- Store: Pocket Casino has a funds-earning system however, there is no store where you can redeem them. This makes the accumulation of funds useless and destroys any incentive to keep playing. Pocket Casino v2 would add a store where you can cash in your earnings.
- More games: Roulette, Blackjack, poker. However, a different display may need to be used as 2*16 maybe insufficient for resolution.
- Hidden Treasure mini-game for slots: If the reels land on a unique combination, the user triggers a hidden mini-game. 3 treasure boxes appear on the 3 reels. The user navigates using the two buttons to open each one.
- Free spins: To play a game, requires a certain amount of funds. The firmware can be expanded so each instance of a game has a random chance to award free spins.
- Advanced display: Modern slot machines use TFT displays, delivering a richer and more immersive experience.
- In Dice, the game doesn’t depict what numbers the two die landed on. Adding this would be nice.
- Unique rolls: In real-life craps, each roll has a name. Recreating this would make for a more realistic and engaging gameplay experience.
- Additional betting options: In Hi or Low, the player doesn’t guess whether the card is black or red or if it is odd or even. Adding these options would add spice to the gameplay.
- Adding the Joker card: Code in a random chance for a Joker card to appear in Hi or Low. The event associated with this is unclear. The most unoriginal idea would be a bonus payout.
- Comprehensive Statistics: Player can see amount of games played for each game, amount earnt, amount lost, how many times they’ve hit the jackpot in Slot, how many times they landed on each die combination in Craps etc. This feature has lots of potential.
- EEPROM: Store high-score and credits in EEPROM, allowing the user to return for another game session.
1. Slots
The LCD presents three reels that depict different icons By pressing the button, you spin the wheel. The player earns a payout depending on how many matching icons the reels land on.
The higher the bet, the higher the payout. No risk no reward baby!
The icons on the 3 reels rapidly change to emulate spinning the wheel. A tune from the buzzer dramatizes this event.
There are five possible glyphs stored in SYMBOLS. Therefore, given 3 reels there are 5^3 = 125 unique combinations of icons the user can land on.
static const uint8_t SYMBOLS[REEL_SYMBOLS] =
{
GLYPH_CHERRY, /* 0 */
GLYPH_BELL, /* 1 */
GLYPH_SEVEN, /* 2 */
GLYPH_COIN_H, /* 3 */
GLYPH_DICE_PIP /* 4 → maps to CGRAM slot 7 */
};
The user can bet in increments: 1, 5, 10, 25. The reels spin for 800 / 1100 / 1400 ms before stopping one by one.
The Payout Table
| Result | Payout |
|---|---|
| Three Sevens | 50× bet (JACKPOT!) |
| Three-of-a-kind (any) | 10× bet |
| Two-of-a-kind | 2× bet |
| No match | 0 (lose bet) |
2. Coin Flip
Coin Flip. Predict Heads or Tails and watch in anticipation which side the digital coin lands on. The LCD simulates the flipping of the coin.
3. Hi or Low
This game can be understood by its name alone. The player receives a card. The house draws a random card from a deck of cards. The player takes a stab in the dark, guessing predicting if the random card is higher or lower than the player’s existing card.
The player doesn’t guess whether the card is black/red or if it’s odd/even. This has been added as a future feature.
Theory
A standard card deck is composed of numbers 1 (Ace) – 10, Jack (11), Queen (12) and King (13). This adds up to 13 cards. Each card has a variant for Club, Diamond, Heart and Spade. Thus, a standard card deck has 13*4 = 52 unique cards.
4. Dice (Craps)
Predict whether a dice roll of two dice will be over, under, equal or a double.
Payout Table
| Target | Win Condition | Payout |
|---|---|---|
| DOUBLES | Both dice same | 5× bet |
| OVER 7 | Sum > 7 | 2× bet |
| UNDER 7 | Sum < 7 | 2× bet |
| EXACT 7 | Sum = 7 | 4× bet |
When two 6-sided die are rolled, there are 21 unique combinations, disregarding same outcomes (e.g. [1,2] and [2,1] are unique but the summation is identical. This is disregard).
In real-life craps, these combinations have names such as Snake Eyes, Hard Four and Midnight. The full list is below. Recreating this would make for a more realistic and engaging gameplay experience.
The mathematics of Craps
(I’ll entertain myself here). I mentioned there are 21 unique combinations. However, there are 6*6 = 36 possible combinations. In this case, [2, 6] != [6, 2]. The rarest combination is [1, 1] and [6, 6] with a 1/36 or 2.76% chance. This is because this combination isn’t bidirectional. For example, to roll a sum of 3, you can do [2, 1] or [1, 2].
The summation of a dice roll varies between 2 – 12. 7 is the most common with a 16.67% chance.
Hardware Connections
The schematic is available at my GitHub repository.
- LCD configured in 4-bit mode
- 2 buttons provide the navigation for the menu. Software, internal pullups enabled
- Buzzer provides audio feedback and SFX according to gameplay events.
The Software
Full source code is located in my GitHub repository.
Project Structure
pocket_casino/
├── Makefile avr-gcc + avrdude build system
├── README.md
├── hardware_connections.h all pin assignments
├── docs/
│ └── state_machine.md Mermaid FSM diagrams
└── src/
├── main.c init + superloop
├── buttons.h / .c PCINT1-driven debounce, long-press, event queue
├── buzzer.h / .c Timer2 CTC tone generator + PROGMEM SFX
├── rng.h / .c RNG Engine
├── ui.h / .c menu FSM, CGRAM loader, draw helpers
└── games/
├── slots.h / .c
├── coinflip.h / .c
├── higherlower.h/ .c
└── dice.h / .c
hardware_connections.h
Standard stuff. Although, LCD connections are absent. Their established in the LCD Driver.
rng.h/.c
A floating ADC pin provides random readings, powering the PRNG. Since all of the games are largely chance-based, this is important.
buttons.h/.c
Initializes the two buttons as digital inputs, pullups enabled with pin-change interrupts. As part of apt embedded architecture, the buttons are interrupt-driven rather than polling-based.
buttons_poll()
PCINT_vect() triggers when either button is pressed. We update the state of the buttons.
All button information is captured in btn_state_t. We store two instances of it in s_btn[] for the two buttons.
typedef struct
{
uint8_t pressed; /* 1 while button is currently down */
uint8_t long_fired; /* 1 once the long-press event is sent */
uint32_t press_time; /* g_ms_tick snapshot when press began */
uint32_t last_change; /* g_ms_tick of last valid transition */
} btn_state_t;
How are able to distinguish via short and long presses? How is this implemented in firmware? Via a timer? Is the pin connected to a PWM-compatible pin?
Both pins are configured as pin-change interrupts. A long press is defined as anything greater than 800ms, #define LONG_PRESS_MS 800U. PCINT_vect() determines what button is pressed and buttons_poll() distinguishes between a short and long press by measuring how long the button is pressed.
static void queue_push(btn_event_t e)
{
uint8_t next = (uint8_t)((s_tail + 1U) % QUEUE_SIZE);
if (next != s_head) /* drop on full — never blocks ISR */
{
s_queue[s_tail] = e;
s_tail = next;
}
}
What’s the purpose of buttons::queue_push()? Why does it use a ring buffer?
This function records a button event in the event that the firmware might not be able to process a button input as it might be updating the LCD. The event is stored in a ring buffer. This allows the firmware to process the request at a later time and allows the ISR to accept new button events.
A ring buffer is used as it allows us to store events in a same block of memory. We can overwrite old entries with new ones. This avoids dynamic memory allocation.
buzzer.h/.c
Buzzer is connected to PB3 which is multiplexed with OC2A. The firmware outputs a PWM signal which we interpret as sound.
A single musical note is composed of two elements: A frequency and a duration.
typedef struct
{
uint16_t hz;
uint16_t ms;
} note_t;
The notes for certain SFX (Win, Lose, Jackpot and reel tick) are stored in PROGMEM as an array. One for each SFX.
What is the purpose of select_prescalar_and_set_ocr()? Usually the clock prescalar is configured as part of the timer configuration. How often is it changed?
The buzzer uses Timer 2 in CTC mode to output PWM signals on PB3.buzzer_tone() invokes the function. The clock prescalar is changed according to the frequency.
What’s the relationship between clock prescalar and frequency? Do we have to ensure the PWM frequency is set to a specific value/range?
It is an inverse relationship: a larger prescaler slows the timer down, which is needed to produce lower-pitched tones, while a smaller prescaler is required to reach higher-pitched ones. The only concern is to ensure the frequency falls within the audible human hearing range.
Ultimately, this function to dynamically change the sound, elevating the audio feedback of Pocket Casino.
buzzer_play_melody() loads the first note from PROGMEM and delegates the actual looping of the note_t to buzzer_update().
SFX_REEL_TICK[] is an array of note_t‘s which is a struct. buzzer_play_melody() accepts a const pointer to note_t rather than an array of note_t‘s. However, we pass it an array and the function doesn’t break. This is due to array-to-pointer decay. The function can accept a pointer to note_t. If we pass in an array of note_t, e.g. SFX_WIN[], the function accepts SFX_WIN[0]. We can then loop through its elements.
Crash course on array-to-pointer decay
Array-to-pointer decay occurs since back when computers were slow, passing an array which might’ve contained a substantial amount of information, would consume your computer’s memory and potentially crash your computer. Thus, the designers of C made it so the array decays to a pointer to the first element.
main.c
Timer 0 is configured to deliver a 1ms tick.
Initialization of all subsystems occur.
A helper function, return_to_menu detects a long button press and exits the current game via a switch/case block which acts as a state dispatcher.
Afterwards, we enter the super loop, for(;;). This is where the FSM resides. States are stored in a enum and a switch/case block acts as a state dispatcher.
// All possible game states
typedef enum
{
STATE_SPLASH = 0,
STATE_MENU = 1,
STATE_SLOTS = 2,
STATE_COINFLIP = 3,
STATE_HIGHERLOWER = 4,
STATE_DICE = 5,
STATE_GAMEOVER = 6
} ui_state_t;
When we enter a case, we enter a game. Since buttons are fundamental to the operation of each game, we pass in the button information, btn_event_t. Each state can know use information from the button. Another design pattern is to have a universal button manager which each state communicates with.
The player is trapped in the FSM until he/she runs out of funds. At which point, STATE_GAMEOVER is set, game over SFX plays and game over message is displayed on the LCD.
ui.h/.c
The bitmaps for the 8 custom glyphs are defined. In order to access them, we create an array of pointers, GLYPH_TABLE[8].
This file contains the various functions which draw to the LCD such as ui_draw_menu(), ui_show_score(), ui_menu_update(), ui_draw_gameover().
ui_load_cgram()
ui_load_cgram() fetches the glyph from PROGMEM, writes it into a buffer then writes it to the LCD via LCD_WriteCustomChar(). We copy the glpyhs from PROGMEM into RAM before displaying to the LCD. This is mandatory since AVR MCU’s have a Harvard architecture. There are separate memory locations and buses for RAM and flash.
ui_load_cgram() loads 8 glyphs then the 8 rows per glyph. Two for loops are used to achieve this. However, I don’t understand how GLYPH_TABLE is accessed like this: GLYPH_TABLE[slot][r]. We iterate through each slot 8 times. We do this 8 times then we print to LCD.
ui_load_cgram() entails looping through GLYPH_TABLE[8] then looping through each index.
The way we index GLYPH_TABLE[] is interesting. We index it like a normal array then we index it again. Thus, GLYPH_TABLE[slot][r]. The result is we index an element, which is a pointer to an array then we index (step through) each element in the array that the pointer is pointing to. It’s a bit confusing since the syntax looks like 2D array indexing.
void ui_draw_centered(uint8_t row, const char *s)
{
char buf[17];
uint8_t len, pad;
len = (uint8_t)strlen(s);
if (len > 16U) { len = 16U; }
pad = (uint8_t)((16U - len) / 2U);
memset(buf, ' ', 16U);
memcpy(&buf[pad], s, len);
buf[16] = '\0';
Lcd_SetCursor(0U, row);
Lcd_WriteString(buf);
}
What’s the operation of memset(), memcpy()
memset() is part of <string.h>. It’s a standard C library function. It’s an alternative way to populate a buffer WITHOUT using a for loop. 💡void *memset(void *ptr, int value, size_t num); The way it’s used below, we populate buf‘s 16 elements with ' '.memcpy() is responsible for copying information. In the excerpt below, we index buf according to pad. We then begin copying bytes from s for len times.buf represents an array of char‘s which represents a string.
Through array-to-pointer decay, we can invoke ui_draw_centered() like this: ui_draw_centered(0U, "Pocket Casino");
ui_draw_menu()
The way the menu is drawn is smart. It’s encapsulated in ui_draw_menu(). The menu items are stored in an array of char pointers, MENU_ITEMS[]. In the function we create two pointers to a char, called top, bot. This represents the information in the top and bottom row. They are assigned values by indexing MENU_ITEMS. A logical progression of navigating the menu is supported as bot always displays the next element after top. Using % ensures the menu wraps back round to the start, thus presenting a never-ending menu. % ensures bot can only be 0, 1, 2, 3. Since the dividend < divisor, the result IS the dividend.
If we’re at Dice, top = (3 + 1) % 4 = 0, thus Slots, (the first game in the menu) is printed.
ui_menu_update() processes button inputs and submits it to a switch case block. This function allows the user to enter a game.
All games consists of 3 functionsenter(), update(), exit() and they all have their own unique FSM.
coinflip.h/.c
This game costs 10 credits and pays 20 credits if you win.
draw_idle() draws the opening screen. This is invoked by enter().
update() progresses from idle → flipping → flipped. It processes user input, validates user has sufficient credits and invokes the flipping animation.
dice.h/.c
Costs 5 credits. The player can select doubles, over 7, under 7 or exactly 7.
The rolling dice animation is assisted by ANIM_PATTERNS[4][8].
Row 0 presents the combination the 2 die landed on and it prints the sum. This is delegated to write_dice_row0(). Btw, the two die are globals since their used by other functions.
Drawing functions exist for the opening screen, rolling and result screen.
In dice_update(), we process user input. If the player is poor, they can’t play the game otherwise a state change occurs. After the rolling animation is played, RNG engine is invoked to calculate the die combination. The outcome of the match is evaluated and the payout is calculated.
Hi or Low and Slots follow a similar pattern. For the sake of brevity, I’ll omit their explanation.
Showcase
FAQ
1. How does the user “scroll” through the menu using the two buttons?
The way the menu is drawn is smart. It’s encapsulated in ui_draw_menu(). The menu items are stored in an array of char
pointers, MENU_ITEMS[] . In the function we create two pointers to a char , called top, bot. This represents the information in the top and bottom row. They are assigned values by indexing MENU_ITEMS. A logical progression of navigating the menu is supported as bot always displays the next element after top. Using %
ensures the menu wraps back round to the start, thus presenting a never-ending menu. % ensures bot can only be 0, 1, 2, 3. Since the dividend < divisor, the result IS the dividend.
2. In slots, how are the custom glyphs generated?
The bitmaps are defined in ui.c. A LUT is defined, GLYPH_TABLE that stores locations of the gylphs, mapping them to CGRAM slot numbers. This is required for the LCD driver. To render an icon, we simply by passing in the index into Lcd_writeCustomChar().
3. There four separate files for each game. Each game requires updating the LCD. Should this responsibility fall on each game file or a different file like a LCD manager file?
Placeholder
4. What’s the complete list of icons that can appear in Slots?
There are 5 possible icons: Cherry, Bell, Seven, Coin, Dice which is defined in slots.c. However, the bitmap of each icon/glyph is coded in ui.c.
5. There are 5^3 = 125 unique combinations for Slots? What’s the probability of a jackpot?
The RNG engine doesn’t assign weights to the different outcomes. Thus, a jackpot has a 1/125 = 0.8% chance of occurring.
The game is fair and square 🙂







