宾果游戏套件 - 第 3 部分





5.00/5 (3投票s)
本文是宾果游戏套件系列的第三篇,也是最后一篇。
目录
1. 介绍
最后,本文是描述我过去几个月开发的宾果游戏套件的三篇文章中的第三篇。发布这些文章是希望这些软件能对读者有所帮助。这三篇文章都涉及75球版本的宾果。
本文讨论了前面图中加粗的项目。宾果卡文件必须已经由 Generate_Cards [^] 程序生成,并且一套宾果卡必须已经由 Print_Cards [^] 程序打印。
2. 玩宾果
宾果游戏在北美数以千计的大小地点进行。本文中玩(和讨论)的游戏是75球版本的宾果(而非90球版本)。
2.1. 背景
玩家购买的宾果游戏卡通常每张纸印三张卡片。每张纸构成一个游戏,并以不同的颜色打印。一个会话通常由六张不同颜色的纸组成。一天中可能会有许多会话。
叫号员(游戏主持人)随机从一个漏斗中抽出球。漏斗中的每个球都编号从1到75,并且通常根据其上的数字着色
- 蓝色 1 - 15
- 红色 16 - 30
- 白色 31 - 45
- 绿色 46 - 60
- 黄色 61 - 75
叫号员宣布抽出的球的号码。如果抽到37号球,号码将宣布为“N 37”。在大多数地方,球被放入板上的编号孔中。当球插入时,叫号板上的号码(在这种情况下是37)会亮起。
2.2. 模式
通常,仅仅在你的卡片上有叫到的号码并不能赢得游戏。叫到的号码通常必须以叫号员在游戏开始时宣布的模式出现。就本文而言,模式有两种类型:静态和动态。请注意,本文并未列举所有可能的模式;相反,它仅讨论少数几种以作演示。Roanoke VA Moose Lodge 有一套相当完整的 宾果模式 [^]。
描述模式的最佳方式是展示一些示例。以下是本文所述软件支持的静态模式。
以下是本文所述软件支持的动态模式。
这些模式显示了宾果卡上叫到的数字必须出现的位置才能获胜。
2.3. 星星
带星星的宾果游戏是正常宾果游戏的补充。它的引入是为了增加宾果游戏初始阶段的刺激性。
在正常的宾果游戏中,如果玩家能在前三个叫到的号码中标记出纸张上的所有三颗星,玩家就喊“星星”。如果纸张上的宾果卡包含有效的星星模式,玩家将获得奖品,宾果游戏继续进行。
2.4. 模拟游戏
为了将所有内容整合起来,我们来模拟一局游戏。假设
- 叫号员要求游戏在浅珊瑚色的宾果卡上进行(这意味着group_face必须以“2”开头)
- 模式是“字母X”
- 已叫出17个号码
- 叫号板看起来像
红色值是最后叫到的球,该号码必须在获胜的宾果卡上。现在我们来看看玩家的宾果卡。
绿色圆圈是玩家在每个号码被叫出后在卡片上做的标记。海贝色圆圈是最后叫出的号码。当最后一个号码被叫出时,玩家喊“宾果”,游戏暂停等待确认。玩家根据叫号员的要求,将宾果卡标识为“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. 确保随机性
在现实世界中,宾果球的抽取是随机的,这正是由于它们所来自的设备的性质。
为了模拟这种随机性,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.Paused为true,则线程暂停,否则线程继续执行。
3.4. 后台工作线程
为了保持主用户界面(UI)的响应性,存在两个 后台工作线程 [^]:一个在叫号板上闪烁数字;另一个在pattern_PAN中显示模式(请记住模式可以是动态的)。
程序初始化完成后,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_DoWork 是 Play_Bingo 的主力。
- 如果需要,进入暂停状态
- 从GV.balls列表中抽取下一个球号ball_to_display,忽略不出现在当前模式中的球号
- 在显示器上绘制球的图像
- 朗读ball_to_display
- 根据GV.drawn_to_flash指定的时间(秒)休眠
- 如果之前有抽到的球,将之前闪烁的叫号板面板从白色前景红色背景重置为黑色前景白色背景。
- 调用其ReportProgress方法更新last_called_PAN和total_calls_PAN
- 调用flash_bingo_card_board使新球号在叫号板上闪烁
- 保存最后叫到的球号
- 如果所有球尚未抽出,则返回步骤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 执行以下步骤来创建要绘制的图像。
请记住,后绘制的对象会覆盖先绘制的对象。
最后,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调用的结果。我们希望对角线模式每隔指定的时间间隔按如下方式改变。
类变量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_buffer 从 Play_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_Paint 将 Play_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”。
3.7. 确认星星
当玩家喊“星星”时,Play_Bingo必须确定玩家的游戏纸是否包含已叫出的带星号码。执行此测试的软件包含在Play_Bingo命名空间中的Confirm_Stars类中。在本文的这一部分中,所有引用的代码都可以在该类中找到。
要确定宾果卡是否获胜,叫号员必须提供纸上最上方宾果卡的group_face。例如,在下图中
纸上最顶部的宾果卡的group_face号码为“12017”。当叫号员输入该号码时,会立即出现三种结果
- 未在前三个号码中叫出
- 未识别的卡片颜色
- 面号不是3的倍数
第一种情况无法恢复;后两种情况可能是由于玩家或叫号员在报告最上方宾果卡group_face时出错。如果该值被接受,则刷新cards_PAN。如果三张宾果卡都叫出了三颗星,将显示如下内容。
如果三张宾果卡没有全部叫到三颗星,将显示如下。
点击Resume后,宾果游戏继续。
4. 执行 Play_Bingo
Play_Bingo 是一个中断驱动的 WinForm 应用程序。其状态图如下所示。
圆角矩形是按钮;方角矩形是状态。
程序启动后(Start Program),Play_Bingo必须首先确定要使用的宾果卡文件。
接下来,Play_Bingo必须确定要使用的模式。
在这种情况下,已经指定了双重(一个水平一个垂直)宾果(注意图案周围的绿色边框)。当点击OK按钮时,会显示Play_Bingo主屏幕。
随着游戏的进行,屏幕会更新信息,如前一个屏幕截图所示。
当玩家喊“宾果”时,叫号员点击宾果按钮。这会显示验证表单confirm_bingo的第一部分。当叫号员输入卡片颜色和卡号,并点击查找时,会出现以下内容。
此屏幕显示玩家的卡片,所有已叫号码均以绿色高亮显示。当叫号员点击验证时,出现以下内容。
如果又有人喊“宾果”,叫号员清除卡号,输入新卡号,点击查找,然后按照之前的相同步骤操作。如果没有有效的“宾果”,叫号员点击继续,游戏继续。如果有有效的“宾果”,叫号员点击新游戏,软件会打开“图案选择”窗口。
测试案例
在confirm_bingo.cs文件的顶部是以下语句//#define TESTING如果注释被移除,将在lookup方法的末尾实例化一个测试用例。要获得有效的“宾果”,必须指定文件cards_800000.dat,图案为垂直-水平,卡片颜色为浅天蓝色,卡号为12017。
当玩家喊“星星”时,叫号员点击星星!按钮。这将显示confirm_stars.bingo表单。当叫号员输入卡号并点击查找时,出现以下内容。
如果给出不同的卡号,可能会出现以下情况。
测试案例
在confirm_stars.cs文件的顶部是以下语句//#define TESTING如果移除注释,将在lookup方法的末尾实例化一个测试用例。要获得有效的“星星”,必须指定文件cards_800000.dat和卡号12017。
无论哪种情况,叫号员都点击Resume返回宾果游戏。
如果游戏的计时和/或图案标记的颜色不满意,叫号员可以点击Settings打开Settings表单。
红色项目是 NumericUpDown 控件,用于设置其各自计时器的值。如果点击图案标记颜色按钮,将打开KnownColorPicker表单。
虽然对任何颜色都没有限制,但用户不应选择浅天蓝色,因为pattern_PAN的背景颜色也是浅天蓝色。此外,饱和度过高或过深的颜色会分散玩家对叫号板的注意力。
当焦点在主窗体中时,某些键盘键会被识别(从属窗体中不识别键盘键)。
键 | 操作 |
S | 打开settings对话框 |
P | 打开patterns对话框 |
F1 | 切换说明可见性 |
s | 开始宾果游戏 |
b | 宾果叫号 |
t | 星星叫号 |
p | 暂停宾果游戏 |
r | 恢复宾果游戏 |
i | 重新初始化宾果游戏 |
x | 退出程序 |
5. 结论
Play_Bingo 为叫号员主持宾果游戏提供协助。
6. 参考文献
- BackgroundWorker 类 [^]
- 宾果模式 [^]
- Control.Paint [^]
- Control Refresh [^]
- 事件处理程序 [^]
- FileStream 类 [^]
- Generate_Cards [^]
- Graphics 类 [^]
- InvokeRequired [^]
- Monitor [^]
- Print_Cards [^]
- 伪随机数生成器 [^]
- SpeechSynthesizer 类 [^]
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 | 原始文章 |