65.9K
CodeProject 正在变化。 阅读更多。
Home

宾果游戏套件 - 第 3 部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2023年3月29日

CPOL

15分钟阅读

viewsIcon

6544

downloadIcon

815

本文是宾果游戏套件系列的第三篇,也是最后一篇。

1. 介绍 目录

最后,本文是描述我过去几个月开发的宾果游戏套件的三篇文章中的第三篇。发布这些文章是希望这些软件能对读者有所帮助。这三篇文章都涉及75球版本的宾果。

Overview

本文讨论了前面图中加粗的项目。宾果卡文件必须已经由 Generate_Cards [^] 程序生成,并且一套宾果卡必须已经由 Print_Cards [^] 程序打印。

2. 玩宾果 目录

宾果游戏在北美数以千计的大小地点进行。本文中玩(和讨论)的游戏是75球版本的宾果(而非90球版本)。

2.1. 背景 目录

玩家购买的宾果游戏卡通常每张纸印三张卡片。每张纸构成一个游戏,并以不同的颜色打印。一个会话通常由六张不同颜色的纸组成。一天中可能会有许多会话。

Cards per Sheet

叫号员(游戏主持人)随机从一个漏斗中抽出球。漏斗中的每个球都编号从1到75,并且通常根据其上的数字着色

  • 蓝色      1 - 15
  • 红色     16 - 30
  • 白色  31 - 45
  • 绿色  46 - 60
  • 黄色 61 - 75

Ball Colors

叫号员宣布抽出的球的号码。如果抽到37号球,号码将宣布为“N 37”。在大多数地方,球被放入板上的编号孔中。当球插入时,叫号板上的号码(在这种情况下是37)会亮起。

Called Board

2.2. 模式 目录

通常,仅仅在你的卡片上有叫到的号码并不能赢得游戏。叫到的号码通常必须以叫号员在游戏开始时宣布的模式出现。就本文而言,模式有两种类型:静态和动态。请注意,本文并未列举所有可能的模式;相反,它仅讨论少数几种以作演示。Roanoke VA Moose Lodge 有一套相当完整的 宾果模式 [^]。

描述模式的最佳方式是展示一些示例。以下是本文所述软件支持的静态模式。

Static Patterns

以下是本文所述软件支持的动态模式。

Dynamic Patterns

这些模式显示了宾果卡上叫到的数字必须出现的位置才能获胜。

2.3. 星星 目录

带星星的宾果游戏是正常宾果游戏的补充。它的引入是为了增加宾果游戏初始阶段的刺激性。

Stars

在正常的宾果游戏中,如果玩家能在前三个叫到的号码中标记出纸张上的所有三颗星,玩家就喊“星星”。如果纸张上的宾果卡包含有效的星星模式,玩家将获得奖品,宾果游戏继续进行。

2.4. 模拟游戏 目录

为了将所有内容整合起来,我们来模拟一局游戏。假设

  • 叫号员要求游戏在浅珊瑚色的宾果卡上进行(这意味着group_face必须以“2”开头)
  • 模式是“字母X”
  • 已叫出17个号码
  • 叫号板看起来像

Game Called

红色值是最后叫到的球,该号码必须在获胜的宾果卡上。现在我们来看看玩家的宾果卡。

Played Bingo Card

绿色圆圈是玩家在每个号码被叫出后在卡片上做的标记。海贝色圆圈是最后叫出的号码。当最后一个号码被叫出时,玩家喊“宾果”,游戏暂停等待确认。玩家根据叫号员的要求,将宾果卡标识为“2066057”(中心方块中的group_face)。当叫号员输入到宾果软件中时,程序确认该宾果卡是赢家。

当然,还有其他结果,玩家喊“宾果”但没有赢:最后叫出的号码不在卡上(玩家可能在另一个号码被叫出之前没有意识到宾果——称为“睡着了”);模式中的所有位置都没有标记;group_face中的第一个数字与所玩卡片的颜色不符;等等。

3. Play_Bingo 实现 目录

3.1. 从属窗体模板 目录

此实现使用多个窗体来收集信息。我使用一个简单的模板来调用从属窗体。

从属窗体与其调用窗体位于同一目录下。从属窗体作为Windows窗体添加到项目中,并具有与调用窗体相同的命名空间。至少,从属窗体除了其构造函数之外,还必须包含一个名为initialize_form的公共方法。initialize_form的签名是

    public bool initialize_form ( )

Retrieve_And_Open_Bingo_Card_File的骨架是

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

Play_Bingo 主窗体中,Retrieve_And_Open_Bingo_Card_File 由方法 retrieve_and_open_bingo_card_file 调用

// ************************* 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

如果调用方法中需要额外的语句,可以在form.ShowDialog ( )语句之前和之后插入它们。

3.2. 确保随机性 目录

在现实世界中,宾果球的抽取是随机的,这正是由于它们所来自的设备的性质。

Capitol Envoy Bingo Console

为了模拟这种随机性,Play_Bingo 使用与 Generate_Cards 相同的 伪随机数生成器 [^] (PRNG)。

包含75个随机宾果球表面数字的列表balls,位于Utilities项目的Global_Values类中(以及许多其他全局变量)。

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 在每局游戏开始时被调用。

3.3. ThreadPauseState 目录

在我描述 Play_Bingo 中的后台工作线程之前,需要解决暂停和恢复后台线程的机制。

Play_Bingo 要求根据叫号员的命令,暂停和恢复其后台工作线程的执行。这通过叫号员点击按钮或按下注册的键盘键来发出信号。每个线程内部都有代码识别它正在被暂停。例如

using TPS = Utilities.ThreadPauseState;

    

    TPS       thread_pause_state = new TPS ( );

    

    thread_pause_state.Wait ( );

Utilities项目中的ThreadPauseState类包含管理暂停/恢复操作的方法。这些方法作为对 Monitor [^] Enter 和 Exit 方法的调用实现,这些方法获取或释放排他锁。

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

例如,在Play_Bingo执行期间,叫号员可能希望更改获胜模式。为此,叫号员点击pattern_BUT按钮或按下键盘键“P”。该按钮点击或按键的事件处理程序调用patterns方法。

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

patterns 的第一个动作是暂停 Play_Bingo 的后台工作线程的执行。

    thread_pause_state.Paused = true;

当所有更改模式所需的工作完成后,patterns 恢复 Play_Bingo 后台工作线程的执行。

    thread_pause_state.Paused = false;

每个后台工作线程中都出现

    thread_pause_state.Wait ( );

如果thread_pause_state.Pausedtrue,则线程暂停,否则线程继续执行。

3.4. 后台工作线程 目录

为了保持主用户界面(UI)的响应性,存在两个 后台工作线程 [^]:一个在叫号板上闪烁数字;另一个在pattern_PAN中显示模式(请记住模式可以是动态的)。

Play Bingo

程序初始化完成后,Play_Bingo 调用 initialize_background_workers。请注意,此方法创建后台工作线程,但不会导致它们执行;相反,必须在每个工作线程上调用 RunWorkerAsync

// ***************************** 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 目录

// ***************************************** 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_DoWorkPlay_Bingo 的主力。

  1. 如果需要,进入暂停状态
  2. GV.balls列表中抽取下一个球号ball_to_display,忽略不出现在当前模式中的球号
  3. 在显示器上绘制球的图像
  4. 朗读ball_to_display
  5. 根据GV.drawn_to_flash指定的时间(秒)休眠
  6. 如果之前有抽到的球,将之前闪烁的叫号板面板从白色前景红色背景重置为黑色前景白色背景。
  7. 调用其ReportProgress方法更新last_called_PANtotal_calls_PAN
  8. 调用flash_bingo_card_board使新球号在叫号板上闪烁
  9. 保存最后叫到的球号
  10. 如果所有球尚未抽出,则返回步骤1

由于draw_flash_DoWork位于与用户界面(UI)线程不同的线程上,因此每当需要重绘UI线程控件时,它都会调用refresh_control。通过访问控件的 InvokeRequired [^] 属性来确定是否需要控件的Invoke方法。

// ******************************************* refresh_control

void refresh_control ( Control  control )
    {

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

    } // refresh_control

refresh_control调用 control.Refresh [^] 时,控件被失效, Control.Paint [^] 事件被触发,并且控件被重绘。对于一个球,执行ball_PB_Paint 事件处理程序 [^]。

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 确定是否显示宾果标志(图标)或绘制一个编号球。在后一种情况下,它调用 draw_ball,该方法创建编号宾果球的图像。为此,draw_ball 执行以下步骤来创建要绘制的图像。

Drawing G52

请记住,后绘制的对象会覆盖先绘制的对象。

最后,ball_PB_Paint 使用 PaintEventArgs Graphics.DrawImage 来实际绘制图像。

3.4.2. draw_pattern_DoWork 目录

draw_pattern_DoWork 负责绘制在 pattern_PAN 中显示的图案。请记住,既有动态图案也有静态图案。每个图案都创建为一个包含25个字节的列表。静态图案有一个列表;动态图案则需要多少列表就拥有多少,以显示图案的所有变化。列表按需创建,并被称为模板

例如,最简单的动态模式之一是对角线模式。它的模板由Utilities项目中Build_Templates类中的build_diagonal_templates创建。

// ********************************** 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

这里,先指定向左(向下)对角线,再指定向右(向上)对角线。两个模板都添加到templates列表的列表中,并返回templates

Play_Bingo 中出现列表的声明

        List < List < byte > >  templates;

接收build_diagonal_templates调用的结果。我们希望对角线模式每隔指定的时间间隔按如下方式改变。

Moving Diagonal

类变量template_count的值决定将绘制哪个模板。retrieve_dynamic_template返回包含下一个要绘制模板的字节列表。

// ********************************* 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

pattern_PAN的Paint事件的处理程序是pattern_PAN_Paint

// ***************************************** 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

pattern_PAN_Paint 获取模板后,它通过调用 create_pattern_buffer 创建一个用于绘制模板的缓冲区。

// ************************************* 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_bufferPlay_Bingo 命名空间中的 GraphicsBuffer 类获取一个新的图形缓冲区。

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 然后调用draw_the_template将模板绘制到pattern_buffer

// ***************************************** 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

一旦模板被绘制,pattern_PAN_Paint 渲染图像。为了美观,pattern_PAN_PaintPlay_Bingo 图标更改为新图案。

3.5. 语音合成 目录

如上所述,当draw_flash_DoWork“抽出”一个新的宾果球时,它通过调用say_the_bingo_ball来宣布抽出的值。

// **************************************** 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

try-catch 结构用于捕获 Play_Bingo 正在执行的计算机没有音频输出设备的情况。在这种情况下,say_the_bingo_ball 是一个 NUL 语句。

say_the_bingo_ball 利用 SpeechSynthesizer 类 [^] 使用默认系统语音来宣布宾果球。

3.6. 确认宾果 目录

当玩家喊“宾果”时,Play_Bingo必须确定玩家的卡片是否确实是赢家。执行此测试的软件包含在Play_Bingo命名空间中的Confirm_Bingo类中。在本文的这一部分中,所有引用的代码都可以在该类中找到。

要确定宾果卡是否获胜,叫号员必须提供以下信息

  • 正在使用的宾果卡颜色
  • 玩家卡片的卡号

当提供这些项目时,Play_Bingo会验证宾果卡颜色。有多种结果

  • 成功 - 卡片颜色有效
  • 不是宾果 - 未识别的卡片颜色
  • 不是宾果 - 错误的颜色卡片

如果卡片颜色有效,Play_Bingo会提取card_number并使用它计算宾果卡文件中的card_offset。如果卡片成功检索,Play_Bingo会调用encode_user_card将卡片转换为可用于确定玩家卡片是否获胜的表示形式。

// ****************************************** 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

生成的user_card_bitmap现在表示玩家通过group_face识别的宾果卡上的已叫号码。Play_Bingo现在使用此位图来确定该宾果卡是否包含该模式。

// ************************************************** 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

 

测试

    ( ( user_card_bitmap & template ) == template )

有多种结果

  • 成功 - 卡片是赢家
  • 不是宾果 - 不匹配图案
  • 不是宾果 - 未在最后叫出的号码中

这个过程部分如下图所示。这里假设玩家将宾果卡标识为“2066011”。

Confirm Bingo

3.7. 确认星星 目录

当玩家喊“星星”时,Play_Bingo必须确定玩家的游戏纸是否包含已叫出的带星号码。执行此测试的软件包含在Play_Bingo命名空间中的Confirm_Stars类中。在本文的这一部分中,所有引用的代码都可以在该类中找到。

要确定宾果卡是否获胜,叫号员必须提供纸上最上方宾果卡的group_face。例如,在下图中

Stars

纸上最顶部的宾果卡的group_face号码为“12017”。当叫号员输入该号码时,会立即出现三种结果

  • 未在前三个号码中叫出
  • 未识别的卡片颜色
  • 面号不是3的倍数

第一种情况无法恢复;后两种情况可能是由于玩家或叫号员在报告最上方宾果卡group_face时出错。如果该值被接受,则刷新cards_PAN。如果三张宾果卡都叫出了三颗星,将显示如下内容。

Confirm Good

如果三张宾果卡没有全部叫到三颗星,将显示如下。

Confirm Bad

点击Resume后,宾果游戏继续。

4. 执行 Play_Bingo 目录

Play_Bingo 是一个中断驱动的 WinForm 应用程序。其状态图如下所示。

Play Bingo State

圆角矩形是按钮;方角矩形是状态。

程序启动后(Start Program),Play_Bingo必须首先确定要使用的宾果卡文件。

Get File Get File

接下来,Play_Bingo必须确定要使用的模式。

Pattern Choice

在这种情况下,已经指定了双重(一个水平一个垂直)宾果(注意图案周围的绿色边框)。当点击OK按钮时,会显示Play_Bingo主屏幕。

Play Bingo

随着游戏的进行,屏幕会更新信息,如前一个屏幕截图所示。

当玩家喊“宾果”时,叫号员点击宾果按钮。这会显示验证表单confirm_bingo的第一部分。当叫号员输入卡片颜色和卡号,并点击查找时,会出现以下内容。

Confirm Bingo

此屏幕显示玩家的卡片,所有已叫号码均以绿色高亮显示。当叫号员点击验证时,出现以下内容。

Confirm Bingo

如果又有人喊“宾果”,叫号员清除卡号,输入新卡号,点击查找,然后按照之前的相同步骤操作。如果没有有效的“宾果”,叫号员点击继续,游戏继续。如果有有效的“宾果”,叫号员点击新游戏,软件会打开“图案选择”窗口。

测试案例
confirm_bingo.cs文件的顶部是以下语句
    //#define TESTING
如果注释被移除,将在lookup方法的末尾实例化一个测试用例。要获得有效的“宾果”,必须指定文件cards_800000.dat,图案为垂直-水平,卡片颜色为浅天蓝色,卡号为12017。

当玩家喊“星星”时,叫号员点击星星!按钮。这将显示confirm_stars.bingo表单。当叫号员输入卡号并点击查找时,出现以下内容。

Confirm Stars

如果给出不同的卡号,可能会出现以下情况。

Confirm Stars

测试案例
confirm_stars.cs文件的顶部是以下语句
    //#define TESTING
如果移除注释,将在lookup方法的末尾实例化一个测试用例。要获得有效的“星星”,必须指定文件cards_800000.dat和卡号12017。

无论哪种情况,叫号员都点击Resume返回宾果游戏。

如果游戏的计时和/或图案标记的颜色不满意,叫号员可以点击Settings打开Settings表单。

Settings

红色项目是 NumericUpDown 控件,用于设置其各自计时器的值。如果点击图案标记颜色按钮,将打开KnownColorPicker表单。

Known Color Picker

虽然对任何颜色都没有限制,但用户不应选择浅天蓝色,因为pattern_PAN的背景颜色也是浅天蓝色。此外,饱和度过高或过深的颜色会分散玩家对叫号板的注意力。

当焦点在主窗体中时,某些键盘键会被识别(从属窗体中不识别键盘键)。

操作
S 打开settings对话框
P 打开patterns对话框
F1 切换说明可见性
s 开始宾果游戏
b 宾果叫号
t 星星叫号
p 暂停宾果游戏
r 恢复宾果游戏
i 重新初始化宾果游戏
x 退出程序

5. 结论 目录

Play_Bingo 为叫号员主持宾果游戏提供协助。

6. 参考文献 目录

7. 开发环境 目录

本文介绍的软件是在以下环境中开发的

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

8. 历史 目录

宾果 - 第3部分,共3部分 - 玩宾果 03/29/2023 原始文章
© . All rights reserved.