Click here to Skip to main content
15,881,742 members
Articles / Game Development

Bingo Game Suite - Part 2

Rate me:
Please Sign up or sign in to vote.
4.80/5 (3 votes)
27 Feb 2023CPOL12 min read 4.3K   296   4  
Print_Cards to produce PDF file containing specified number of unique Bingo cards
In this second part of the series, we look at how Print_Cards produces a PDF file containing a specified number of unique Bingo cards.

1. Introduction Table of Contents

This article is the second 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 US version of Bingo that has 75 numbered balls.

If the reader needs a refresher in the game of Bingo, please refer to the first article in this series.

In order for a game of Bingo to be played, it is necessary to generate and print the Bingo cards that players will use.

Overview

This article discusses the bolded items in the preceding figure. The Bingo card file must have been already generated by the Generate_Cards [^] program.

1.1. Printed Bingo Card Table of Contents

Printed Bingo cards are composed of six rows of five columns. The first row is the card heading and contains the letters 'B', 'I', 'N', 'G', and 'O', each letter appearing in its own column. The five remaining rows contain the numeric values. When 24 random numbers are inserted, the card may appear as follows:

Decorated Bingo Card Decorated Bingo Card with Star

The figure on the right was created with the requirement that stars be distributed throughout the Bingo cards. When this requirement is imposed, each Bingo card will have one star randomly placed thereon. The location of the star on each card is recorded in the Bingo card file.

The center square of the 5 x 5 card is known as a "free space" that is considered as called.

Physically, because I was building the suite on a home computer, I decided that the following design criteria should apply.

  • The format of the output file of the program will be Portable Document Format (PDF).
  • The dimensions of the paper on which the PDF images will be printed are either 8-1/2 x 11 or 8-1/2 x 14 inches.

1.2. Number of Cards Table of Contents

Bingo is played in a large number of locations, each with its own rules. In this article, I am going to suggest the following rules:

  • A "game" is made up of three Bingo cards printed on a sheet. Each of the three Bingo cards on a sheet is printed in the same color.

Cards per Sheet

  • A "session" is made up of six games:

Session Games

  • During a day, there may be many sessions. For my purposes, the number of sessions per day is seven.
  • Between sessions, there may be a special game that requires the winner to have all squares on a Bingo card (known as a "black out" or "cover all"). Normally, a single Bingo card is provided for this intersession game.
  • The Bingo cards in play may have stars printed on them, allowing a player to call "stars" when the numbers with stars on all three cards on a sheet have been called.

Information required to determine how many Bingo cards to print is dependent on the following:

  • How many Bingo cards can be printed on a sheet? A Bingo card has the dimensions 4 x 4-1/2 inches. On a 8-1/2 x 11 inch sheet, four Bingo cards can be printed; on a 8-1/2 x 14 inch, six Bingo cards can be printed.

Paper Sizes

  • Note that in the commercial printing of Bingo cards, paper sheets are multiples of 8-1/2 x 14 inch. However, a home printer can usually only print a 8-1/2 x 11 inch sheet. For this project, a default sheet size of 8-1/2 x 14 inches will be used. This then means that six Bingo cards can be printed on a sheet.
  • How many games are in a session? Alternately, how many colors are used to color sheets? The colors that are offered in this project are the following:

Bingo Card Colors

  • The number of colors selected determines the number of games in a session. In this project, at least one color and up to all six colors may be chosen; there is no default.
  • The number of sessions that will be played in a day needs to be specified.
  • Finally, the number of people who will be playing Bingo on this day needs to be specified.

1.3. Bingo Card Dimensions Table of Contents

The most difficult part of the Print_Cards project was determining the dimensions of a Bingo card and the positional relationship of one card with another. After some experimenting, the following figure depicts these dimensions.

Dimensions

These dimensions are defined in the Utilities project in the class GDI_Card_Drawing_Data.

C#
using System.Drawing;

namespace Utilities
    {

    // *********************************** class GDI_Card_Drawing_Data

    public class GDI_Card_Drawing_Data
        {

                                        // card drawing data
        public  const   int     CARD_HEIGHT = 324;              // 4.5"
        public  const   int     CARD_WIDTH = 288;               // 4"

        public  const   int     X_OFFSET = 18;                  // 0.25"
        public  const   int     Y_OFFSET = 18;                  // 0.25"
        public  static  Point   CARDS_ORIGIN = new Point ( X_OFFSET,
                                                           Y_OFFSET );

                                        // header (B) drawing definitions
        public  static  Point   HEADER_OFFSET = new Point ( 9,  // 0.125"
                                                            9 );// 0.125"
        public  const   int     HEADER_WIDTH = 54;              // 0.75"
        public  const   int     HEADER_HEIGHT = 36;             // 0.5"
        public  const   int     HEADER_FONT_SIZE = 25;
        public  static  Color   HEADER_BRUSH_COLOR = Color.White;

                                        // card white rectangle (R)
        public  const   int     RECTANGLE_OFFSET_X = 9;         // 0.125"
        public  const   int     RECTANGLE_OFFSET_Y = 36;        // 0.5"
        public  static  Point   RECTANGLE_OFFSET = 
                                    new Point ( RECTANGLE_OFFSET_X,  
                                                RECTANGLE_OFFSET_Y );

                                        // card definitions
        public  const   int     CARDS_OFFSET_X = CARD_WIDTH + 
                                                 X_OFFSET;
        public  const   int     CARDS_OFFSET_Y = CARD_HEIGHT;
        public  const   int     CARDS_PER_SHEET_4 = 4;
        public  const   int     CARDS_PER_SHEET_6 = 6;

                                        // cell definitions
        public  const   int     CELL_HEIGHT = 54;               // 0.75"
        public  static  Point   CELLS_ORIGIN = new Point ( X_OFFSET, 
                                                           36 );// 0.5"
        public  const   int     CELL_WIDTH = 54;                // 0.75"

        public  const   int     CELLS_PER_COLUMN = 5;
        public  const   int     CELLS_PER_ROW = 5;

                                        // footer (F) definitions
        public  const   int     FOOTER_OFFSET_Y = RECTANGLE_OFFSET_Y + 
                                          ( CELLS_PER_COLUMN * 
                                            CELL_HEIGHT );
        public  static  Point   FOOTER_ORIGIN = 
                                    new Point ( RECTANGLE_OFFSET_X,
                                                FOOTER_OFFSET_Y );
        public  const   int     FOOTER_WIDTH = ( CELLS_PER_ROW *// 3.75"
                                                 CELL_WIDTH );
        public  const   int     FOOTER_HEIGHT = CARD_HEIGHT -   // 0.35"
                                        HEADER_HEIGHT -
                                        ( CELLS_PER_COLUMN * 
                                          CELL_HEIGHT );

        } // class GDI_Card_Drawing_Data

    } // namespace Utilities

The units for the various positional constants is Points, required by the PDFSharp [^] library. The values in inches are provided to the right as a comment.

Because stars may be requested, the Bingo cards are printed in the order P0, P2, P4, P1, P3, P5. This causes the group_face number to increase down the game sheet from the top Bingo card to the bottom Bingo card.

Speaking of stars, they are drawn within a cell of a Bingo card using the dimensions in the following figure

Star Dimensions

2. Printing the Bingo Cards Table of Contents

The Print_Cards program prints specified Bingo cards from a Bingo card file that was earlier generated by the Generate_Cards program.

In addition to the name of the desired input Bingo card file, cards_per_sheet, games_per_session, sessions_per_day, and number_of_players, Print_Cards must be told where, within the Bingo card file, the first Bingo card for a particular execution is found (starting_face_number).

Recall from Part 1, the Bingo cards file is a direct access file and that the formula used to access a specific record is:

C#
// in Utilities.Constants with a using statement
// using CONST = Utilities.Constants;

public  const   int     CARD_DATA_VALUES = 24;

                                // following define a card
public  const   int     CARD_VALUES_AT = 128;
public  const   int     CARD_VALUES_SIZE = 
                                CARD_DATA_VALUES * 
                                sizeof ( byte );
public  const   int     CARD_SIZE = CARD_VALUES_SIZE;

// in local compilation unit

long        file_offset = CONST.CARD_VALUES_AT +
                          ( ( face_number - 1 ) *
                            CONST.CARD_VALUES_SIZE );

where initially face_number is set equal to starting_face_number.

2.1. Card_File_IO Class Table of Contents

To obtain the Bingo cards used for generating the PDF file, the FileStream Class [^] is used. The various methods needed for retrieving the card file are encapsulated in the Card_File_IO class in the Utilities project. Note that writing the PDF file is the responsibility of the PDFSharp library.

C#
using System;
using System.IO;
using System.Linq;

using CONST = Utilities.Constants;

namespace Utilities
    {

    // ******************************************** class Card_File_IO

    public class Card_File_IO
        {
                                        // the FileStream fs is known 
                                        // only within the 
                                        // Card_File_IO class
        private FileStream   fs = null;


        // ************************************************* open_file

        /// <summary>
        /// open an existing FileStream for reading and writing
        /// </summary>
        /// <param name="path">
        /// fully qualified path of the file to create
        /// </param>
        /// <param name="error_message">
        /// reference to a string that will receive the error message 
        /// from the IO system if the open process fails
        /// </param>
        /// <returns>
        /// true, if the file is opened successfully; false otherwise
        /// </returns>
        public bool open_file (     string path,
                                ref string error_message )
            {
            bool        success = false;

            error_message = String.Empty;
            try
                {
                fs = new FileStream ( path, 
                                      FileMode.Open,
                                      FileAccess.ReadWrite, 
                                      FileShare.Read );
                success = true;
                }
            catch ( Exception e )
                {
                error_message = e.Message;
                success = false;
                }

            return ( success );

            } // open_file

        // *************************************** read_card_from_file

        /// <summary>
        /// reads a specific card from the card file
        /// </summary>
        /// <param name="card_buffer">
        /// reference to a byte array that is to receive the card
        /// </param>
        /// <param name="file_offset">
        /// long value specifying the location within the file where 
        /// reading will begin
        /// </param>
        /// <param name="error_message">
        /// reference to a string that will receive the error message 
        /// from the IO system if the read process fails
        /// </param>
        /// <returns>
        /// integer containing the number of bytes read, if the read 
        /// is successful; otherwise, zero if the end of file is 
        /// reached
        /// </returns>
        /// <note>
        /// the file must have been successfully opened or created by 
        /// an earlier invocation of open_file or create_file
        /// </note>
        public int read_card_from_file ( 
                                    ref byte [ ]  card_buffer,
                                        long      file_offset,
                                    ref string    error_message )
            {
            int   bytes_read = 0;

            card_buffer = Enumerable.Repeat ( 
                                    ( byte ) 0, 
                                    card_buffer.Length ).ToArray ( );

            if ( fs != null )
                {
                try
                    {
                    fs.Seek ( file_offset, SeekOrigin.Begin );
                    bytes_read = fs.Read ( card_buffer, 
                                           0, 
                                           card_buffer.Length );
                    }
                catch ( Exception ex )
                    {
                    error_message = ex.Message;
                    bytes_read = -1;
                    }
                }

            return ( bytes_read );

            } // read_card_from_file

        // ************************************** read_bytes_from_file

        /// <summary>
        /// reads bytes the card file
        /// </summary>
        /// <param name="bytes">
        /// reference to a byte array that is to receive the bytes
        /// </param>
        /// <param name="bytes_offset">
        /// integer value specifying the location within the byte 
        /// array into which the bytes will begin to be read
        /// </param>
        /// <param name="bytes_count">
        /// integer value containing the number of bytes to read
        /// </param>
        /// <param name="file_offset">
        /// long value specifying the location within the file where 
        /// reading will begin
        /// </param>
        /// <param name="error_message">
        /// reference to a string that will receive the error message 
        /// from the IO system if the read process fails
        /// </param>
        /// <returns>
        /// true, if the file is read successfully; false otherwise
        /// </returns>
        /// <note>
        /// the file must have been successfully opened or created by 
        /// an earlier invocation of open_file or create_file
        /// </note>
        public bool read_bytes_from_file (     
                                            byte [ ]  bytes,
                                            int       bytes_offset,
                                            int       bytes_count,
                                            long      file_offset,
                                        ref string    error_message )
            {
            bool    success = false;

            error_message = String.Empty;
            if ( fs != null )
                {
                try
                    {
                    fs.Seek ( file_offset, SeekOrigin.Begin );
                    fs.Read ( bytes, bytes_offset, bytes_count );                    
                    success = true;
                    }
                catch ( Exception e )
                    {
                    error_message = e.Message;
                    success = false;
                    }
                }

            return ( success );

            } // read_bytes_from_file

        // ************************************************ close_file

        /// <summary>
        /// closes the open card file
        /// </summary>
        /// <param name="error_message">
        /// reference to a string that will receive the error message 
        /// from the IO system if the close process fails
        /// </param>
        /// <returns>
        /// true, if the file is closed successfully; false otherwise
        /// </returns>
        /// <note>
        /// the file must have been successfully opened or created by 
        /// an earlier invocation of open_file or create_file
        /// </note>
        public bool close_file ( ref string error_message)
            {
            bool    success = false;

            error_message = String.Empty;
            try 
                {
                if ( fs != null )
                    {
                    fs.Close ( );
                    }
                success = true;
                }
            catch ( Exception e )
                {
                error_message = e.Message;
                success = false;
                }

            return ( success );

            } // close_file

        // *************************************************** Dispose

        /// <summary>
        /// disposes the card file
        /// </summary>
        /// <param name="error_message">
        /// reference to a string that will receive the error message 
        /// from the IO system if the disposal process fails
        /// </param>
        /// <returns>
        /// true, if the file is disposed successfully; false,
        /// otherwise
        /// </returns>
        public bool Dispose ( ref string error_message )
            {
            bool    success = false;

            error_message = String.Empty;
            try 
                {
                if ( fs != null )
                    {
                    fs.Dispose ( );
                    fs = null;
                    }
                success = true;
                }
            catch ( Exception e )
                {
                error_message = e.Message;
                success = false;
                }

            return ( success );

            } // Dispose

        } // class Card_File_IO

    } // namespace Utilities

As with Generate_Cards, the FileStream (fs) is private to the Card_File_IO class and within the Print_Cards program, it is created once and closed by the application_cleanup event handler, triggered by the application exit.

C#
// *************************************** application_cleanup

void application_cleanup ( object    sender,
                           EventArgs e )
    {

    cfio.close_file ( ref error_message );
    cfio.Dispose ( ref error_message );

    } // application_cleanup

There are really no surprises in the Card_File_IO class. All of the methods contained therein are simple and straight-forward.

2.2. Generating the Cards Table of Contents

In discussing the layout of the printed Bingo card, it is useful to recall the relationship between the ordering of the Bingo card data in the Bingo card file and the ordering of the cells on the printed Bingo card. It is best summarized by the following figure:

Bingo Card Indexing

The grayed-out square in the center of the Bingo card is the "free space" cell and is considered already called. face_number is used to access a specific card. Generate_Cards replaces the placeholders 0 ... 24 by random numbers. It is the responsibility of Print_Cards to produce a correctly colored Bingo card in a format that can be printed.

Once required data has been collected, Print_Cards validates the input with entries_valid.

C#
// ********************************************* entries_valid

bool entries_valid ( )
    {

    if ( cards_per_sheet4_RB.Checked )
        {
        cards_per_sheet = CDD.CARDS_PER_SHEET_4;
        }
    else 
        {
        cards_per_sheet = CDD.CARDS_PER_SHEET_6;
        }

    number_of_colors = PDF_desired_colors.Count;
    if ( number_of_colors <= 0 )
        {
        display_in_message_RTB ( 
            "At least one color must be specified" );
        return ( false );
        }

    display_stars = display_stars_CHKBX.Checked;

    bingo_cards_per_game = 
        ( int ) bingo_cards_per_game_NUD.Value;
        
    games_per_session = 
        Convert.ToInt32 ( games_per_session_TB.Text );
    sessions_per_day = ( int ) sessions_per_day_NUD.Value;
    number_of_players = ( int ) number_of_players_NUD.Value;

    starting_face_number = 
    Convert.ToInt32 ( starting_face_number_TB.Text );
    if ( starting_face_number <= 0 )
        {
        display_in_message_RTB ( 
            "The starting number must be greater than zero" ); 
        return ( false );
        }

    series_number = Convert.ToInt32 ( series_number_TB.Text );
    if ( series_number <= 0 )
        {
        series_number = date_created;
        series_number_TB.Text = series_number.ToString ( );
        }

    total_cards = bingo_cards_per_game * 
                  games_per_session * 
                  sessions_per_day *
                  number_of_players;

    if ( total_cards > number_cards )
        {
        display_in_message_RTB ( 
            String.Format (
            "Total number of cards to print ({1}){0}" +
            "is greater than the number of cards ({2}){0}" +
            "in the Bingo card file.",
            Environment.NewLine,
            total_cards,
            number_cards ) );
        return ( false );
        }

    if ( ( total_cards + starting_face_number ) > 
         number_cards )
        {
        display_in_message_RTB ( 
            String.Format (
            "Total number of cards to print ({1}){0}" +
            "plus 'Starting Face Number' is greater than " +
                "the number{0}" +
            "of cards ({2}){0} in the Bingo card file.",
            Environment.NewLine,
            total_cards,
            number_cards ) );
        return ( false );
        }

    display_pdf = false;
    if ( can_display_pdf )
        {
        display_pdf = display_pdf_CHKBX.Checked;
        }

    ending_face_number = starting_face_number +
                         total_cards - 1;

    return ( true );

    } // entries_valid

2.3. PDFSharp Library Table of Contents

PDFSharp is the Open Source .NET library that easily creates and processes PDF documents on the fly from any .NET language. The same drawing routines can be used to create PDF documents, draw on the screen, or send output to any printer.

PDFSharp [^]

The PDFSharp library is available at SourceForge [^]. I downloaded it to C:\Downloads\PDFSharp and referred to its .DLL at:

C:\Downloads\
  PDFSharp\
    pdfsharp\
      sourceCode\
        sourceCode\
          PDFsharp\
            code\
              PdfSharp\
                bin\
                  Debug\
                    PdfSharp.dll

Once the project references were specified, the following using statements could be included in Print_Card.cs:

C#
using PdfSharp;
using PdfSharp.Drawing;
using PdfSharp.Pdf;

Throughout Print_Cards are invocations of the PDFSharp library. The following were encapsulated in helper routines. open_PDF_document is invoked once; add_a_PDF_page and close_PDF_page are invoked for each PDF sheet produced.

C#
// ***************************************** open_PDF_document

void open_PDF_document ( string  title )
    {

    PDF_document = new PdfDocument ( );
    PDF_document.Info.Title = title;

    } // open_PDF_document

// ***************************************** open_PDF_document

void open_PDF_document ( )
    {

    open_PDF_document ( "Bingo Cards" );

    } // open_PDF_document

// ******************************************** add_a_PDF_page

void add_a_PDF_page ( )
    {

    PDF_page = PDF_document.AddPage ( );
    PDF_page.Orientation = PageOrientation.Portrait;
    PDF_page.Size = PageSize.Legal;

    PDF_graphics = XGraphics.FromPdfPage ( PDF_page );

    } // add_a_PDF_page

// ******************************************** close_PDF_page

void close_PDF_page ( )
    {

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

    if ( PDF_page != null )
        {
        PDF_page.Close ( );
        PDF_page = null;
        }

    } // close_PDF_page

The PDF variables used in Print_Cards are:

C#
                            // PDF variables
List < XColor > PDF_desired_colors = new List < XColor > ( );
PdfDocument     PDF_document = null;
XStringFormat   PDF_format = new XStringFormat ( ) {
                        LineAlignment = XLineAlignment.Center,
                        Alignment = XStringAlignment.Center
                        };
XGraphics       PDF_graphics = null;
PdfPage         PDF_page = null;

Note that PDFSharp uses many of the same concepts as does the Graphics Class [^]. To differentiate, PDFSharp prefixes the GDI names with an "X". For example, "Graphics" becomes "XGraphics", "Color" becomes "XColor", and so forth.

2.4. Rendering the Bingo Cards Table of Contents

When entries_valid returns true, Print_Cards initiates the printing process. This process is a sequence of relatively simple methods, each of which performs a simple task:

C#
render_PDF_document
    render_Bingo_sheet
        build_bingo_card_sheet
        render_a_card
          render_colored_background
          render_header
          render_cells_and_values
            render_center_cell_contents
            render_star
          render_footer

The printing process is started by render_PDF_document that opens a new PDF document, sets the initial face_number from the starting_face_number supplied by the user, and loops over the face_numbers invoking render_Bingo_sheet for each color specified by the user.

C#
// *************************************** render_PDF_document

void render_PDF_document ( )
    {
    int     face_number = 0; 

    open_PDF_document ( );

    face_number = starting_face_number;
    while ( face_number <= ending_face_number )
        {
        foreach ( XColor xcolor in PDF_desired_colors )
            {
            render_Bingo_sheet (     xcolor,
                                 ref face_number );
            }
        }

    PDF_document.Close ( );

    } // render_PDF_document

Note that when rendering Bingo cards, Print_Cards orders the cards by color.

Collated Colors

To be able to pass the defining data about a Bingo card, the Bingo_Card class was defined.

C#
using System.Collections.Generic;

using PdfSharp.Drawing;

namespace Utilities
    {

    // ********************************************** class Bingo_Card

    public class Bingo_Card
        {

        public List < int > Card_Numbers { get; set; }
        public XPoint       Card_Origin { get; set; }
        public XColor       Card_XColor { get; set; }
        public int          Face_Number { get; set; }
        public int          Star_At { get; set; }

        // *********************************************** Bingo_Card

        public Bingo_Card ( )
            {

            Card_Numbers = null;
            Card_Origin = new XPoint ( );
            Card_XColor = XColor.Empty;
            Face_Number = 0;
            Star_At = 0;

            } // Bingo_Card

        } // class Bingo_Card

    } // namespace Utilities

The Bingo_Card class merely collects information about one Bingo card.

render_Bingo_sheet is invoked to render a single Bingo sheet that has Bingo cards printed with one color.

C#
// **************************************** render_Bingo_sheet

void render_Bingo_sheet (     XColor   xcolor,
                          ref int      face_number )
    {
    List < BC > bingo_card_sheet;
    long        file_offset = CONST.CARD_VALUES_AT +
                              ( ( face_number - 1 ) *
                                CONST.CARD_VALUES_SIZE );

    add_a_PDF_page ( );

    bingo_card_sheet = build_bingo_card_sheet (    
                                                file_offset,
                                                xcolor, 
                                            ref face_number );

    foreach ( BC card in bingo_card_sheet )
        {
        render_a_card ( card );
        progress_bar.PerformStep ( );
        }

    close_PDF_page ( );

    } // render_Bingo_sheet

By invoking add_a_PDF_page, PDFSharp begins a new PDF page, establishes the page orientation (portrait), establishes the page size (legal - 8-1/2 x 14), and creates the graphics object that will be used to draw the page. (If it has not already dawned on the reader, PDFSharp is a graphics library that produces printable pages. Familiarity with the .NET graphics library greatly assists the developer in understanding how PDFSharp operates.)

build_bingo_card_sheet, as its name implies, builds the template for a bingo_card_sheet that includes all of the data required to render a Bingo card sheet of a specific color and starting at a specific face_number.

C#
// ************************************ build_bingo_card_sheet

// generates the data necessary to render a single sheet of 
// Bingo cards (either 4 or 6 Bingo cards per sheet)

List < BC > build_bingo_card_sheet (     long    file_offset,
                                         XColor  xcolor, 
                                     ref int     face_number )
    {
    List < BC > cards = new List < BC > ( );
                                // all pages have the same 
                                // origin
    int         cards_per_sheet_div_2 = cards_per_sheet / 2;
    XPoint      origin = new XPoint ( CDD.X_OFFSET,
                                      CDD.Y_OFFSET ); 

    cards.Clear ( );
                                // cards_per_sheet is user 
                                // supplied; defaults to 6
    for ( int i = 0; ( i < cards_per_sheet ); i++ )
        {
        byte [ ]      buffer = 
                          new byte [ CONST.CARD_DATA_VALUES ];
        BC            card = new BC ( );
        List < int >  numbers;
                                // retrieve 25-byte card from 
                                // card file
        cfio.read_card_from_file ( ref buffer,
                                       file_offset,
                                   ref error_message );
        numbers = byte_array_to_int_list ( buffer );
        card.Star_At = numbers [ CONST.CENTER_CELL_INDEX ];
                                // replace star_at with the 
                                // face_number
        numbers.RemoveAt ( CONST.CENTER_CELL_INDEX ); 
        numbers.Insert ( CONST.CENTER_CELL_INDEX, 
                         face_number );

        card.Card_XColor = xcolor;
        card.Card_Numbers = numbers;
        card.Card_Origin = origin;
        card.Face_Number = face_number;

        cards.Add ( card );

        origin.Y += CDD.CARD_HEIGHT;
        if ( i == ( cards_per_sheet_div_2 - 1 ) )
            {
            origin.X += CDD.CARD_WIDTH;
            origin.Y = CDD.Y_OFFSET;
            }

        file_offset += CONST.CARD_SIZE;
        face_number++;
        }

    return ( cards );

    } // build_bingo_card_sheet

build_bingo_card_sheet invokes byte_array_to_int_list to convert the array buffer to a list. Lists are significantly easier to work with than arrays. When build_bingo_card_sheet returns, the bingo_card_sheet has been filled with the information needed to write a PDF page, accomplished by invoking render_a_card for each card in the bingo_card_sheet.

C#
// ********************************************* render_a_card

void render_a_card ( BC  card )
    {
    int     color_index = 0;
    XPoint  footer_origin = card.Card_Origin;
    string  group_face = String.Empty;
    XPoint  rectangle_origin = card.Card_Origin;

    render_colored_background ( card );

    render_header ( card );

    rectangle_origin.X += CDD.RECTANGLE_OFFSET.X;
    rectangle_origin.Y += CDD.RECTANGLE_OFFSET.Y;
    render_card_rectangle ( rectangle_origin );

    color_index = 
        PDF_card_colors.IndexOf ( card.Card_XColor );
    group_face = 
        ( color_index + 1 ).ToString ( ) + 
        card.Face_Number.ToString ( ).PadLeft ( 3, '0' );

    render_cells_and_values ( rectangle_origin,
                              card,
                              group_face );

    footer_origin.X += CDD.RECTANGLE_OFFSET.X;
    footer_origin.Y += CDD.RECTANGLE_OFFSET.Y +
                       ( CDD.CELLS_PER_COLUMN * 
                         CDD.CELL_HEIGHT );
    render_footer ( footer_origin, group_face );

    } // render_a_card

It is within render_a_card that the sequence, mentioned earlier, occurs.

render_colored_background fills the 4 x 4-1/2 inch rectangle that is the Bingo card background with the specified color.   render_header draws the Bingo card header ("BINGO").
Render Colored Background   Render Header
render_card_rectangle draws the white rectangle that will contain all of the Bingo card squares.   render_cells_and_values draws the Bingo card squares, fills the center square with the free space annotation, displays the star, if any, for this Bingo card, and fills the squares with the Bingo card numbers.
Render Card Rectangle   Render Cells and Values
Finally, render_footer places the group_face and series_number at the bottom of the card.
Render Footer

As with the .NET Graphics library, items that are drawn later overwrite items that were drawn earlier.

3. Executing Print_Cards Table of Contents

There are a number of values needed to print Bingo cards: the filename of the Bingo card file, generated by Generate_Cards, that is the source of Bingo card data, card printing specifications, and the name of the PDF file that is to contain the Bingo card images.

First, Print_Cards queries for the location where the Bingo card file is to be found.

Print Cards

To avoid permission issues, the default directory for Print_Cards is C:\Users\Public\Bingo. The file may be located on a network drive.

Once an input Bingo card file is agreed, the statistics for the specified input Bingo card file and the Card Printing portion of the user interface are displayed.

Print Cards

The value in "Games per Session" reflects the number_of_colors chosen. At least one color must be chosen. As colors are chosen (i.e., as the colored square is clicked), the border around the color square turns green, games_per_session increments, and the color is added to the PDF_desired_colors list. If a chosen color square is clicked again, the green border is removed, games_per_session decrements, and the color is removed from the PDF_desired_colors list.

The "Starting Face Number" defaults to 1. It may take on any value such that:

            ( total_cards + starting_face_number ) <= number_cards 
where
            total_cards = bingo_cards_per_game * 
                          games_per_session * 
                          sessions_per_day *
                          number_of_players;

The "Series Number" is a user supplied value that will be printed on each Bingo card generated. Its default value is the date on which Print_Cards is executed. Checking "Display Stars" will cause Print_Cards to generate a random star on each Bingo card.

When all of the card printing specifications have been collected and validated, the Go button is clicked and Print_Cards generates the contents of the PDF file. The actual generation of the values that go into a card is very fast. However, Print_Cards invokes the PDFSharp library routine PDF_document.Save that appears to take an inordinately long time. There is no event in PDF_document.Save to which we can subscribe to drive a progress bar. So there may be a delay between clicking Go and seeing the following window.

When the PDF file contents have been generated, Print_Cards queries for the name of the PDF file into which to save the generated Bingo cards.

Print Cards

I'd recommend that the name include the series number.

Print Cards

If "Display PDF" was checked, Print_Cards will display the PDF file using the default PDF reader. The 337th sheet of 630 sheets, generated by the preceding steps, is:

PDF File

The display of the PDF file is independent of the execution of Print_Cards so the Print_Cards Exit button may be clicked once the PDF file is displayed..

4. Conclusion Table of Contents

Print_Cards produces a PDF file containing a specified number of unique Bingo cards. The next step is to print these cards and use them in playing Bingo. This is the subject of the third and last article in this series.

5. References Table of Contents

6. 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

7. History Table of Contents

Bingo - Part 2 of 3 - Print Cards 02/20/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 --