Microsoft Foundation Classes: creating a Sudoku game. Part 2.

It's the second part of the article about creating a Sudoku game using MFC. In this part you will read about how to create GUI for this game. The MFC applications are closely connected to the document-view architecture. When you create a project two classes are created: document class and view class. The document class contains the data used in your application. The view class is a class that displays the data from the document class. Im Sudoku project these classes are declared as: class CSudokuDoc : public CDocument and class CSudokuView : public CView In the document class we declare a variable of board class:
  1. public:
  2. board game;
It's the only one change to the generated document class. The view class definition is almost unchanged. I added only 1 variable, an array of colors to draw different numbers with different colors, 3 buttons and a form to enter number(the description of this form will be shown later):
  1. public:
  2. COLORREF colors[9];
  3. bool editOpened;
  4. //buttons---------------------
  5. CButton newGame,
  6. help,
  7. solve,
  8. about;
  9. //--------------Buttons rects
  10. CRect _NewGame,
  11. _Help,
  12. _Solve,
  13. _About;
  14. //-----------------
  15. CEdit *enterData;
The representation of data, that is stored in the board variable is done in the function
  1. virtual void OnDraw(CDC* pDC);
Now let's look on the OnDraw function, because it contains the most part of the project's code. First step is to get the pointer to the document object to draw data from the document:
  1. CSudokuDoc* pDoc = GetDocument();
  2. ASSERT_VALID(pDoc);
  3. if (!pDoc)
  4. return;
Now I create some pens for work and get the clients frame rectangle:
  1. CPen SolidPen(PS_SOLID,3,RGB(0,0,0));
  2. CPen ThinPen(PS_SOLID,1,RGB(0,0,0));
  3. help.SetRedraw();
  4. CRect rcClient;
  5. GetClientRect(&rcClient);
The GUI should represent a grid of numbers. That's why we need to draw this grid first:
  1. for (int i = 0; i != 4; ++i)
  2. {
  3. pDC->SelectObject(&SolidPen);
  4. pDC->MoveTo(0,i*rcClient.Height()/3);
  5. pDC->LineTo((int)(rcClient.Width()*0.75),(int)(i*rcClient.Height()/3));
  6.  
  7. pDC->MoveTo(i*rcClient.Width()*0.25,0);
  8. pDC->LineTo(i*rcClient.Width()*0.25,i*rcClient.Height());
  9.  
  10. pDC->SelectObject(&ThinPen);
  11. pDC->MoveTo(0,i*rcClient.Height()/3+1.0/9.0*rcClient.Height());
  12. pDC->LineTo(rcClient.Width()*0.75,i*rcClient.Height()/3+1.0/9.0*rcClient.Height());
  13.  
  14. pDC->MoveTo(0,i*rcClient.Height()/3+2.0/9.0*rcClient.Height());
  15. pDC->LineTo(rcClient.Width()*0.75,i*rcClient.Height()/3+2.0/9.0*rcClient.Height());
  16. if ( i != 3){
  17. pDC->MoveTo(i*rcClient.Width()*0.25 + 1.0/9.0*rcClient.Width()*0.75,0);
  18. pDC->LineTo(i*rcClient.Width()*0.25 + 1.0/9.0*rcClient.Width()*0.75,rcClient.Height());
  19.  
  20. pDC->MoveTo(i*rcClient.Width()*0.25 + 2.0/9.0*rcClient.Width()*0.75,0);
  21. pDC->LineTo(i*rcClient.Width()*0.25 + 2.0/9.0*rcClient.Width()*0.75,rcClient.Height());
  22. }
  23. }
The board variable holds data of the Sudoku game. We need to draw the numbers from this variable in the frame:
  1. for(int i1 = 0; i1 != 9; ++i1){
  2. for(int i2 = 0; i2 != 9; ++i2){
  3. if(pDoc->game.numbers[i1][i2])
  4. {
  5. CString t;
  6. t.Format(_T("%d"),pDoc->game.numbers[i1][i2]);
  7. RECT posK = {i2*rcClient.Width()*0.75/9.0+25,i1*rcClient.Height()/9.0+25,(i2+1)*rcClient.Width()*0.75/9.0,(i1+1)*rcClient.Height()/9.0};
  8. pDC->SetBkMode(TRANSPARENT);
  9. pDC->SetTextColor(colors[pDoc->game.numbers[i1][i2]-1]);
  10. pDC->DrawText(t,1,&posK,DT_CALCRECT);
  11. pDC->DrawText(t,1,&posK,0);
  12. }
  13. }
  14. }
The initialization of buttons and colors is done in the next way:
  1. CSudokuView::CSudokuView()
  2. {
  3.  
  4. _NewGame = new CRect(600,10,800,50);
  5. _Help = new CRect(600,60,800,100);
  6. _Solve = new CRect(600,110,800,150);
  7. _About = new CRect(600,200,800,240);
  8. editOpened = false;
  9.  
  10. colors[0] = RGB(0,0,0);
  11. colors[1] = RGB(0,0,205);
  12. colors[2] = RGB(0,100,0);
  13. colors[3] = RGB(255,255,0);
  14. colors[4] = RGB(255,0,0);
  15. colors[5] = RGB(160,32,240);
  16. colors[6] = RGB(0,0,255);
  17. colors[7] = RGB(0,255,0);
  18. colors[8] = RGB(139,35,35);
  19. }
And this is an example how to set text to button:
  1. newGame.Create(_T("New game"), WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
  2. _NewGame, this, NEWGAME_BN);
  3.  
  4. help.Create(_T("Help"), WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
  5. _Help, this, HELP_BN);
  6.  
  7. solve.Create(_T("Solve"), WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
  8. _Solve, this, SOLVE_BN);
  9.  
  10. about.Create(_T("About"), WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
  11. _About, this, ABOUT_BN);
The next step is to handle events from 3 buttons and event of mouse pressed. We have to edit the declaration of message map and add next events:
  1. BEGIN_MESSAGE_MAP(CSudokuView, CView)
  2. ON_BN_CLICKED(NEWGAME_BN,onNewGame)
  3. ON_BN_CLICKED(HELP_BN,onHelp)
  4. ON_BN_CLICKED(SOLVE_BN,onSolve)
  5. ON_BN_CLICKED(ABOUT_BN,onAbout)
  6. ON_WM_LBUTTONDOWN()
  7. END_MESSAGE_MAP()
This can be done automatically by Visual Studio by adding events in the property window. The message man declares which function will handle event from every source. So now we have to implement function to handle events. When you press the "New Game" button a form to select difficulty appears: new The onNewGame functions takes the result of your selection and sets difficulty to the game:
  1. CSudokuDoc* pDoc = GetDocument();
  2. difList list1;
  3. list1.DoModal();
  4. switch(list1.selected)
  5. {
  6. case 0: pDoc->game.difficulty = 30;
  7. break;
  8. case 1: pDoc->game.difficulty = 40;
  9. break;
  10. case 2: pDoc->game.difficulty = 50;
  11. break;
  12. case 3: pDoc->game.difficulty = 64;
  13. break;
  14. default:pDoc->game.difficulty = 30;
  15. break;
  16. }
  17. pDoc->game.generate();
  18. Invalidate();
The implementation of the difficulty form is:
  1. BEGIN_MESSAGE_MAP(difList, CDialog)
  2. END_MESSAGE_MAP()
  3.  
  4. BOOL difList::OnInitDialog()
  5. {
  6. CDialog::OnInitDialog();
  7. SetWindowText(L"Chose difficulty");
  8. this->SetWindowPos(&CWnd::wndTop, 0, 0, 200,200,SWP_NOMOVE);
  9.  
  10. difBox.Create(WS_CHILD|WS_VISIBLE|LBS_STANDARD|WS_HSCROLL, CRect(0,0,200,100), this, 99);
  11. difBox.AddString(L"1. Easy");
  12. difBox.AddString(L"2. Medium");
  13. difBox.AddString(L"3. Hard");
  14. difBox.AddString(L"4. Allmost unsolvable");
  15. difBox.SetSel(0,true);
  16. GetDlgItem(IDOK)->MoveWindow(60,140,80,30);
  17.  
  18. return TRUE; // return TRUE unless you set the focus to a control
  19. }
  20.  
  21.  
  22. void difList::OnOK()
  23. {
  24. // TODO: добавьте специализированный код или вызов базового класса
  25. selected = difBox.GetCurSel();
  26. CDialog::OnOK();
  27. }
The help button simply add a number from the solution array to the array of current state:
  1. if(!GetDocument()->game.win())
  2. {
  3. MessageBox(_T("A number is added to board"), _T("Help"),MB_ICONQUESTION| MB_OK);
  4. GetDocument()->game.help();
  5. Invalidate();
  6. }
The most interesting thing is about handling event from mouse. For the game we need to achieve the coordinates of the pressed point and check, if it's pressed on an empty cell:
  1. CRect rcClient;
  2. GetClientRect(&rcClient);
  3.  
  4. CSudokuDoc* pDoc = GetDocument();
  5.  
  6. int row = 9*point.y/rcClient.Height();
  7. int column = (9*point.x)/(rcClient.Width()*0.75);
Now, if the cell is empty, ask user to input a number:
  1. if(!pDoc->game.numbers[row][column])
  2. {
  3. enterNumber addNum;
  4. addNum.DoModal();
  5. if(addNum.numb&&pDoc->game.checkNumberValidity(addNum.numb,row,column))
  6. pDoc->game.numbers[row][column]=addNum.numb;
  7. else
  8. MessageBox(L"Incorrect number!!!!!!!!",(LPCTSTR)0,MB_ICONERROR | MB_OK);
  9. if(pDoc->game.win())
  10. MessageBox(L"You win!!!!!!!!",(LPCTSTR)0, MB_OK);
  11. Invalidate();
  12. }
In this part of code a form to input a number appears: form2 If the inputted number can be added to the board it's added and the main frame is redrawn.If it's an incorrect number - user will see the message of the error and nothing will be changed. You can download the source code of the game and try it by yourself. Try to solve the game on the most difficult level.

Tags

Add new comment