1、综合实例——猜纸牌游戏 问题描述: 该游戏使用52张扑克牌,黑桃、红桃、方片和梅花各13张(A、2,3,4,…,10,J,Q,K)。游戏规则为:计算机随机抽取8张纸牌,以背面显示在屏幕上。使用者可使用鼠标左键单击翻看某张纸牌(最多翻看8张次,看下张牌时以前翻开的牌会自动翻回去)。从翻开第一张牌时开始计时,10秒钟后自动弹出一个对话框,其中显示下列问题之一,并可接受使用者的回答: 这八张牌中有几张是红桃(黑桃,方块,梅花)? 这八张牌中红桃(黑桃,方块,梅花)的点数(A=1,J=Q=K=0)之和是多少? 这八张牌中红桃(黑桃,方块,梅花)的大牌(J,Q,K,A)共几张? 这八张牌中共
2、有几张大牌(J,Q,K,A)? 如果回答正确,则在总分上加10分;否则扣10分。此时游戏会将所有纸牌翻到正面,供使用者复查。 再单击鼠标左键则重新发牌开始新的一局。 编程要求: (1)扑克牌显示正确,比例恰当。所有牌的背面应有统一的花纹,牌面可使用简化方法显示,即在牌中央显示一个表示花色的图案(黑桃、红桃、梅花和方片,应使用位图资源),牌面左上角显示点数(2~10,J,Q,K,A)。黑桃和梅花用黑色,红桃和方片用红色。在屏幕上分两行共显示8张纸牌,每行4张。 (2)在适当位置显示提示信息,包括已进行的局数、当前得分、当前剩余时间和已翻开的纸牌张数。 (3)用鼠标左键单击某张纸牌可将
3、其翻转(正面变反面或反面变正面)。在一局中最多可翻开8张牌。 (4)第一次单击鼠标左键时开始计时,10秒钟后自动弹出一对话框用于提问和接受使用者的回答。要求对话框设计美观大方,尺寸合理。对话框下方有一个编辑控件(设置为数值属性),用于输入用户的回答。 (5)对话框上能正确显示问题,而具体显示哪个题目,题目中是什么花色或什么大牌均应随机确定。 (6)用户回答问题后将所有纸牌翻为正面,用户再次单击鼠标左键后开始新的一局。 要点分析: 本题主要涉及到的知识点有:鼠标消息、对话框、定时器消息、位图显示、随机函数的使用,同时还有数值和字符串之间的转换,位图显示位置的计算以及较多的逻辑判断工作,
4、涉及的内容较多,难度较大。 该题的难点在于纸牌的显示、纸牌产生的随机性以及提问问题的产生和判断等。 纸牌的背面可以用一个位图显示,而显示正面较为复杂,因为有52张不同的牌,如果使用52个不同的位图表示,工作量就太大了。这里把显示纸牌的正面分成两个部分,首先用四个位图表示四种不同花色的纸牌,然后用A、2~10、J、Q、K在位图的左上角显示表示不同的点,从而显示出一个完整的纸牌正面。 这里给出的随机产生纸牌的算法非常简单,即用0~51这52个整数来表示纸牌,也就是说,0~12表示梅花A~K、13~25表示方块A~K,26~38表示红桃A~K,39~51表示黑桃A~K。这样,表示纸牌的整数对1
5、3取模就得到纸牌的花色(0~3分别表示梅花、方块、红桃和黑桃),除以13得到的余数就是纸牌的点数。 问题和答案通过一个对话框表示出来,而相关的操作则都在定时器消息处理函数中进行。问题的产生涉及到两个随机数,一个产生问题的类型,一个产生问题中纸牌的花色,而相关问题的正确答案在纸牌产生时就已经计算出来,就等着根据不同的问题和答题分蘖节 的答案进行判断了。 解题步骤: (1)首先用AppWizard生成一个名为PuzzleCard的SD1程序框架,各选项均可用缺省设置。 (2)使用Developer Studio菜单的Insert / Resource…选项调出Insert Sourc
6、e对话框,为项目添加5个位图资源,分别表示纸牌的背面和四种花色纸牌的正面。相应位图的资源ID分别为IDB_BACK、IDB_CLUB、IDB_DIAMOND、IDB_HEART和IDB_SPADE。 (3)使用Developer Studio菜单的Insert / Resource…选项调出Insert Source对话框,为项目添加一个对话框模板资源。 (4)修改对话框模板的标识符ID为IDD-QUESTION,适当调整其大小,并在其上添加两个静态文本控件,其中表示问题的文本近代件ID改为IDC_Question, Caption改为空;另一个静态文本控件的Caption改“答案”。添加
7、一个编辑控件,ID改为IDC_Answer。 (5)利用ClassWizard自动建立对话框类。用Ctrl+W键可直接调出ClassWizard,也可以通过Developer Studio菜单的View/ClassWizard…选项调出。进入ClassWizard后,它会发现已建立的对话框模板资源,并弹出一个对话框询问是否要为该对话框模板建立类。按下“OK”按钮,会弹出New Class对话框,在Name栏填写对话框类的名称CQuestion后按“OK”按钮即可为对话框创建出一个对应的类。 (6)利用ClassWizard为对话框类添加与各控件对应的数据成员。选择MemberVariabl
8、es选项卡,确保Class Name项为对话框CQuestion类,然后在选项卡下方的窗口中选择各近代件的ID并按下“Add Variable…”按钮,为其添加对应成员变量,如下表4所示。 表4 对话框类中与各控件对应的成员变量 Control IDs Variable Type Member variable name IDC_Answer int m_nAnser IDC_Question CString m_strQuestion (7)为视图类添加一个新的成员函数Initial ( ),无返回值和参数。 (8)用ClassWizard在视图类中建立定时器
9、处理函数OnTimer ( )。 (9)用ClassWizard在视图类中建立鼠标左键消息处理函数OnLButtonDown ( )。 (10)完成以上工作后,即可修改程序框架,添加必要的代码。 源程序清单: 为简化编程,本题的所有数据都在视图类中声明,代码也都在视图中编写,无需对其他类进行操作。 在视图类的头文件中加入以下变量定义: class CPuzzleCardView: public CView { //此处略去若干行由系统生成的代码 public: bool m_bStart; bool m_bFirstClick;//第一次翻纸牌 int m
10、nBigNum[4];//各花色大牌数 int m_nTotalPoint[4];//各花色点数和 int m_nCardCount[4];//各花色牌数 int m_nTotalBigNum;//大牌数 CString m_strInfor;//游戏状态 int m_nGameNum;//局数 int m_nMark;//得分 int m_nRemTime;//剩余时间 int m_nCardNum;//翻过纸牌的数目 CRect m_rectInfo;//游戏状态显示我间 int m_nCard[8];//用整数0~51表示52
11、张牌 bool m_bCardFace[8];//桌面纸牌是否显示正面 CBitmap m_bmCardFace[4], m_bmCardBack;//纸牌的位图 int m_nCard, m_nCardh;//纸牌位图的尺寸 }; 在视图类cpp文件的最前面添加以下代码: #include〃Question. h〃 在视图类构造函数中添加以下初始化代码: CPuzzleCardView :: CPuzzleCardView ( ) { //载入纸牌的位图资源 m_bmCardFace[0]. LoadBitmap (IDB_CLUB); m_bm
12、CardFace[1]. LoadBitmap (IDB_DIAMOND); m_bmCardFace[2]. LoadBitmap (IDB_HEART); m_bmCardFace[3]. LoadBitmap (IDB_SPADE); m_bmCardBack. LoadBitmap (IDB_BACK); //计算纸牌位图的高和宽 BITMAP bmCard; m_bmCardBack. GetBitmap (&bmCard); m_nCardw = bmCard. bmWidth; m_nCardh = bmCard. bmHeight;
13、 m_rectInfo = CRect (49, 454, 797, 500); m_nGameNum = 1; //局数 m_nMark = 100; //得分 Initial ( ); } 在视图类的OnDraw( )成员函数中添加以下代码: void CPuzzleCardView :: OnDraw (CDC * pDC) { CPuzzleCardDoc * pDoc = GetDocument ( ); ASSERT_VALID (pDoc); CDC memDC[8];
14、CBitmap * pOldDC; for (int i=0; i<8; i++) { int nTopLeftx, nTopLefty; //纸牌左上角坐标 CString strNum; nTopLeftx = 50 + i% 4 * (m_nCardw + 20); nTopLefty = 50 + i/4 * (m_nCardh + 20); memDC[i]. CreateCompatibleDC (NULL); if (m_bCardFace [i]) { //显示正面 pOldDC = memDC [i]
15、 SelectObject (&m_bmCardFace [m_nCard [i] / 13]); switch (m_nCard [i] % 13) { case 0: strNum = 〃A〃; break; case 10: strNum = 〃J〃; break; case 11: strNum = 〃Q〃; break; case 12: strNum = 〃K〃;
16、 break; default: strNum. Format (〃%d〃, m_nCard [i] % 13 + 1); } } //显示背面 else pOldDC = memDC [i]. SelectObject (&m_bmCardBack); pDC - > BitBlt (nTopLeftx, nTopLefty, m_nCardw, m_nCardh, &memDC [i], 0, 0, SRCCOP Y); memDC [i]. SelectObject (pOldD
17、C); pDC - > TextOut (nTopLeftx + 10), nTopLefty + 10, strNum); //显示纸牌点数 } CString strInfo; strInfo. Format (〃局数: % d得分: % d剩余时间: % d已翻开纸牌数: % d〃, m_nGame.Num, m_nMark, m_nRemTime, m_nCardNum); pC - > TextOut
18、50, 460, strInfo); //显示游戏状态 } 鼠标、定时器消息处理函数的代码如下: void CPuzzleCardView :: OnLButtonDown (UINT nFlags, CPoint point) { int nTopLeftx, nTopLefty; //纸牌左上角坐标 int nCardFace = -1; CRect rectCard; //当前点中的纸牌 CRect rectCardFace; //前一次翻开的纸牌
19、 int i = 0; if (m_bStart) { if (m_nCardNum > 7) return; if (m_bFirstClick) { //第一次翻开,开始计时 SetTimer (1, 1000, NULL); m_bFirstClick = false; } while (i < 8) //找出前一次翻开的纸牌 { if (m_bCardFace [i]) { nCardFace = i;
20、 nTopLeftx = 50 +i % 4 * (m_nCardw + 20); nTopLefty = 50 + i / 4 * (m_nCardh + 20); rectCardFace = CRect (nTopLeftx, nTopLefty, nTopLeftx + m_nCardw, nTopLefty + m_nCardh); break; } i + +; } for (i=0; i<8; i + +) //
21、找出当前点中的纸牌翻开 { nTopLeftx = 50 +i % 4 * (m_nCardw + 20); nTopLeftx = 50 + i / 4 * (m_nCardh + 20); rectCard = CRect (nTopLeftx, nTopLefty, nTopLeftx + m_nCardw, nTopLefty + m_nCardh); if (rectCard. PtInRect (point)) {
22、 if (nCardFace ! = -1) { m_bCardFace [nCardFace] = false; InvalidateRect (rectCardFace); } m_bCardFace [i] = true; m_nCardNum + +; InvalidateRect (rectCard); InvalidateRect (
23、 m_rectInfo); break; } } } else { //重新开始 Initial ( ); m_nGameNum + +; Invalidate ( ); } CView :: OnLButtonDown (nFlags, point) } void CPuzzleCardView :: OnTimer (UINT nI
24、DEvent) { m_nRemTime - -; InvalidateRect (& m_rectInfo); if (m_nRemTime = = 0) //时间到,开始提问 { KillTimer (1); for (int i=0; i<8; i + +) m_bCardFace [i] = false; //所有纸牌翻回背面 Invalidae ( );
25、 CQuestion dlgQuestion; int nCardType; int nQuestionType; int nAnswer; CString strCardType; srand ((unsigned) time (NULL)); nCardType = rand ( ) % 4; //随机产生纸牌花色 switch (nCardType) {
26、 case 0: strCardType = 〃梅花〃; break; case 1: strCardType = 〃方块〃; break; case 2: strCardType = 〃红桃〃; break; case 3: strCardType = 〃黑桃
27、〃; } nQuestionType = rand ( ) % 4; //随机产生问题 switch (nQuestionType) {//根据问题类型构造问题字符串,得到正确答案 case 0: digQuestion. m_strQuestion. Format (〃问题:这八张牌中有几张是% s ?〃, strCardType);
28、nAnaswer = m_nCardCount [nCardType]; break; case 1: dlgQuestion. m_strQuestion. Format (〃问题:这八张牌中% s的点数(A=1, J=Q=k=0)之和是多少?〃, strCardType); nAnswer = m_nTotalPoint [nCardType];
29、break; case 2: dlgQuestion. m_strQuestion. Format (〃问题:这八张牌中% s大牌(J, Q, K, A)共几张?〃, strCardType); nAnswer = m_nBigNum [nCardType]; break; case 3: dlgQuestion. m_strQuestion. Format
30、 (〃问题:这八张牌中共有几张大牌(J, Q, K, A)?〃); nAnswer = m_nTotalBigNum; } if (dlgQuestion. DoModal ( ) = = IDOK) { if (dlgQuestion. m_nAnswer = = nAnswer) { m_nMark + = 10; }
31、 else { m_nMark - = 10; } } for (i=0; i<8; i + +)//所有纸牌翻开 m_bCardFace [i] = true; m_bStart = false; Invalidate ( ); } CView :: OnTimer (nIDEvent);
32、 } 初始化函数代码如下: void CPuzzleCardView :: Initial ( ) { srand ( (unsigned) time (NULL) ); for (int i=0; i < 4; i + +) { m_nCardCount [i] = 0; m_nTotalPoint [i] = 0; m_nBigNum[i]=0; } m_nTotalBigNum = 0; m_nRemTime = 10;//初始时间10s m_nCardNum = 0; m_bFirstC
33、lick = true; m_bStart = true; for (i =0; i < 8; i + + ) {/ /随机产生8张纸牌,计算问题答案 m_bCardFace [i] = false; m_nCard [i] = rand ( ) % 52; int nType = m_nCard [i] / 13; int nPoint = m_nCard [i] % 13 + 1; m_nCardCount [nType] + +; if (nPoint > 10) {
34、nPoint = 0; m_nBigNum [nType] + +; m_nTotalBigNum + +; } if (nPoint = = 1) { m_nBigNum [nType] + +; m_nTotalBigNum + +; } m_nTotalPoint [nType] + = nPoint; } } 输入输出: 游戏开始时显示8张纸牌的背面,用户可以使用鼠标左键翻看点中的纸牌,同时以前翻开的牌自动翻回去,如下图1、2、3所示。每次游戏最多可以翻看8张次;系统在用户翻开第一张牌后开始计时,10秒钟后自动弹出一个对话框,其中显示一个问题。若使用者回答正确,则在总分上加10分;否则扣10分。此时游戏会将所有纸牌翻到正面,供使用者查看。 图1 翻开一张牌 图2 提问对话框 图3 最终结果图 小结: 本题的题解多处使用到随机函数,附带介绍了使用整型数表示其他信息的方法。同时,通过显示纸牌的正面,也可以了解显示较为复杂的图案的方法。
©2010-2025 宁波自信网络信息技术有限公司 版权所有
客服电话:4009-655-100 投诉/维权电话:18658249818