VC++2008开发网络百家乐街机游戏(上)

来源:岁月联盟 编辑:zhu 时间:2009-03-12

  前  言: 目前街头电子游戏厅内充斥着各类明目繁多的街机游戏,画面绚烂,游戏方式各异。其实剖开游戏的表面,分析内部的系统架构,您将会发现大大小小的街机游戏开发模式都是一直的,先在PC机上完成编码架构与环境测试,再移植到专用的街机平台上,有的甚至就是PC终端直接改装成街机。本文将以Visual C 2008平台下开发网络百家乐街机游戏为例,向您详细阐述街机游戏开发的全过程。

  一、 游戏背景介绍

  街机游戏厅内最为常见的就是百家乐一类的游戏,街机游戏迷们大多对此类游戏有兴趣,一为娱乐二为彩头。百家乐英文为Baccarat,其名字取自义大利语中的“零”,因为在大部份扑克牌游戏中占着高价值的人面牌及十点牌,在百家乐游戏中却都算作零。一些历史学家认为百家乐游戏起源于中世纪的意大利西西里岛,后来流传到法国,并广受豪门贵族欢迎,在传入美国后逐渐发展成熟起来,规则得到进一步完善。不论起源于哪,百家乐最早是只有庄家和玩家两人参加的数位博彩游戏,由庄家给玩家和自己各发两张牌,谁的两张牌加起来的总数最接近9,谁就赢。几个世纪以来,适用于第三张牌的一系列博彩规则,以及从每一个玩家收取一定拥金的规则不断发展变化。

  时至今日,百家乐成了博彩业最追捧的游戏。上世纪60年代年代,何鸿燊的合伙人叶汉将这种游戏引入澳门博彩业,并为其起了一个具有东方色彩的好名字─百家乐,百家乐成了中国人喜欢玩的一种博彩游戏。当然,本文以百家乐游戏为例,不是让您以此去做违法的事,只是百家乐的游戏规则是一种精辟的数学分析,它的规则能充分综合概率统计与数字逻辑的运用。对于软件开发者而言,设计一款完善的网络百家乐街机游戏,能很好的将软件开发与数学思维综合在一起,从而提升软件研发水平。

  二、 游戏规则数学分析

  百家乐游戏一般需用6副扑克(每副52张牌, 不包括大, 小王). 庄家和闲家将各发到两张牌. 然后依照标准的百家乐规则, 将第三张牌发给闲家或庄家. 百家乐游戏的目的是要玩家预测庄家或闲家谁手上纸牌的点数最接近 9 点. 你可将押注押在庄家, 闲家, 或是和局. 当双方的点数相同时即为平手. 如果游戏达成平手, 押在庄家和闲家的押注会退还. 庄家和闲家的赔率都是相同的, 只是如果你押庄家而庄家赢, 庄家会收取 5% 的佣金. 如果你下分和局同时结果正确的话, 将可以获得 8 赔 1 的赔率金额. 所有十点的牌都以 0 计算, 而A牌则计为一点, 其它按其原先牌面点数. 如果牌的点数总和超过9, 则将总和减去10.

  例如: 手中的牌    点数总和

  9 8=17        7

   5 5 5=15      5

   10 9=19       9

  庄和闲如何补牌早已设计好了,由荷官按规则执行,只要闲或庄任何一方两张牌的总点数为8点或者9点,胜负已定,这种情形称为天然赢。

   只要不是天然赢,闲家及庄家按下面顺序决定要不要第三张牌:

   只要闲的点数是5点或者5点以下,必须补第三张牌。

  如果闲家没有补第三张牌(即闲家两张牌的点数为或者7点)而庄家点数5点或5点以下,庄家必须要第三张牌;如果闲家补了第三张牌(即闲家的点数在5点或者5点以下),那么庄家依照下面的图表决定补不补第三张牌。

 

  庄家

  点数

 

 

  0

 

 

  1

 

 

  2

 

 

  3

 

 

  4

 

 

  5

 

 

  6

 

 

  7

 

 

  8

 

 

  9

 

 

  7

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  6

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  补牌

 

 

  补牌

 

 

  -

 

 

  -

 

 

  5

 

 

  -

 

 

  -

 

 

  -

 

 

  -

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  -

 

 

  -

 

 

  4

 

 

  -

 

 

  -

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  -

 

 

  -

 

 

  3

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  -

 

 

  补牌

 

 

  2

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  1

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  0

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

 

  补牌

 

  人们喜欢百家乐也是因为它的“快”,只要庄家给玩家和自己各发两张牌,谁的总数越接近9谁就嬴了。

  百家乐和 BLACKJACK 、 POKER 等游戏不同,在百家乐局上虽然也有庄和闲的分别,但玩家既可以把注下在庄上也可以把注下在闲上,那么运营方是怎样来实现其在规则上的优势呢,首先按照补牌规则,庄补牌比闲更具优势,闲输给庄,但玩家要是始终把注下在庄上,运营方岂不要吃亏,因此在百家乐游戏中还有一个补充规定,庄赢要抽水5%,这个5%要大于庄对闲的优势,这样一来,下在庄上的注也占不到任何便宜。下面我们进行具体分析。

  百家乐的庄与闲分别最少也有两张牌,最多也只有三张牌。由百家乐的规则很容易想到,百家乐也存在着一个庄与闲的点数的概率分布表,但并不能直接用这个概率分布按照公式(2?1?1)来计算收益率,因为百家乐的点数和对方的牌点甚至和对方的第三张牌有关,显然,三张牌的“6”点从来就不会和对方的“8”点遇到一起,因此这张表并没有更多的意义。

 

  点数

 

 

  0

 

 

  1

 

 

  2

 

 

  3

 

 

  4

 

 

  5

 

 

  6

 

 

  7

 

 

  8

 

 

  9

 

 

  庄家

 

 

  8.88

 

 

  6.93

 

 

  6.91

 

 

  7.28

 

 

  9.34

 

 

  10.07

 

 

  12.11

 

 

  12.84

 

 

  12.80

 

 

  12.84

 

 

  闲家

 

 

  9.40

 

 

  7.45

 

 

  7.43

 

 

  7.45

 

 

  7.43

 

 

  7.45

 

 

  13.32

 

 

  13.37

 

 

  13.32

 

 

  13.37

 

  有人会觉得这个表格不准确,至少庄或者闲出现“8”和“9”点的概率似乎应该相同。这是因为每发出一张牌,后面的牌出现的概率就有了细微的变化,如果以牌的平均出现概率1/13来计算,庄或者闲出现“8”和“9”点的概率就是相同的了。百家乐的收益率的计算,也是应用公式(2:1:1)来计算赔率的加权平均值,但是是通过计算具体到每一种情形下赔率和它发生的概率的乘积的累加值得到,计算收益率时顺便也得到了百家乐庄与闲的点数的概率分布表。

  下面以8副牌为例,并对牌的花色不加以区分,举例如下:

  闲:“2、4”,庄:“2、3、2”

  闲的第一张牌“2”出现的概率为32/416,庄的第一张牌“2”出现的概率为31/415,闲的第二张牌“4”出现的概率为32/414,庄的第二张牌“3”出现的概率为32/413,闲不能再补牌,庄必须再补一张,庄的第三张牌“2”出现的概率为30/412。闲“6”点,庄“7”点,由于庄的点数比闲大,押庄赢,押闲输,这种情形发生的概率:

  32/416×32/414×31/415×32/413×30/412

  又如闲:“2、4”,庄:“2、2、1”,

  闲的第一张牌“2”出现的概率为32/416,庄的第一张牌“2”出现的概率为31/415,闲的第二张牌“4”出现的概率为32/414,庄的第二张牌“3”出现的概率为32/413,闲不能再补牌,庄必须再补一张,出现庄的第三张牌“1”的概率为32/412。闲“6”点,庄“5”点,由于闲的点数比庄大,押闲赢,押庄输,这种情形发生的概率:

  32/416×32/414×31/415×32/413×32/412

  闲:“10、4、5”,庄:“10、5、2”

  闲的第一张牌“10”出现的概率为128/416,庄的第一张牌“4”出现的概率为32/415,闲的第二张牌“4”出现的概率为32/414,庄的第二张牌“5”出现的概率为32/413,闲必须补第三张牌,闲的第三张牌“5”出现的概率为31/412,庄也补第三张牌,庄的第三张牌“2”出现的概率为32/411。闲“9”点,庄“7”点,由于庄的点数比闲大,押庄赢,押闲输,这种情形发生的概率:

  128/416×32/414×31/412×32/415×32/413×30/411

  又如闲:“10、10、10”,庄:“10、10、10”,

  闲的第一张牌“10”出现的概率为128/416,庄的第一张牌“10”出现的概率为127/415,闲的第二张牌“10”出现的概率为126/414,庄的第二张牌“10”出现的概率为125/413,,闲必须补第三张牌,闲的第三张牌“10”出现的概率为124/412,庄也补第三张牌,庄的第三张牌“10”出现的概率为123/411。闲“0”点,庄“0”点,由于庄和闲的点数一样大,押庄或闲都不输不赢,押和赢,这种情形发生的概率:

  128/416×126/414×124/412×127/415×125/413×123/411

  把所有可能出现的情形都计算一遍并把所有的结果进行累加,就能得到我们需要的结果,当然,用程式来实现上面的思想并不难,下面是编程后计算得到的结果。

  庄、和、闲的概率:   45.860  44.625 9.516

  庄、闲、和的赢率:   49.471  49.382 42.820

  庄、闲、和的收益率: -1.058  -1.235 -14.360

  由庄、和、闲的概率计算收益率的过程如下: 

  押庄的收益率=0.95×45.860-44.625=-1.058

  押闲的收益率=44.625-45.860=-1.235

  押和的收益率=8×9.516-44.625-45.860=-14.357

  可见,百家乐中庄赢抽水“5%”其实并不是真正意义上的抽水,只是实现抽水的一种手段,而不是抽水本身。其实,在百家乐中,运营商在庄、闲、和上分别架了三台抽水机在抽水,数“和”那台马力出奇的大,闲次之,庄最小。上面的概率乘以消耗的牌的数目,把所有可能出现的情形都计算一遍,并把所有的结果进行累加就能得到每轮的平均耗牌数:4.939张牌。

  以上是百家乐游戏规则的详细数学分析,下面介绍该游戏架构的设计。

  三、 游戏架构及数据库结构设计

  根据目前街机游戏的设计结构,本系统架构由前台客户端/后台服务端/同步控制台/数据设置端四块组成,数据库大多采用小型数据库软件,本系统以ACCESS2007数据库为例建立数据库文件。

  3.1  游戏架构

  前台客户端即玩家游戏时的主要操作平台,大多采用WIN32开发模式,以多线程运用为主。所有街机终端的操作输入装置只有几个简单的按钮,以本系统为例,每一个玩家接触的只有小分,大分,退分,红庄,黑闲,绿和六个按钮。同时街机游戏为提升人气,大多支持多人同步统一操作画面控制台游戏,即前台客户端支持多个玩家在同一个软件平台上同时操作,看到的反馈画面是完全一致的。本系统前端采用一台主机通过分频器连接十台显示器模式可供十人同时游戏。因此前台客户端软件的输入设计需对ISO标准键盘ASCII码分区定义,支持键盘的读码输入,而不同于普通软件的控件输入。后台服务端主要负责前台客户端游戏的运行监控;所有玩家的上分、退分;百家乐开局,每局的场次及路单结果生成;运营数据统计等工作,操作人员为游戏运行管理人员。同步控制台主要负责整个游戏的运行时间控制,在本系统设计的街机百家乐游戏中每局为180场,每场均根据后台设置的到倒计时规定每场的投分时限,时限到达后,系统根据本场百家乐的运行路单显示本场结果,前台端软件根据本场结果及各玩家的投分状况计算出各玩家的得失分值并计入数据库。同步控制台会产生每场的读秒运行时由前台客户端软件读取,由此控制每场游戏的同步运行。因为是网络百家乐街机游戏,本系统后台数据库采用ACCESS2007文件,为方便系统安装维护,开发数据设置端软件以方便网络数据的安装与配置。后台服务端的数据统计报表采用VC 专用报表工具aResReport设计,可方便直观的查看各类分析数据汇总。

  根据以上描述,系统逻辑架构设计如下:

VC   2008开发网络百家乐街机游戏(上) 

  3.2  数据库结构

  基于以上的分析,以ACCESS2007建立游戏数据库文件th_round.dat,并设置数据库文件访问密码。添加数据库表如下:

  th_round_bjcc表用于记录百家乐游戏本局的当前场次与总局数,bjcc字段记录本局场次,sumcc字段记录总局数

VC   2008开发网络百家乐街机游戏(上)

  th_round_time表用于存储同步控制台与前台客户端软件通讯的同步控制计时及上分机器读数,djs字段记录倒计时限,boolreadsf字段记录是否有机器上退分,jqh字段记录上退分的机器号,sfsz记录上退分的数值。

VC   2008开发网络百家乐街机游戏(上)

  th_round_fpjl表存储本局百家乐的所有发牌记录,本系统设计每局百家乐为180场,由10列×18行组成,发牌记录在开局时由系统根据游戏规则按机器发牌的随机概率算法自动生成,存储数据示例表示如下:

VC   2008开发网络百家乐街机游戏(上)

  th_round_lpjl表与th_round_fpjl表相对应,也由10列×18行组成,存储每一场对应机器发牌的庄闲和路单结果,th_round_lpjl表的设计是为在每局百家乐游戏开局前打印路单,路单即是存储每局百家乐游戏的所有180场庄闲和结果的密闭信封,在本局结束后开拆,由玩家与客户端软件屏幕显示的场次记录核对,确保游戏的公平性。存储数据示例如下:

VC   2008开发网络百家乐街机游戏(上)

  图片看不清楚?请点击这里查看原图(大图)。

  th_round_info表存储本游戏的所有后台设置参数,运营商在后台服务端软件中修改此表中的各项参数可以控制整个游戏的运行效果,图表如下:

VC   2008开发网络百家乐街机游戏(上)

  图片看不清楚?请点击这里查看原图(大图)。

  th_round_pass表存储游戏后台服务端的控制权限,图表如下:

VC   2008开发网络百家乐街机游戏(上)

  th_round_hist表存储游戏每天的运营利润统计记录,图表如下:

VC   2008开发网络百家乐街机游戏(上)

  本游戏的前台客户端软件可供十人同时操作,因此在数据库表中设计如下表项:

  th_round_singleXX[XX为从01到10的十个表];

  th_round_dayXX[XX为从01到10的十个表];

  th_round_sumXX[XX为从01到10的十个表];

  th_round_singleXX为前台客户端软件单个控制台的操作记录存储,single_zsf字段为该控制台玩家的总上分记录, single_ztf字段为该控制台玩家的总退分记录,red/balck/green分别记录该控制台玩家本场百家乐的庄闲和投分记录,图表如下:

VC   2008开发网络百家乐街机游戏(上)

  th_round_dayXX为前台客户端软件单个控制台的每日上/退分记录存储,day_zsf记录每日上分和, day_ztf记录每日退分和,图表如下:

VC   2008开发网络百家乐街机游戏(上)

  th_round_sumXX为前台客户端软件单个控制台的运营总上/退分记录存储, sum_zsf记录总上分和, sum_ztf记录总退分和,图表如下:

VC   2008开发网络百家乐街机游戏(上)

  以上为该百家乐游戏的数据库基础架构,下面详细介绍系统编码。

  四、 游戏系统编码分析

  根据百家乐游戏的规则及以上的游戏架构数据库设计等系统分析,我们开始进行游戏的编码工作,还是以前台客户端/后台服务端/同步控制台/数据设置端四块组成的游戏架构方式,我们分块介绍该游戏的编码设计。

  4.1  数据设置模块

  数据设置模块主要功能是配置游戏的数据源,为方便游戏的安装与维护设计,根据游戏的架构及数据库文件th_round.dat结构设计如下:

  4.1.1  软件界面布置

  打开Visual C 2008 IDE环境,新建以TH_ROUND_SET命名的MFC Dialog工程方案,布置Dialog界面的控件如下图:

VC   2008开发网络百家乐街机游戏(上)

  4.1.2  模块编码

  设置模块的任务是方便配置网络数据源(注:th_round.dat文件布置于网络服务器端),因此先设计配置网络数据源的通用函数,经过研究笔者发现无论何种数据源,采用何种内置方式配置,最终都是在系统注册表中完成写入操作。为此,笔者详细分析了ACCESS 2007数据库文件的注册表写入的键值装配泛型,针对此设计了ACCESS 2007数据源配置的通用注册表执行函数,编码如下:

// REG方式注册ACCESS数据库
bool CTH_ROUND_SETDlg::RegisterACCESSDBSource(CString strDSName, CString strDBPath, CString strDefaultDir, CString strDescription)
{
     HKEY hKey;
     DWORD nLabel;
     CString strBaseKey = _T("SOFTWARE/ODBC/ODBC.INI");
     CString strMid = strBaseKey _T("/ODBC Data Sources");
     if(strDSName.IsEmpty()||strDBPath.IsEmpty())
         return false;
     CString strDataSource = strBaseKey _T("/") strDSName;
     CString strMdb = _T("Microsoft Access Driver (*.mdb)");
     CString strDBDriver = _T("C:/WINDOWS/system32/odbcjt32.dll");
     CString strFIL = _T("Ms Access");
     CString strUID = _T("");
     CString strEngines = strDataSource _T("/Engines");
     CString strJet = strEngines _T("/Jet");
     CString strImplicitCommitSync = _T("");
     CString strUserCommitSync = _T("Yes");
     RegCreateKeyEx(HKEY_LOCAL_MACHINE,strMid,0,NULL,REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,NULL
,&hKey,&nLabel);//获取数据源键值句柄
     RegSetValueEx(hKey,strDSName,0,REG_SZ,(const unsigned char *)((LPCTSTR)strMdb),strlen((LPCTSTR)strMdb) 1);///设置数据源类型
 
     RegCreateKeyEx(HKEY_LOCAL_MACHINE,strDataSource,0,NULL,REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS,NULL,&hKey,&nLabel);//创建数据源子键
     RegSetValueEx(hKey,_T("DBQ"),0,REG_SZ,(const unsigned char *)((LPCTSTR)strDBPath),strlen((LPCTSTR)strDBPath) 1);//数据库表的全路径
     RegSetValueEx(hKey,_T("DefaultDir"),0,REG_SZ,(const unsigned char *)((LPCTSTR)strDefaultDir),strlen((LPCTSTR)strDefaultDir) 1);//数据库表所在目录
     RegSetValueEx(hKey,_T("Description"),0,REG_SZ,(const unsigned char *)((LPCTSTR)strDescription),strlen((LPCTSTR)strDescription) 1);//数据库描述
     RegSetValueEx(hKey,_T("Driver"),0,REG_SZ,(const unsigned char *)((LPCTSTR)strDBDriver),strlen((LPCTSTR)strDBDriver) 1);//ODBC驱动的全路径
     DWORD DriverId = (DWORD)25;
     RegSetValueEx(hKey,_T("DriverId"),0,REG_DWORD,(const BYTE *)(&DriverId),sizeof(DWORD));//必须项
     RegSetValueEx(hKey,_T("FIL"),0,REG_SZ,(const unsigned char *)((LPCTSTR)strFIL),strlen((LPCTSTR)strFIL) 1);//表的类型
     DWORD SafeTrans = (DWORD)0;
     RegSetValueEx(hKey,_T("SafeTransactions"),0,REG_DWORD,(const BYTE *)(&SafeTrans),sizeof(DWORD));//可选项
     RegSetValueEx(hKey,_T("UID"),0,REG_SZ,(const unsigned char *)((LPCTSTR)strUID),strlen((LPCTSTR)strUID) 1);//必须项
     RegCreateKeyEx(HKEY_LOCAL_MACHINE,strEngines,0,NULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,
NULL,&hKey,&nLabel);//创建数据源引擎子键
     RegCreateKeyEx(HKEY_LOCAL_MACHINE,strJet,0,NULL,REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,NULL,
&hKey,&nLabel);//创建数据源引擎子键
     RegSetValueEx(hKey,_T("ImplicitCommitSync"),0,REG_SZ,(const unsigned char *)((LPCTSTR)strImplicitCommitSync),strlen((LPCTSTR)strImplicitCommitSync) 1);//数据库表的全路径
     DWORD MaxBufferSize = (DWORD)2048;
     RegSetValueEx(hKey,_T("MaxBufferSize"),0,REG_DWORD,(const BYTE *)(&MaxBufferSize),sizeof(DWORD));//必须项
     DWORD PageTimeout = (DWORD)5;
     RegSetValueEx(hKey,_T("PageTimeout"),0,REG_DWORD,(const BYTE *)(&PageTimeout),sizeof(DWORD));//必须项
     DWORD Threads = (DWORD)3;
     RegSetValueEx(hKey,_T("Threads"),0,REG_DWORD,(const BYTE *)(&Threads),sizeof(DWORD));//必须项
     RegSetValueEx(hKey,_T("UserCommitSync"),0,REG_SZ,(const unsigned char *)((LPCTSTR)strUserCommitSync),strlen((LPCTSTR)strUserCommitSync) 1);//必须项
     return true;
}

  该函数共有四个形参,strDSName参数为ODBC配置系统数据源的DSN名称,该名称由用户自定义;strDBPath参数为选中的需要配置的ACCESS 2007数据库文件的全路径名称;strDefaultDir为配置的ACCESS 2007数据库文件所在的目录路径;strDescription为该数据源的描述信息。

  下面设计选择数据库文件的按钮编码函数:

void CTH_ROUND_SETDlg::OnFileButton()
{
     // TODO: Add your control notification handler code here
     CFileDialog dlgFileOpen(TRUE);  
     //   dlgFileOpen.m_ofn.lStructSize=structsize;
     dlgFileOpen.m_ofn.lpstrFilter = "数据文件*.dat所有的文件(*.*)*.*";
     //TCHAR   lpstrFilename[MAX_PATH] = "";
     dlgFileOpen.m_ofn.lpstrFile;//=lpstrFilename;
     dlgFileOpen.m_ofn.lStructSize=88;
     //标题栏
     dlgFileOpen.m_ofn.lpstrTitle="打开数据文件";
     //显示以“只读方式打开”
     dlgFileOpen.m_ofn.Flags&=~OFN_HIDEREADONLY;
     //显示“帮助”,对应于当前的HLP文件
     ///dlgFileOpen.m_ofn.Flags|=OFN_ENABLETEMPLATEHANDLE;
     //dlgFileOpen.m_ofn.Flags|=OFN_EXPLORER|OFN_SHOWHELP;//|OFN_EXPLORER;
     //dlgFileOpen.m_ofn.lpTemplateName=MAKEINTRESOURCE(IDD_FILEOPENPREVEIW);
     if(dlgFileOpen.DoModal()==IDOK)
     {
         string_filename=dlgFileOpen.GetPathName();
         string_pathname=string_filename.Left(string_filename.Find("/th_round.dat"));
         string_filename.MakeLower();
         string_pathname.MakeLower();
         m_filename.SetWindowText(string_filename);
         GetDlgItem(IDC_SAVE_BUTTON)->EnableWindow(true);
     }
}

  此函数用于选择网内ACCESS 2007数据库文件所在的位置。随后设计保存设置按钮事件的函数,主要调用前面编写的数据源配置的通用注册表执行函数,编码如下:

void CTH_ROUND_SETDlg::OnSaveButton()
{
     // TODO: Add your control notification handler code here
     //添加本地ACCESS数据源
     if(RegisterACCESSDBSource("TH_ROUND",string_filename,string_pathname,"百家乐数据信息"))
     {
         MessageBox("数据库配置成功!","提示信息",MB_ICONINFORMATION);
     }
     else
     {
         MessageBox("数据库配置失败,请确认!","警告",MB_ICONERROR);
     }
}

  该模块程序运行效果如下图:

VC   2008开发网络百家乐街机游戏(上)

  4.2  后台服务端设计

  后台服务端程序是控制整个游戏的运行核心模块,主要负责前台客户端游戏的运行监控;所有玩家的上分、退分;百家乐开局,每局的场次及路单结果生成;运营数据统计等工作.操作人员为游戏运行管理人员, 根据以上架构将后台服务端程序按【参数设置】、【系统管理】及【日常操作】三大功能组设计。下面分布介绍:

  4.2.1  前期准备

  打开Visual C 2008 IDE环境,新建以TH_ROUND_CTL命名的MFC MDI工程方案。根据面向对象的设计思想,我们先设计出通用的数据库操控类以方便各功能组的数据访问。本系统以VC 的ADO方式设计通用数据库访问类,核心编码如下:

  CDATABE_ADO数据库访问类声明:

class CDATABE_ADO 
{
public:
     CDATABE_ADO();
     virtual ~CDATABE_ADO();
// Attributes
public:
     _RecordsetPtr AdoSet; //记录指针对象
     _ConnectionPtr AdoDb; //连接字符串指针
// Operations
public:
     void GetBlobField(CString csBlobName, CString csImageFilePath); //读取图像BLOB数据
     void OpenTable(CString csStatement); //打开数据库表
     void SetBlobField(CString csBlobName,CString csImageFilePath); //写入图像BLOB数据
     short IsEOS(); //判断是否在表记录尾部
     long int GetRecordCount(); //获取数据库表记录数量
     void MovePrevious(); //将表记录指针移到第一行
     void MoveNext(); //将表记录指针移到下一行
     void MoveLast(); //将表记录指针移到最后一行
     void MoveFirst(); //将表记录指针移到第一行
     void SetField(CString csName,CString csValue); //写入指定字段数据
     CString GetField(CString csName); //读取指定字段数据
     _RecordsetPtr DataBindGrid(CString csStatement); //获取查询集合的绑定对象
     void Exec(CString csStatement); //执行指定的SQL语句
     void Close();//关闭数据库连接,释放指针对象
     void Open(CString Connectionstring,CString UserID,CString Password); //打开数据库连接,准备数据访问
     bool GetBoolField(CString csName); //读取BOOL字段的数据值
     void SetBoolField(CString csName,bool csValue); //写入BOOL字段的数据值
};

  CDATABE_ADO数据库访问类函数定义,下面部分举例说明:

//打开数据库连接,准备数据访问
void CDATABE_ADO::Open(CString Connectionstring,CString UserID,CString Password)
{
     ::CoInitialize(NULL);
     AdoDb.CreateInstance(__uuidof(Connection));
     AdoSet.CreateInstance(__uuidof(Recordset));
     AdoDb->Open(Connectionstring.AllocSysString(),UserID.AllocSysString(),Password.AllocSysString(),-1);
}
//关闭数据库连接,释放指针对象
void CDATABE_ADO::Close()
{
     AdoSet->Close();
     AdoDb->Close();
     AdoSet.Release();
     AdoDb.Release();
     ::CoUninitialize();
}
//执行指定的SQL语句
void CDATABE_ADO::Exec(CString csStatement)
{
     AdoDb->Execute(csStatement.AllocSysString(),NULL,adCmdText|adExecuteNoRecords);
}
//获取查询集合的绑定对象,可以将返回对象值直接绑定到DataGrid控件
_RecordsetPtr CDATABE_ADO::DataBindGrid(CString csStatement)
{
     AdoSet->Open(csStatement.AllocSysString(),AdoDb.GetInterfacePtr(),adOpenKeyset,adLockPessimistic,adCmdText);
     return AdoSet;
}
//打开数据库表
void CDATABE_ADO::OpenTable(CString csStatement)
{
     AdoSet->Open(csStatement.AllocSysString(),AdoDb.GetInterfacePtr(),adOpenKeyset,adLockPessimistic,adCmdText);
}
……
//读取图像BLOB数据
void CDATABE_ADO::SetBlobField(CString csBlobName, CString csImageFilePath)
{
     CFile imagefile;
     if(imagefile.Open(csImageFilePath,CFile::modeRead)==0)
         return;
     _variant_t varChunk;
     BYTE* pbuf;
     long nLength=imagefile.GetLength();
     pbuf = new BYTE[nLength 2];
     if(pbuf == NULL)
         return; //allocate memory error
     imagefile.Read(pbuf,nLength); //read the file into memory
     BYTE *pBufEx;
     pBufEx = pbuf;//build a SAFFERRAY
     SAFEARRAY* psa;
     SAFEARRAYBOUND rgsabound[1];
     rgsabound[0].lLbound = 0;
     rgsabound[0].cElements = nLength;
     psa = SafeArrayCreate(VT_UI1, 1, rgsabound);
     for (long i = 0; i < nLength; i )
         SafeArrayPutElement(psa, &i, pBufEx );
     VARIANT varBLOB;
     varBLOB.vt = VT_ARRAY | VT_UI1;
     varBLOB.parray = psa;
     try
     {
         AdoSet->Fields->GetItem(_variant_t(csBlobName))->AppendChunk(varBLOB);
         AdoSet->Update();
     }
     catch (_com_error &e)
     {
         // Notify the user of errors if any.
         _bstr_t bstrSource(e.Source());
         _bstr_t bstrDescription(e.Description());
         CString sError;
         sError.Format("Source : %s n Description : %sn",(LPCSTR)bstrSource,(LPCSTR)bstrDescription);
         AfxMessageBox("图像字段读取错误" sError);
     }   
}
//写入图像BLOB数据
void CDATABE_ADO::GetBlobField(CString csBlobName, CString csImageFilePath)
{
     try
     {
         _variant_t varChunk;
         VARIANT varBLOB;
         long lDataLength = AdoSet->Fields->GetItem(_variant_t(csBlobName))->ActualSize;
         varBLOB = AdoSet->GetFields()->GetItem(_variant_t(csBlobName))->GetChunk(lDataLength);
         if(varBLOB.vt == (VT_ARRAY | VT_UI1))
         {
              BYTE *pBuf = NULL;
              pBuf = (BYTE*)GlobalAlloc(GMEM_FIXED,lDataLength);
              SafeArrayAccessData(varBLOB.parray,(void **)pBuf);
              CFile outFile(csImageFilePath,CFile::modeCreate|CFile::modeWrite);
              LPSTR buffer = (LPSTR)GlobalLock((HGLOBAL)pBuf);
              outFile.Write(buffer,lDataLength);
              GlobalUnlock((HGLOBAL)pBuf);
              outFile.Close();
              SafeArrayUnaccessData(varBLOB.parray);
         }
     }
     catch(_com_error &e)
     {
         // Notify the user of errors if any.
         _bstr_t bstrSource(e.Source());
         _bstr_t bstrDescription(e.Description());
         CString sError;
         sError.Format("Source : %s n Description : %sn",(LPCSTR)bstrSource,(LPCSTR)bstrDescription);
         AfxMessageBox("图像字段读取错误" sError);
     }
}

  设计好数据库通用访问类后,下面考虑软件界面的编码,因为此工程为MDI方式,为方便用户操作采用菜单控制方式调用每一个MDI界面,为此编写调用MDI界面的通用模板函数如下:

BOOL CMainFrame::OnOpenView(CMultiDocTemplate *pDocTemplate)
{
     CMDIChildWnd* pActiveChild = MDIGetActive();
     CDocument* pDocument;
     if (pActiveChild == NULL ||(pDocument = pActiveChild->GetActiveDocument()) == NULL)
     {
     TRACE0("Now New the specify viewn");
     ASSERT(pDocTemplate != NULL);
     ASSERT(pDocTemplate->IsKindOf(RUNTIME_CLASS(CDocTemplate)));
     pDocTemplate->OpenDocumentFile(NULL);
     return TRUE;
     }
// otherwise we have a new frame to the same document!
     CMultiDocTemplate* pTemplate = pDocTemplate;
     ASSERT_VALID(pTemplate);
     CFrameWnd* pFrame = pTemplate->CreateNewFrame(pDocument, pActiveChild);
     if (pFrame == NULL)
     {
     TRACE0("Warning: failed to create new framen");
     return FALSE; // command failed
     }
     pTemplate->InitialUpdateFrame(pFrame, pDocument);
     return TRUE;
}

  使用此函数可方便的采用MDI方式调用每一个VC FORM的Template模板,从而控制MDI界面的显示,下面分功能组介绍。

  4.2.2  参数设置功能组

  参数设置功能组负责游戏的基本参数,含【操作权限】与【游戏参数】两块。操作权限管理操作后台服务端软件的人员权限信息,本系统的人员权限分老板、总管及营业员三种,此模块设计较简单,不再赘述。游戏参数模块控制整个游戏运营的基础数据设置,下面详细描述:

  4.2.2.1  软件界面布置

  打开Visual C 2008 IDE环境,添加以IDD_TH_ROUND_SET_FORM命名的FORM,对应游戏基础数据库表的设置将控件布置如下图:

VC   2008开发网络百家乐街机游戏(上)

  该界面的菜单调用按VC 的MDI界面调用编码,使用了前述的MDI界面的通用模板函数,调用如下:

void CMainFrame::OnSetYxcs()
{
     // TODO: 在此添加命令处理程序代码
     OnOpenView(((CTH_ROUND_CTLApp*)AfxGetApp())->pTH_ROUND_SETView_Template);
}

  其中pTH_ROUND_SETView_Template是在工程的应用程序类CTH_ROUND_CTLApp中声明及定义的,代表指向IDD_TH_ROUND_SET_FORM的DOC Template模板指针,声明代码如下:

//Attributes
public:
     CMultiDocTemplate* pTH_ROUND_SETView_Template;

  定义代码如下:

pTH_ROUND_SETView_Template = new CMultiDocTemplate(
         IDR_MAINFRAME,
         RUNTIME_CLASS(CTH_ROUND_CTLDoc),
         RUNTIME_CLASS(CChildFrame), // custom MDI child frame
         RUNTIME_CLASS(CTH_ROUND_SETView));

  在CTH_ROUND_CTLApp类的ExitInstance()函数中须释放模板资源,代码如下:

if(pTH_ROUND_SETView_Template)
         delete pTH_ROUND_SETView_Template;

  下面介绍功能编码。

  4.2.2.2  模块编码

  游戏参数模块的编码主要是访问本游戏基础数据库表,要用到前面设计的CDATABE_ADO数据库访问类,由此类的实例对象调用数据库访问函数操作数据库表充分利用了面向对象设计的思想,会大大减少编码的工作量。部分代码举例如下:

void CTH_ROUND_SETView::OnBnClickedBtocszButton()
{
     // TODO: 在此添加控件通知处理程序代码
     ……
     // 读取用户控件的输入数据
     m_csledit.GetWindowText(string_csl);
     if(string_redcomrate!=""&&string_blackcomrate!=""&&string_greencomrate!=""&&string_bcdz1!=""&&string_bcdz2!=""&&string_bcdz3!=""&&string_csq!=""&&string_csl!="")
     {
         try
         {
              CDATABE_ADO m_datado; // 声明数据库访问类的实例对象
              m_datado.Open("TH_ROUND","admin/admin","flyingtjf"); //连接数据库准备操作
              m_datado.OpenTable("select * from th_round_info"); //打开数据库表
              // 以下操作是针对该数据库表中各字段的设置数据
              m_datado.SetField("红球赔率",string_redcomrate);
              m_datado.SetField("黑球赔率",string_blackcomrate);
              m_datado.SetField("绿球赔率",string_greencomrate);
              m_datado.SetField("红黑差限",string_hhcx);
              m_datado.SetField("本场单注一",string_bcdz1);
              m_datado.SetField("本场单注二",string_bcdz2);
              m_datado.SetField("押和上限",string_bcdz3);
              m_datado.SetField("抽水球",string_csq);
              m_datado.SetField("抽水率",string_csl);
              m_datado.Close();
              MessageBox("老板对客人游戏规则参数设置成功!","提示",MB_ICONINFORMATION);
         }
         catch (...)
         {
              ……
         }
     }
     else
     {
         MessageBox("有数值为空!","提示",MB_ICONINFORMATION);
     }
}

  以上数据库访问类的面向对象运用可以看出,使用此数据库操作类是非常简单的。