宾果游戏套件 - 第 2 部分






4.82/5 (4投票s)
Print_Cards 用于生成包含指定数量的独特宾果卡的 PDF 文件
目录
1. 引言     
 
本文是关于我在过去几个月开发的宾果游戏套件的三篇文章中的第二篇。发表这些文章是希望该软件能对读者有所帮助。这三篇文章讨论的是有 75 个数字球的美国版宾果游戏。
如果读者需要回顾宾果游戏规则,请参考本系列文章的第一篇文章。
为了能够进行宾果游戏,有必要生成并打印玩家将使用的宾果卡。
本文讨论了上图中的加粗项目。宾果卡文件必须已由 Generate_Cards [^] 程序生成。
1.1. 打印的宾果卡     
 
打印的宾果卡由六行五列组成。第一行是卡片标题,包含字母“B”、“I”、“N”、“G”和“O”,每个字母出现在自己的列中。其余五行包含数字值。当插入 24 个随机数字时,卡片可能如下所示:
|   |   | 
右图的创建是为了满足在宾果卡上分布星形的要求。当满足此要求时,每张宾果卡上都会随机放置一个星形。星形在每张卡片上的位置都记录在宾果卡文件中。
5x5 卡的中心方格被称为“免费格”,被视为已叫出。
实际上,由于我是在家用电脑构建这个套件的,所以我决定应遵循以下设计标准:
- 程序的输出文件格式将是便携式文档格式 (PDF)。
- 将打印 PDF 图像的纸张尺寸为 8-1/2 x 11 或 8-1/2 x 14 英寸。
1.2. 卡片数量     
 
宾果游戏在许多地点进行,每个地点都有自己的规则。在本文中,我将提出以下规则:
- 一个“游戏”由打印在一张纸上的三张宾果卡组成。纸上的每张宾果卡都打印成相同的颜色。
- 一个“会话”由六个游戏组成。
- 一天中可能有很多会话。对我而言,每天的会话次数是七次。
- 在会话之间,可能会有一个特殊的游戏,要求获胜者填满宾果卡上的所有方格(称为“全黑”或“全覆盖”)。通常,为这个会话间游戏提供一张宾果卡。
- 正在使用的宾果卡上可能印有星形,允许玩家在纸上的三张卡片上所有带星形的数字都被叫出时喊出“星形”。
确定需要打印多少张宾果卡的所需信息取决于以下因素:
- 一张纸可以打印多少张宾果卡?一张宾果卡尺寸为 4 x 4-1/2 英寸。在 8-1/2 x 11 英寸的纸上可以打印四张宾果卡;在 8-1/2 x 14 英寸的纸上可以打印六张宾果卡。
- 请注意,在商业印刷宾果卡时,纸张尺寸是 8-1/2 x 14 英寸的倍数。但是,家用打印机通常只能打印 8-1/2 x 11 英寸的纸张。对于本项目,将使用默认的 8-1/2 x 14 英寸纸张尺寸。这意味着一张纸可以打印六张宾果卡。
- 一个会话中有多少游戏?或者,使用了多少种颜色来为纸张着色?本项目提供的颜色如下:
- 选择的颜色数量决定了会话中的游戏数量。在此项目中,可以选择一种颜色到所有六种颜色;没有默认值。
- 需要指定一天中要玩多少会话。
- 最后,需要指定当天将有多少人玩宾果游戏。
1.3. 宾果卡尺寸     
 
Print_Cards 项目中最困难的部分是确定宾果卡的尺寸以及卡片之间的位置关系。经过一些实验,下图描述了这些尺寸。
这些尺寸定义在 Utilities 项目的 GDI_Card_Drawing_Data 类中。
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
各种位置常数的单位是“点”,这是 PDFSharp [^] 库所必需的。英寸值作为注释提供在右侧。
由于可能需要打印星形,因此宾果卡按 P0、P2、P4、P1、P3、P5 的顺序打印。这会导致 group_face 数字在游戏纸张上从顶部的宾果卡向下增加。
说到星形,它们是使用下图中的尺寸在宾果卡的单元格内绘制的。
2. 打印宾果卡     
 
Print_Cards 程序从之前由 Generate_Cards 程序生成的宾果卡文件中打印指定的宾果卡。
除了所需的输入宾果卡文件名、cards_per_sheet、games_per_session、sessions_per_day 和 number_of_players 之外,Print_Cards 还必须知道在宾果卡文件中,特定执行的第一个宾果卡的位置(starting_face_number)。
回想一下,在 第一部分 中,宾果卡文件是一个直接访问文件,用于访问特定记录的公式是:
// 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 );
其中最初 face_number 设置为等于 starting_face_number。
2.1. Card_File_IO 类     
 
为了获取用于生成 PDF 文件的宾果卡,使用了 FileStream 类 [^]。检索卡片文件所需的各种方法封装在 Utilities 项目的 Card_File_IO 类中。请注意,PDF 文件的写入由 PDFSharp 库负责。
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
与 Generate_Cards 一样,FileStream (fs) 对 Card_File_IO 类是私有的,在 Print_Cards 程序中,它被创建一次,并通过应用程序退出时触发的 application_cleanup 事件处理程序关闭。
// *************************************** application_cleanup
void application_cleanup ( object    sender,
                           EventArgs e )
    {
    cfio.close_file ( ref error_message );
    cfio.Dispose ( ref error_message );
    } // application_cleanup
Card_File_IO 类中确实没有令人惊讶的地方。其中包含的所有方法都简单明了。
2.2. 生成卡片     
 
在讨论打印的宾果卡布局时,回顾宾果卡文件中宾果卡数据的排序与打印宾果卡上单元格的排序之间的关系很有用。最好通过下图进行总结:
宾果卡中心的灰色方格是“免费格”单元格,被视为已叫出。face_number 用于访问特定卡片。Generate_Cards 将占位符 0...24 替换为随机数。Print_Cards 的职责是生成正确着色的宾果卡,并以可打印的格式输出。
一旦收集了 所需数据,Print_Cards 就会使用 entries_valid 来验证输入。
// ********************************************* 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 库     
 
PDFSharp 是一个开源 .NET 库,可以从任何 .NET 语言轻松地即时创建和处理 PDF 文档。相同的绘图例程可用于创建 PDF 文档、在屏幕上绘图或将输出发送到任何打印机。
PDFSharp 库可在 SourceForge [^] 上找到。我将其下载到C:\Downloads\PDFSharp 并引用了其.DLL,位于
    C:\Downloads\
      PDFSharp\
        pdfsharp\
          sourceCode\
            sourceCode\
              PDFsharp\
                code\
                  PdfSharp\
                    bin\
                      Debug\
                        PdfSharp.dll
一旦指定了项目引用,就可以在 Print_Card.cs 中包含以下 using 语句:
using PdfSharp;
using PdfSharp.Drawing;
using PdfSharp.Pdf;
Print_Cards 中到处都是 PDFSharp 库的调用。以下内容封装在辅助例程中。 open_PDF_document 被调用一次; add_a_PDF_page 和 close_PDF_page 为每个生成的 PDF 纸张调用。
// ***************************************** 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
Print_Cards 中使用的 PDF 变量是:
                            // 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;
请注意,PDFSharp 使用了许多与 Graphics 类 [^] 相同的概念。为了区分,PDFSharp 在 GDI 名称前加上“X”。例如,“Graphics”变成“XGraphics”,“Color”变成“XColor”,依此类推。
2.4. 渲染宾果卡     
 
当 entries_valid 返回 true 时,Print_Cards 开始打印过程。这个过程是一系列相对简单的方法,每种方法执行一个简单的任务:
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
打印过程由 render_PDF_document 启动,该过程打开一个新的 PDF 文档,设置用户提供的 starting_face_number 的初始 face_number,并遍历 face_numbers,为用户指定的每种颜色调用 render_Bingo_sheet。
// *************************************** 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
请注意,在渲染宾果卡时,Print_Cards 按颜色排序卡片。
为了能够传递宾果卡的定义数据,定义了 Bingo_Card 类。
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
Bingo_Card 类仅收集一张宾果卡的信息。
调用 render_Bingo_sheet 来渲染一张单张宾果纸,上面印有某种颜色的宾果卡。
// **************************************** 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
通过调用 add_a_PDF_page,PDFSharp 开始一个新的 PDF 页面,设置页面方向(纵向),设置页面大小(法律尺寸 - 8-1/2 x 14),并创建用于绘制页面的图形对象。(如果读者还没有意识到,PDFSharp 是一个图形库,它会生成可打印的页面。熟悉 .NET 图形库有助于开发人员理解 PDFSharp 的工作原理。)
build_bingo_card_sheet 正如其名称所示,它构建了一个 bingo_card_sheet 的模板,其中包含渲染特定颜色和从特定 face_number 开始的宾果卡片的所需数据。
// ************************************ 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 调用 byte_array_to_int_list 将字节数组转换为列表。列表比数组更容易处理。当 build_bingo_card_sheet 返回时,bingo_card_sheet 已填充了写入 PDF 页所需的信息,通过为 bingo_card_sheet 中的每张卡片调用 render_a_card 来完成。
// ********************************************* 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
在 render_a_card 中,会发生前面提到的 顺序。
与 .NET Graphics 库一样,后绘制的项目会覆盖先绘制的项目。
3. 执行 Print_Cards     
 
打印宾果卡需要许多值:由 Generate_Cards 生成的宾果卡文件的文件名(它是宾果卡数据的来源)、卡片打印规格以及要包含宾果卡图像的 PDF 文件的名称。
首先,Print_Cards 查询宾果卡文件的存储位置。
为避免权限问题,Print_Cards 的默认目录是C:\Users\Public\Bingo。该文件可能位于网络驱动器上。
一旦确定了输入宾果卡文件,就会显示指定输入宾果卡文件的统计信息和用户界面的卡片打印部分。
“每会话游戏数”的值反映了所选的 number_of_colors。至少必须选择一种颜色。当选择颜色时(即单击彩色方块时),彩色方块周围的边框会变成绿色,games_per_session 递增,并将颜色添加到 PDF_desired_colors 列表中。如果再次单击选定的颜色方块,绿色边框会消失,games_per_session 递减,并且颜色会从 PDF_desired_colors 列表中移除。
“起始面数”默认为 1。它可以取任何使得以下条件成立的值:
            ( total_cards + starting_face_number ) <= number_cards 
where
            total_cards = bingo_cards_per_game * 
                          games_per_session * 
                          sessions_per_day *
                          number_of_players;
“系列号”是用户提供的值,将打印在生成的每张宾果卡上。其默认值为执行 Print_Cards 的日期。选中“显示星形”将导致 Print_Cards 在每张宾果卡上生成一个随机星形。
当收集并验证完所有卡片打印规格后,单击“开始”按钮,Print_Cards 将生成 PDF 文件的内容。进入卡片的值的实际生成速度非常快。但是,Print_Cards 调用 PDFSharp 库例程 PDF_document.Save,该例程似乎需要异常长的时间。 PDF_document.Save 中没有可以订阅来驱动进度条的事件。因此,单击“开始”和看到以下窗口之间可能会有延迟。
当 PDF 文件内容生成完毕后,Print_Cards 会查询要将生成的宾果卡保存到的 PDF 文件的名称。
我建议名称中包含系列号。
如果选中了“显示 PDF”,Print_Cards 将使用默认的 PDF 阅读器显示 PDF 文件。通过上述步骤生成的 630 张纸中的第 337 张是:
PDF 文件的显示独立于 Print_Cards 的执行,因此一旦显示了 PDF 文件,就可以单击 Print_Cards 的退出按钮。
4. 结论     
 
Print_Cards 生成一个包含指定数量的独特宾果卡的 PDF 文件。下一步是打印这些卡片并在玩宾果游戏时使用它们。这是本系列第三篇也是最后一篇文章的主题。
5. 参考资料     
 
- FileStream 类 [^]
- Generate_Cards [^]
- Graphics 类 [^]
- PDFSharp [^]
- SourceForge PDFSharp 文件 [^]
6. 开发环境     
 
本文介绍的软件是在以下环境中开发的
| Microsoft Windows 7 Professional Service Pack 1 | 
| Microsoft Visual Studio 2008 Professional | 
| Microsoft .NET Framework 版本 3.5 SP1 | 
| Microsoft Visual C# 2008 | 
7. 历史     
 
	| 宾果 - 第 2 部分(共 3 部分)- 打印卡片 | 02/20/2023 | 原始文章 | 



 
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
 