Click here to Skip to main content
15,882,163 members
Articles / Game Development

Bingo Game Suite - Part 3

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
29 Mar 2023CPOL15 min read 4.2K   425   7  
This article is the third and final in the Bingo Game Suite series.

1. Introduction Table of Contents

At last, this article is the third of three articles describing the Bingo game suite that I developed over the past few months. The articles are published in the hope that the software may prove useful to the articles' readers. The three articles address the 75-ball version of Bingo.

Overview

This article discusses the bolded items in the preceding figure. The Bingo card file must have already been generated by the Generate_Cards [^] program and a set of Bingo Cards must have already been printed by the Print_Cards [^] program.

2. Playing Bingo Table of Contents

Bingo is played in thousands of locations, big and small, across North America. The game that is played (and discussed in this article) is the 75-ball version of Bingo (as opposed to the 90-ball version).

2.1. Background Table of Contents

Players purchase Bingo game cards that are usually printed three cards to a sheet. Each sheet forms one game and is printed in a different color. A session is usually made up of six differently colored sheets. There may be many sessions during a day.

Cards per Sheet

The caller (game host) randomly draws balls from a hopper. Each ball in the hopper is numbered from 1 to 75 and is usually colored according to the number on them:

  • Blue      1 - 15
  • Red     16 - 30
  • White  31 - 45
  • Green  46 - 60
  • Yellow 61 - 75

Ball Colors

The caller announces the number of the ball drawn. If ball numbered 37 was drawn, the number would be announced as "N 37". In most locations, the ball is placed into a numbered hole in a board. When the ball is inserted, the number on a called board (in this case 37) lights.

Called Board

2.2. Patterns Table of Contents

Normally just having the called number on your card does not win the game. The called number must usually appear in a pattern announced by the caller at the start of the game. For the purpose of this article, there are two types of pattern: static and dynamic. Note that this article does not enumerate all possible patterns; rather, it discusses just a few for demonstration purposes. The Roanoke VA Moose Lodge has a set of Bingo Patterns [^] that is rather complete.

The best way to describe patterns is to display some examples. The following are the static patterns supported by the software described in this article.

Static Patterns

The following are the dynamic patterns supported by the software described in this article.

Dynamic Patterns

The patterns display the locations on a Bingo card where called numbers must appear in order to win.

2.3. Stars Table of Contents

Bingo with Stars is an addition to the normal Bingo game. It was introduced to increase excitement during the initial play of a Bingo game.

Stars

During a normal Bingo game, if a player can mark all three stars on the sheet in the first three numbers called, the player calls "Stars". If the Bingo cards on the sheet contain a valid stars pattern, the player is awarded a prize and the Bingo game continues.

2.4. Simulate a Game Table of Contents

To put it all together, let's simulate the play of a game. Assume

  • The caller has required that the game be played on LightCoral-colored Bingo cards (this implies that the group_face must begin with a "2")
  • The pattern is the "letter-x"
  • 17 numbers have been called
  • The called board looks like:

Game Called

The value in red is the last ball called and that number must be on the winning Bingo card. Now let's look at a player's Bingo card.

Played Bingo Card

The green circles are the marks that the player made on the card as each number was called. The SeaShell-colored circle is the last number called. When the last number was called, the player called "Bingo" and the game was suspended pending confirmation. The player, at the request of the caller, identifies the Bingo card as "2066057" (the group_face in the center square). When entered into the Bingo software by the caller, the program confirms that the Bingo card is a winner.

Of course there are other outcomes in which a player who calls "Bingo" does not win: the last number called is not on the card (the player may not have realized the Bingo before another number was called - known as "sleeping"); all of the positions in the pattern were not marked; the first digit in the group_face does not equate to the color of the card being played; etc.

3. Play_Bingo Implementation Table of Contents

3.1. Subordinate Form Template Table of Contents

This implementation uses a number of forms to collect information. I use a simple template for the invocation of a subordinate form.

A subordinate form is created in the same directory as the form that will invoke it. The subordinate form is added to the project as a Windows Form with the same namespace as the form that will invoke it. At a minimum, the subordinate form must contain a public method named initialize_form in addition to its constructor. The signature of initialize_form is:

C#
public bool initialize_form ( )

The skeleton for the Retrieve_And_Open_Bingo_Card_File is:

C#
namespace Play_Bingo
    {

    // *********************** class Retrieve_And_Open_Bingo_Card_File

    public partial class Retrieve_And_Open_Bingo_Card_File : Form
        {
        
        
        
        // *********************************** initialize_GUI_controls

        bool initialize_GUI_controls ( )
            {
            bool successful = true;

            
            
            return ( successful );

            } // initialize_GUI_controls

        // ************************************************ close_form

        void close_form ( )
            {

            this.Close ( );

            } // close_form

        // ******************************************* initialize_form

        public bool initialize_form ( )
            {
            bool  successful = false;

            if ( !initialize_GUI_controls ( ) )
                {
                successful = false;
                }
            else
                {
                successful = true;
                }

            if ( !successful )
                {
                close_form ( );
                }

            return ( successful );
            
            } // initialize_form

        // ************************* Retrieve_And_Open_Bingo_Card_File

        public Retrieve_And_Open_Bingo_Card_File ( )
            {

            InitializeComponent ( );

            

            } // Retrieve_And_Open_Bingo_Card_File

        } // class Retrieve_And_Open_Bingo_Card_File

    } // namespace Play_Bingo

In the Play_Bingo main form Retrieve_And_Open_Bingo_Card_File is invoked by the method retrieve_and_open_bingo_card_file:

C#
// ************************* retrieve_and_open_bingo_card_file

void retrieve_and_open_bingo_card_file ( )
    {
    Form    form = null;

    if ( form == null )
        {
        form = new Retrieve_And_Open_Bingo_Card_File ( );
        }

    if ( ( ( Retrieve_And_Open_Bingo_Card_File ) form ).
                                        initialize_form ( ) )
        {
        form.ShowDialog ( );
        }

    form.Dispose ( );
    form = null;

    } // retrieve_and_open_bingo_card_file

If additional statements are needed in the invoking method, they may be inserted before and after the form.ShowDialog ( ) statement.

3.2. Insuring Randomness Table of Contents

In the real world, Bingo balls are drawn at random by the very nature of the equipment from which they are drawn.

Capitol Envoy Bingo Console

To simulate that randomness, Play_Bingo uses the same type of Pseudo Random Number Generator [^] (PRNG) as did Generate_Cards.

The list of 75 random Bingo ball face numbers, balls, is found in the Global_Values class of the Utilities project (along with a number of other global variables).

C#
public  static  List < int >    balls = new List < int > ( );



// ****************************************** initialize_balls

public static void initialize_balls ( )
    {
    List < int >    allowed = Enumerable.Range ( 
                        1, 
                        MAXIMUM_BALLS ).ToList ( );

    balls.Clear ( );
    called_int.Clear ( );

    while ( allowed.Count > 0 )
        {
        int index = random.Next ( allowed.Count );

        balls.Add ( allowed [ index ] );
                            // to avoid duplicates, remove 
                            // the entry at index 
        allowed.RemoveAt ( index );
        }

    } // initialize_balls

initialize_balls is invoked at the beginning of each game.

3.3. ThreadPauseState Table of Contents

Before I describe the background workers in Play_Bingo, the mechanism by which background threads are paused and resumed needs to be addressed.

Play_Bingo requires that, upon caller command, it pause and resume the execution of its background workers. This is signaled by the caller clicking on a button or pressing a registered keyboard key. Internal to each thread is code that recognizes that it is being suspended. For example:

C#
using TPS = Utilities.ThreadPauseState;

    

    TPS       thread_pause_state = new TPS ( );

    

    thread_pause_state.Wait ( );

The ThreadPauseState class of the Utilities project contains the methods that manage the pause/resume operations. These are implemented as invocations of the Monitor [^] Enter and Exit methods that acquire or release an exclusive lock.

C#
using System.Threading;

using GV = Utilities.Global_Values;

namespace Utilities
    {

    /// <summary>
    /// class provides a mechanism to pause/resume BackgroundWorkers
    /// </summary>
    /// <reference>
    /// dynamichael's answer on 
    /// https://stackoverflow.com/questions/12780329/
    ///                  how-to-pause-and-resume-a-backgroundworker
    /// </reference>
    public class ThreadPauseState 
        {
        bool    paused = false;

        // **************************************************** Paused

        public bool Paused 
            {

            get { return ( paused ); }
            set {
                if( paused != value ) 
                    {
                    if( value ) 
                        {
                        Monitor.Enter ( GV.thread_pause_lock );
                        paused = true;
                        } 
                    else 
                        {
                        paused = false;
                        Monitor.Exit ( GV.thread_pause_lock );
                        }
                    }
                }

            } // Paused

        // ****************************************************** Wait

        public void Wait ( ) 
            {

            lock ( GV.thread_pause_lock ) 
                { 

                }

            } // Wait

        } // ThreadPauseState

    } // namespace Utilities

For example, during the execution of Play_Bingo the caller may want to change the winning pattern. To do so, the caller clicks the pattern_BUT button or presses the keyboard key "P". The event handler for that button click or key press invokes the patterns method.

C#
using BT = Utilities.Build_Templates;
using GV = Utilities.Global_Values;



// ************************************************** patterns

void patterns ( )
    {
    BT.Patterns  current_pattern = GV.pattern_wanted;

    thread_pause_state.Paused = true;

    open_choose_pattern ( );

    disable_all_buttons ( );
    bingo_BUT.Enabled = true; 
    pause_BUT.Enabled = true;
    settings_BUT.Enabled = true;
    reinitialize_BUT.Enabled = true;
    exit_BUT.Enabled = true;

    thread_pause_state.Paused = false;

    } // patterns

The first action of patterns is to pause the execution of Play_Bingo's background workers.

C#
thread_pause_state.Paused = true;

When all of the work required to change patterns has been completed, patterns resumes the execution of Play_Bingo's background workers.

C#
thread_pause_state.Paused = false;

Within each background worker appears

C#
thread_pause_state.Wait ( );

If thread_pause_state.Paused is true the thread is suspended, otherwise the tread continues its execution.

3.4. Background Workers Table of Contents

In order to keep the main User Interface (UI) responsive, two background workers [^] exist: one flashes a number on the called board; the other displays the pattern in the pattern_PAN (recall that patterns may be dynamic).

Play Bingo

When program initialization has been completed, Play_Bingo invokes initialize_background_workers. Note that this method creates the background workers, it does not cause them to execute; rather RunWorkerAsync must be invoked on each worker.

C#
// ***************************** initialize_background_workers

void initialize_background_workers ( )
    {

    draw_flash_BW = new BackgroundWorker
        {
        WorkerReportsProgress = true,
        WorkerSupportsCancellation = true
        };
    draw_flash_BW.DoWork += draw_flash_DoWork;
    draw_flash_BW.RunWorkerCompleted += 
                draw_flash_WorkerCompleted;
    draw_flash_BW.ProgressChanged += 
                draw_flash_ProgressChanged;

     draw_pattern_BW = new BackgroundWorker
        {
        WorkerReportsProgress = true,
        WorkerSupportsCancellation = true
        };
    draw_pattern_BW.DoWork += draw_pattern_DoWork;
    draw_pattern_BW.RunWorkerCompleted += 
                draw_pattern_WorkerCompleted;
    draw_pattern_BW.ProgressChanged += 
                draw_pattern_ProgressChanged;

    } // initialize_background_workers

3.4.1. draw_flash_DoWork Table of Contents

C#
// ***************************************** draw_flash_DoWork

void draw_flash_DoWork ( object             sender, 
                         DoWorkEventArgs    e )
    {
    int                 balls_called = 0;
    Label               label;
    Panel               panel;
    int                 panel_index = 0;
    BackgroundWorker    worker = ( BackgroundWorker ) sender;

    while ( total_calls < GV.MAXIMUM_BALLS )
        {
        thread_pause_state.Wait ( );

        if ( worker.CancellationPending ) 
            { 
            e.Cancel = true;
            return; 
            }
                                // draw ball
        do 
            {
            ball_to_display = GV.balls [ total_calls++ ];
            } while ( excluded.Contains ( ball_to_display ) );

        refresh_control ( ball_PB );
                                // delay the time needed 
                                // between the ball appearing 
                                // and the time it can be 
                                // called and moved to the 
                                // balls_called_PAN
        Thread.Sleep ( ( int ) ( 1000M * 
                                 GV.drawn_to_flash  ) );
        say_the_bingo_ball ( ball_to_display );
                                // reset last called (red) to 
                                // called (white) if not first
        if ( balls_called > 0 )
            {
            panel_index = ( GV.last_called );
            panel = ( Panel ) balls_called_PAN.Controls [ 
                                                panel_index ];
            label = ( Label ) panel.Controls [ 0 ];
            
            panel.BackColor = Color.White;
            label.BackColor = Color.White;
            label.ForeColor = Color.Black;

            refresh_control ( panel );
            }
                                // update last_called and 
                                // total_calls
        GV.called_int.Add ( ball_to_display );
        balls_called++;
        worker.ReportProgress ( balls_called, 
                                ball_to_display );
                                // flash the called ball
        flash_bingo_card_board ( );
        
        GV.last_called = ball_to_display - 1;
        }

    e.Result = GV.MAXIMUM_BALLS;

    } // draw_flash_DoWork

draw_flash_DoWork is the workhorse of Play_Bingo:

  1. Enters a paused state if required
  2. Draws the next ball number ball_to_display from the GV.balls list, ignoring ball numbers that do not appear in the current pattern
  3. Draws the ball image on the monitor
  4. Speaks the ball_to_display
  5. Sleeps for the number of seconds specified by GV.drawn_to_flash
  6. If there was an earlier ball drawn, resets the called board panel that flashed earlier from a White foreground with a Red background to a Black foreground with a White background.
  7. Invokes its ReportProgress method to update the last_called_PAN and total_calls_PAN
  8. Invokes flash_bingo_card_board to cause the new ball number to flash on the called board
  9. Saves the number of the last-called ball
  10. If all balls have not been drawn, returns to step 1

Because draw_flash_DoWork is on a different thread than the User Interface (UI) thread, it invokes refresh_control whenever the need arises to redraw a UI thread control. The control's InvokeRequired [^] property is accessed to determine if the control's Invoke method is required.

C#
// ******************************************* refresh_control

void refresh_control ( Control  control )
    {

    if ( control.InvokeRequired )
        {
        control.Invoke ( 
            new EventHandler ( 
                delegate
                    {
                    refresh_control ( control );
                    } 
                ) 
            );
        }
    else
        {
        control.Refresh ( );
        }

    } // refresh_control

When refresh_control invokes control.Refresh [^], the control is invalidated, the Control.Paint [^] event is raised, and the control is redrawn. For a ball, the ball_PB_Paint event handler [^] is executed.

C#
using DOB = Play_Bingo.Draw_One_Ball;

DOB                     dob = new DOB ( );



// ********************************************* ball_PB_Paint

void ball_PB_Paint ( object         sender, 
                     PaintEventArgs e )
    {
    Image   image = null;

    e.Graphics.Clear ( BALL_BACKGROUND );
    if ( ball_to_display == 0 )
        {
        image = Properties.Resources.bingo_image;
        }
    else 
        {
        image = dob.draw_ball ( ball_to_display );
        }

    e.Graphics.DrawImage ( image, new Point ( 0, 0 ) );

    } // ball_PB_Paint

ball_PB_Paint determines if the Bingo logo (icon) is to be displayed or if a numbered ball is to be drawn. In the latter case, it invokes draw_ball that creates the image of the numbered Bingo ball. To do so, draw_ball performs the following steps to create the image to be drawn.

Drawing G52

Recall that objects drawn later overwrite objects drawn earlier.

Lastly, ball_PB_Paint uses the PaintEventArgs Graphics.DrawImage to actually draw the image.

3.4.2. draw_pattern_DoWork Table of Contents

draw_pattern_DoWork is responsible for drawing the pattern that is displayed in the pattern_PAN. Recall that there are dynamic patterns as well as static patterns. Each pattern is created as a list of 25 bytes. Static patterns have a single list; dynamic patterns have as many lists as are required to display all of the variations of a pattern. The lists are created as needed and are referred to as templates.

For example, one of the simplest dynamic patterns is the diagonal pattern. Its template is created by build_diagonal_templates found in the Build_Templates class in the Utilities project.

C#
// ********************************** build_diagonal_templates

public List < List < byte > > build_diagonal_templates ( )
    {
    List < byte >           template;
    List < List < byte > >  templates;

    templates = new List < List < byte > > ( );
    template = new List < byte > {  1, 0, 0, 0, 0,
                                    0, 1, 0, 0, 0,
                                    0, 0, 1, 0, 0,
                                    0, 0, 0, 1, 0,
                                    0, 0, 0, 0, 1 };
    templates.Add ( template );
    template = new List < byte > {  0, 0, 0, 0, 1,
                                    0, 0, 0, 1, 0,
                                    0, 0, 1, 0, 0,
                                    0, 1, 0, 0, 0,
                                    1, 0, 0, 0, 0 };
    templates.Add ( template );

    return ( templates );

    } // build_diagonal_templates

Here the leftward (downward) diagonal is specified first with the rightward (upward) diagonal specified second. Both of the templates are added to the templates list of lists and templates is returned.

In Play_Bingo appears the declaration of the list of lists

C#
List < List < byte > >  templates;

that receives the results of the build_diagonal_templates invocation. What we desire is the diagonal pattern to change every specified time interval as follows.

Moving Diagonal

The value of the class variable template_count determines which template will be drawn. retrieve_dynamic_template returns the list of bytes that contains the next template to draw.

C#
// ********************************* retrieve_dynamic_template

List < byte > retrieve_dynamic_template ( 
                                        BT.Patterns pattern )
    {

    if ( first_time )
        {
        switch ( pattern )
            {
            ⋮
            case BT.Patterns.DIAGONAL:
                templates = 
                    bt.build_diagonal_templates ( );
                break;
            ⋮
            default:
                templates = 
                    bt.build_empty_templates ( );
            break;
            }

        template_count = templates.Count;

        first_time = false;
        }

    template_count++;
    if ( template_count >= templates.Count )
        {
        template_count = 0;
        }

    return ( templates [ template_count ] );

    } // retrieve_dynamic_template

The event handler for the pattern_PAN Paint event is pattern_PAN_Paint.

C#
// ***************************************** pattern_PAN_Paint

void pattern_PAN_Paint ( object         sender, 
                         PaintEventArgs e )
    {

    if ( first_paint )
        {
        first_paint = false;
        }
    else 
        {
        template = retrieve_a_template ( );
        create_pattern_buffer ( );
        draw_the_template ( template );
                                // pattern_PAN
        pattern_buffer.RenderGraphicsBuffer ( e.Graphics );
                                // form upper left icon
        this.Icon = Icon.FromHandle ( 
                                 pattern_buffer.Bitmap_Image.
                                 GetHicon ( ) );
        }

    } // pattern_PAN_Paint

After pattern_PAN_Paint has retrieved the template, it creates a buffer into which to draw the template by invoking create_pattern_buffer.

C#
// ************************************* create_pattern_buffer

void create_pattern_buffer ( )
    {

    if ( pattern_buffer != null )
        {
        pattern_buffer = 
            pattern_buffer.DeleteGraphicsBuffer ( );
        }
    pattern_buffer = new GraphicsBuffer ( );
    pattern_buffer.CreateGraphicsBuffer ( 
                                    TEMPLATE_BITMAP_EDGE,
                                    TEMPLATE_BITMAP_EDGE );
    pattern_buffer.Graphic.SmoothingMode = 
        SmoothingMode.HighQuality;
    pattern_buffer.ClearGraphics ( PANEL_BACKCOLOR );

    } // create_pattern_buffer

create_pattern_buffer obtains a new graphics buffer from the class GraphicsBuffer in the namespace Play_Bingo.

C#
using System.Drawing;

namespace Play_Bingo
    {

    // ****************************************** class GraphicsBuffer

    public class GraphicsBuffer
        {
        Bitmap      bitmap;
        Graphics    graphics;
        int         height;
        int         width;

        // ******************************************** GraphicsBuffer

        /// <summary>
        /// constructor for the GraphicsBuffer
        /// </summary>
        public GraphicsBuffer ( )
            {

            width = 0;
            height = 0;

            } // GraphicsBuffer

        // ************************************** CreateGraphicsBuffer

        /// <summary>
        /// completes the creation of the GraphicsBuffer object
        /// </summary>
        /// <param name="width">
        /// width of the bitmap
        /// </param>
        /// <param name="height">
        /// height of the bitmap
        /// </param>
        /// <returns>
        /// true, if GraphicsBuffer created; otherwise, false
        /// </returns>
        public bool CreateGraphicsBuffer ( int width,
                                           int height )
            {
            bool  success = true;

            if ( graphics != null )
                {
                graphics.Dispose ( );
                graphics = null;
                }

            if ( bitmap != null )
                {
                bitmap.Dispose ( );
                bitmap = null;
                }

            this.width = 0;
            this.height = 0;

            if ( ( width == 0 ) || ( height == 0 ) )
                {
                success = false;
                }
            else
                {
                this.width = width;
                this.height = height;

                bitmap = new Bitmap ( this.width, this.height );
                graphics = Graphics.FromImage ( bitmap );

                success = true;
                }

            return ( success );

            } // CreateGraphicsBuffer

        // ************************************** DeleteGraphicsBuffer

        /// <summary>
        /// deletes the current GraphicsBuffer
        /// </summary>
        /// <returns>
        /// null, always
        /// </returns>
        public GraphicsBuffer DeleteGraphicsBuffer ( )
            {

            if ( graphics != null )
                {
                graphics.Dispose ( );
                graphics = null;
                }

            if ( bitmap != null )
                {
                bitmap.Dispose ( );
                bitmap = null;
                }

            width = 0;
            height = 0;

            return ( null );

            } // DeleteGraphicsBuffer

        // *************************************************** Graphic

        /// <summary>
        /// returns the current Graphic Graphics object
        /// </summary>
        public Graphics Graphic
            {

            get
                {
                return ( graphics );
                }

            } // Graphic

        // ********************************************** Bitmap_Image

        /// <summary>
        /// returns the current Graphic Bitmap object
        /// </summary>
        public Bitmap Bitmap_Image
            {

            get
                {
                return ( bitmap );
                }

            } // Bitmap_Image

        // ************************************** GraphicsBufferExists

        /// <summary>
        /// returns true if the graphics object exists; false, 
        /// otherwise
        /// </summary>
        public bool GraphicsBufferExists
            {

            get
                {
                return ( graphics != null );
                }

            } // GraphicsBufferExists

        // ******************************************* ColorAtLocation

        /// <summary>
        /// given a point in the graphics bitmap, returns the GDI 
        /// Color at that point
        /// </summary>
        /// <param name="location">
        /// location in the bitmap from which the color is to be 
        /// returned
        /// </param>
        /// <returns>
        /// if the location is within the bitmap, the color at the 
        /// location; otherwise, Black
        /// </returns>
        public Color ColorAtLocation ( Point location )
            {
            Color  color = Color.Black;

            if ( ( ( location.X > 0 ) && 
                   ( location.X <= this.width ) ) && 
                 ( ( location.Y > 0 ) &&
                   ( location.Y <= this.height ) ) )
                {
                color = this.bitmap.GetPixel ( location.X,
                                               location.Y );
                }

            return ( color );

            } // ColorAtLocation

        // ************************************** RenderGraphicsBuffer

        /// <summary>
        /// Renders the buffer to the graphics object identified by 
        /// graphic
        /// </summary>
        /// <param name="graphic">
        /// target graphics object (e.g., PaintEventArgs e.Graphics)
        /// </param>
        public void RenderGraphicsBuffer ( Graphics graphic )
            {

            if ( bitmap != null )
                {
                graphic.DrawImage (
                            bitmap,
                            new Rectangle ( 0, 0, width, height ),
                            new Rectangle ( 0, 0, width, height ),
                            GraphicsUnit.Pixel );
                }

            } // RenderGraphicsBuffer

        // ********************************************* ClearGraphics

        /// <summary>
        /// clears the graphics object identified by graphics
        /// </summary>
        /// <param name="graphics">
        /// Window forms graphics object
        /// </param>
        /// <param name="background_color">
        /// background color to be used to clear graphics
        /// </param>
        public void ClearGraphics ( Color background_color )
            {

            Graphic.Clear ( background_color );

            } // ClearGraphics

        } // class GraphicsBuffer

    } // namespace Play_Bingo

pattern_PAN_Paint then invokes draw_the_template to draw the template to the pattern_buffer.

C#
// ***************************************** draw_the_template

void draw_the_template ( List < byte >  template )
    {
    Graphics    graphic = pattern_buffer.Graphic;
    int         i = 0;
    Pen         pen = new Pen ( PANEL_BORDER_COLOR, 1.0F );
    SolidBrush  solidbrush = new SolidBrush ( 
                                 GV.marker_color ); 
    int         x = 0;
    int         y = 0;

    foreach ( byte item in template )
        {
        Point       marker_location = new Point ( ( x + 5 ), 
                                                  ( y + 4 ) );
        Rectangle   rectangle = new Rectangle ( x, y, 40, 40 );

        graphic.FillRectangle ( new SolidBrush ( 
                                        PANEL_BACKCOLOR ), 
                                rectangle);
        graphic.DrawRectangle ( pen, rectangle);
        if ( item >= 1 )
            {
            graphic.FillEllipse ( 
                        solidbrush, 
                        new Rectangle ( marker_location,
                                        MARKER_SIZE ) );

            }
        x += PANEL_EDGE;
        i++;
        if ( ( i % PANEL_COLUMNS ) == 0 )
            {
            x = 0;
            y += PANEL_EDGE;
            }
        }

    solidbrush.Dispose ( );
    pen.Dispose ( );

    } // draw_the_template

Once the template has been drawn, pattern_PAN_Paint renders the image. Just for cosmetics, pattern_PAN_Paint changes the Play_Bingo icon to the new pattern.

3.5. Speech Synthesis Table of Contents

As mentioned above, as draw_flash_DoWork "draws" a new Bingo ball, it announces the value drawn by invoking say_the_bingo_ball

C#
// **************************************** say_the_bingo_ball

public void say_the_bingo_ball ( int ball_to_call )
    {
    string              ball_label = ", ,";
    PromptBuilder       prompt = new PromptBuilder ( );
    SpeechSynthesizer   voice = null;

    try 
        {
        if ( voice != null )
            {
            voice.Dispose ( );
            voice = null;
            }
        voice = new SpeechSynthesizer ( );

        ball_label += CONST.BALL_LABELS [
                          ( ( ball_to_call - 1 ) / 15 ) ];

        prompt.StartSentence ( );
        prompt.AppendTextWithHint ( ball_label,
                                    SayAs.Text );
        prompt.AppendTextWithHint ( ball_to_call.ToString ( ),
                                    SayAs.NumberCardinal );
        prompt.EndSentence ( );

        voice.Speak ( prompt );

        voice.Dispose ( );
        voice = null;
        }
    catch ( Exception ex )
        {
        
        }
        
    } // say_the_bingo_ball

The try-catch construct is used to capture those cases in which the computer on which Play_Bingo is executing does not have an audio device for output. In that case, say_the_bingo_ball is a NUL statement.

say_the_bingo_ball utilizes SpeechSynthesizer Class [^] to announce the Bingo ball using the default system voice.

3.6. Confirming Bingo Table of Contents

When a player calls "Bingo", Play_Bingo must determine if the player's card is, in fact, a winner. The software that performs this test is contained in the class Confirm_Bingo in the namespace Play_Bingo. Within this section of this article, all referenced code can be found in that class.

To determine if a Bingo card is a winner, the caller must supply the following information:

  • The color of the Bingo cards being used
  • The card number of the player's card

When these items are supplied, Play_Bingo validates the Bingo card color. There are a number of outcomes:

  • Success - The card color is valid
  • NOT A BINGO - UNRECOGNIZED CARD COLOR
  • NOT A BINGO - WRONG COLOR CARD

If the card color is valid, Play_Bingo extracts the card_number and uses it to compute the card_offset into the Bingo card file. If the card is retrieved successfully, Play_Bingo invokes encode_user_card to convert the card to a representation that can be used for determining if the player's card is a winner.

C#
// ****************************************** encode_user_card

List < byte > encode_user_card ( List < byte >  user_card,
                                 List < byte >  called )
    {
    List < byte >   encoded = new List < byte > {
                                  0, 0, 0, 0, 0,
                                  0, 0, 0, 0, 0,
                                  0, 0, 0, 0, 0,
                                  0, 0, 0, 0, 0,
                                  0, 0, 0, 0, 0 };


    for ( int i = 0; ( i < user_card.Count ); i++ )
        {
        if ( called.Contains ( user_card [ i ] ) )
            {
            encoded [ i ] = 1;
            }
        }
                                // replace existing free space 
                                // value with 1
    encoded [ CONST.CENTER_CELL_INDEX ] = 1;

    return ( encoded );

    } // encode_user_card

The resulting user_card_bitmap now represents the called numbers on the Bingo card identified by its group_face by the player. Play_Bingo now uses this bitmap to determine if that Bingo card contains the pattern.

C#
// ************************************************** validate

bool validate ( )
    {
    bool    equal = false;

    foreach ( List < byte > template in templates )
        {
        equal = true;

        for ( int i = 0; ( i < template.Count ); i++ )
            {
                                // don't test the Free Space
            if ( i == CONST.CENTER_CELL_INDEX )
                {
                continue;
                }
                
            if ( ( user_card_bitmap [ i ] & 
                   template [ i ] ) != template [ i ] )
                {
                equal = false;
                break;
                }
            }

        if ( equal )
            {
            GV.found_template = new List < byte > ( );

            for ( int i = 0; ( i < template.Count ); i++ )
                {
                GV.found_template.Add ( template [ i ] );
                }
            break;
            }
        }

    if ( !equal )
        {
        display_in_message_RTB (
            "NOT A BINGO - DOES NOT MATCH PATTERN" );
        return ( false );
        }

    return ( true );

    } // validate

 

The test

C#
( ( user_card_bitmap & template ) == template )

has a number of outcomes:

  • Success - The card is a winner
  • NOT A BINGO - DOES NOT MATCH PATTERN
  • NOT A BINGO - NOT CALLED ON LAST CALLED

This process is partly depicted in the following figure. Here assume the player identified the Bingo card as "2066011".

Confirm Bingo

3.7. Confirming Stars Table of Contents

When a player shouts "Stars", Play_Bingo must determine if the player's game sheet contains starred numbers that have been called. The software that performs this test is contained in the class Confirm_Stars in the namespace Play_Bingo. Within this section of this article, all referenced code can be found in that class.

To determine if a Bingo card is a winner, the caller must provide the group_face of the top Bingo card on the sheet. For example, in the following figure

Stars

the top Bingo card on the sheet has the group_face number of "12017". When that number is entered by the caller, there are three immediate results:

  • NOT CALLED IN FIRST THREE NUMBERS
  • UNRECOGNIZED CARD COLOR
  • FACE NUMBER NOT MULTIPLE OF 3

The first is unrecoverable; the other two may be caused by a player or caller error in reporting the top Bingo card group_face. If the value is accepted, cards_PAN is refreshed. If the three Bingo cards have three stars called, the following will be displayed.

Confirm Good

If the three Bingo cards do not have the three stars called, the following will be displayed.

Confirm Bad

When Resume is clicked, the Bingo game resumes.

4. Executing Play_Bingo Table of Contents

Play_Bingo is an interrupt-driven WinForm application. Its state diagram is depicted in the following state diagram.

Play Bingo State

The rounded rectangles are buttons; the squared rectangles are states.

Upon starting (Start Program), Play_Bingo must first determine what Bingo card file is to be used.

Get File Get File

Next, Play_Bingo must determine which pattern is to be used.

Pattern Choice

In this case, a double (one horizontal and one vertical) Bingo has been specified (note the green border surrounding the pattern). When the OK button is clicked, the Play_Bingo main screen is displayed.

Play Bingo

As the game progresses, the screen updates its information as in the previous screen shot.

When a player calls "Bingo" the caller clicks the Bingo button. This brings up the first portion of the verification form confirm_bingo. When the caller enters both the card color and the card number, and clicks Lookup, the following appears.

Confirm Bingo

This screen displays the player's card with all called numbers highlighted in green. When the caller clicks Validate, the following appears.

Confirm Bingo

If there is another "Bingo" called, the caller clears the card number, enters the new card number, clicks Lookup, and follows the same procedure as earlier. If there were no valid "Bingos", the caller clicks Resume and the game continues. If there as a valid "Bingo", the caller clicks New Game and the software opens the Pattern Choice window.

TEST CASE:
At the top of the confirm_bingo.cs file is the statement
C#
//#define TESTING
If the commenting is removed, a test-case will be instantiated near the end of the lookup method. To have a valid "Bingo", file cards_800000.dat, the pattern vertical-horizontal, the card color LightSkyBlue, and the card number 12017 must be specified.

When a player calls "Stars" the caller clicks the Stars! button. This brings up the confirm_stars form.bingo. When the caller enters the card number and clicks Lookup, the following appears.

Confirm Stars

If a different card number is given, the following might appear.

Confirm Stars

TEST CASE:
At the top of the confirm_stars.cs file is the statement
C#
//#define TESTING
If the commenting is removed, a test-case will be instantiated near the end of the lookup method. To have a valid "Stars", file cards_800000.dat and the card number 12017 must be specified.

In either case, the caller clicks Resume to return to the Bingo game.

If the timing of the game and/or the color of the pattern marker is not satisfactory, the caller may click Settings to bring up the Settings form.

Settings

The red items are NumericUpDown controls that set the value of their respective timings. If the Pattern marker color button is clicked, the KnownColorPicker form is opened.

Known Color Picker

Although there are no restrictions placed against any color, the user should not pick LightSkyBlue because the background color of the pattern_PAN is also LightSkyBlue. Also, colors that are too highly saturated or too dark will distract players from the called_board.

Certain keyboard keys are recognized when focus is in the main form (keyboard keys are not recognized in subordinate forms).

Key Action
S Open settings dialog
P Open patterns dialog
F1 Toggle instructions visibility
s Start the Bingo game
b Bingo called
t Stars called
p Pause the Bingo game
r Resume the Bingo game
i Reinitialize the Bingo game
x Exit the program

5. Conclusion Table of Contents

Play_Bingo provides a caller assistance in hosting games of Bingo.

6. References Table of Contents

7. Development Environment Table of Contents

The software presented in this article was developed in the following environment:

Microsoft Windows 7 Professional Service Pack 1
Microsoft Visual Studio 2008 Professional
Microsoft .Net Framework Version 3.5 SP1
Microsoft Visual C# 2008

8. History Table of Contents

Bingo - Part 3 of 3 - Play Bingo 03/29/2023 Original Article

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
In 1964, I was in the US Coast Guard when I wrote my first program. It was written in RPG (note no suffixing numbers). Programs and data were entered using punched cards. Turnaround was about 3 hours. So much for the "good old days!"

In 1970, when assigned to Washington DC, I started my MS in Mechanical Engineering. I specialized in Transportation. Untold hours in statistical theory and practice were required, forcing me to use the university computer and learn the FORTRAN language, still using punched cards!

In 1973, I was employed by the Norfolk VA Police Department as a crime analyst for the High Intensity Target program. There, I was still using punched cards!

In 1973, I joined Computer Sciences Corporation (CSC). There, for the first time, I was introduced to a terminal with the ability to edit, compile, link, and test my programs on-line. CSC also gave me the opportunity to discuss technical issues with some of the brightest minds I've encountered during my career.

In 1975, I moved to San Diego to head up an IR&D project, BIODAB. I returned to school (UCSD) and took up Software Engineering at the graduate level. After BIODAB, I headed up a team that fixed a stalled project. I then headed up one of the two most satisfying projects of my career, the Automated Flight Operations Center at Ft. Irwin, CA.

I left Anteon Corporation (the successor to CSC on a major contract) and moved to Pensacola, FL. For a small company I built their firewall, given free to the company's customers. An opportunity to build an air traffic controller trainer arose. This was the other most satisfying project of my career.

Today, I consider myself capable.

Comments and Discussions

 
-- There are no messages in this forum --