Game : Tic Tac Toe
  • Brief Intro
This is my favorite program. I write it every time I change over to a new language or to a new OS. That gives me an idea of how the language and the OS do the drawing activity and how they respond to the mouse messages. The output of the program is shown in the figure. The game is very simple and you most have played it with your friends umpteen number of times. This time the only difference is your opponent is computer. Firstly you make a move by clicking the left mouse button in the empty slot. As a result an 'O' appears in the empty slot. Then the computer does some thinking and responds by making a move by drawing a circle in another empty slot. This goes on till three symbols 'X' or 'O' fall in one horizontal line, one vertical line or one diagonal. The player who achieves this first stands to win the game. The game is declared as drawn if all nine squares are filled and neither player has managed to claim three squares in a row horizontally, vertically or diagonally.
Once the user has made his move it is for the computer to make the next move. Our program logic would guide it to do this thinking by following the steps given below:
* Occupy a row, column or diagonal that has two elements filled by the computer.
* Occupy a row, column or diagonal that has two cells occupied by the player.
* Occupy the center. * Occupy the first corner available.
* Occupy the first cell, which is free, when scanning row-wise, from first to last.
The action begins in the frame window's constructor function myframe( ). In this function we have used AfxRegisterWndClass( ) to register a window class with the desired attributes. These attributes include the background color, a cursor and an icon. We have selected a light gray background, an arrow shaped cursor and a user defined-icon. The first parameter passed to AfxRegisterWndClass( ) is CS_DBLCLKS. This parameter ensures that if the user double clicks in the window then a WM_DBLCLKS message would be passed to it. The standard cursor and the user-defined icon are loaded into the memory by calling the functions myapp::LoadStandardCursor( ) and myapp::LoadIcon( ) respectively. To be able to facilitate calls to these functions firstly the address of the global application object is obtained by calling AfxGetApp( ). Once the window class has been registered, a window of that class has been created by calling CFrameWnd::Create( ). Note that the window class name returned by AfxRegisterWndClass( ) is passed as the first parameter to CFrameWnd::Create( ). Once the window has been created a WM_CREATE message gets posted into the message queue. The myframe::OnCreate( ) handler reacts to this message by initializing two arrays arr[ ] and square[ ]. The elements of the first one are initialized to a value EMPTY to indicate that no player has as yet made any move. The square[ ] array is an array of CRect objects. This array has been initialized to several CRect values that specify the coordinates of rectangles into which the user should click the left mouse button to be able to make a move. Once the window has been created and the initialization in the OnCreate( ) function has been completed the CWnd::Show-Window( ) function is called to display the window on the screen. This results into the WM_PAINT message getting posted into the message queue. In response to this message the myframe::OnPaint( ) function gets called. The actual preparation of playing field takes place in this function. This involves drawing two vertical and horizontal lines of pen thickness equal to 10 and calling the drawex( ) and drawoh( ) function to draw an 'X' or 'O' as per the values present in the array arr[ ]. When the OnPaint( ) handler is called for the first time there is no question of drawing either an 'X' or 'O' since all the elements of the array arr[ ] are EMPTY.
The user makes his move by clicking the left mouse button in an empty square. This results into a call to the OnLButtonDown( ) function. Here it is first verified whether the user has clicked in an empty square. If not then the move is reported as invalid. This checking is done by calling CRect::PtInRect( ). This function returns TRUE if the point passed to it lies within the rectangle represented by the CRect object, or FALSE if it does not. If the move is found to be valid then the drawoh( ) function is called to draw an 'O' in the empty square where the click occurred. Also, the corresponding element (representing the square where the click occurred) in the array arr[ ] is set up with a value OH to indicate that it has now been occupied by an 'O'. Lastly, the isgameover( ) function is called to check whether the user won the game on making the move. If he has not won the game so far and the game has not been drawn then the computer is given a chance to make its move by calling the function compplay( ).
The computer plays its move by drawing an 'X' in an empty square. Which empty square to choose for drawing the 'X' is decided by the computer as per the guidelines we discussed earlier. The logic has been implemented in the compplay( ) function. The complicated set of conditions in this function would become easier to digest if you look at the strategies that we laid down for the computer's move.
Once a player (user or computer) makes a move it is necessary to find out whether by making that move did they win the game. To this effect we have called the function diduserwin( ) from the OnLButtonDown( ) handler, that is, after the user has made his move. Similarly, we have called a function didcomputerwin( ) from the compplay( ) function, i.e, after the computer has made its move. From each of these functions we have called the findwinner( ) function to determine the winner. As we know, the game would be drawn if no player can claim three straight positions vertically, horizontally or diagonally. Since it is the user who plays the first move, in event of the drawn game it would be the user who would get to play the ninth move. That is the reason why we have called the isgamedrawn( ) function only from OnLButtonDown( ) and not from compplay( ).
If the game stands drawn then from the isgamedrawn( ) function we have called the resetgame( ) function to start a fresh game. To do this all the elements of the array arr[ ] are reset to the value EMPTY. However, just doing this won't be enough. The status of the array should be reflected in the window. This is achieved by calling the CWnd::Invalidate( ) function. This function invalidates the entire client area resulting into the WM_PAINT message getting posted into the message queue. In response to this message the OnPaint( ) handler redraws the entire client area to reflect the current status of the array arr[ ]. If the user so desires he can restart the game by double clicking the left mouse button on one of the horizontal or vertical lines. On double clicking the left mouse button the OnLButtonDblClk( ) handler gets called. In this function, again the resetgame( ) and Invalidate( ) are called to restart the game.
The system menu usually contains six menu items. Restore, Move, Size, Minimize, Maximize and Close. In this program we have added two more items to the system menu, namely About and Help. This has been achieved in the OnCreate( ) handler. Here we have called CWnd::GetSystemMenu( ) to obtain a pointer to the system menu. The FALSE parameter passed to the GetSystemMenu( ) function indicates that we want a pointer to a copy of the system menu that we can modify. Passing TRUE resets the system menu to its default state. Once the pointer to the system menu is obtained, the AppendMenu( ) function is called four times to add two separators and the 'About' and the 'Help' menu items. Note that the ids for the menu items have been given as 112 and 128 respectively. This is because it is necessary that the ids of system menu items be multiples of 16 and the 'About' and 'Help' menu items happen to be the seventh and eighth menu items. When the user clicks on an item from the system menu, the window receives a WM_SYSCOMMAND message. We have tackled this message using the handler OnSysCommand( ). The first parameter passed to it contains in its upper 12 bits the id of the menu item selected. The lower four bits of the parameter are used by Windows internally. Hence, the id has been ANDed with 0xFFF0 to strip off any bits that Windows may have added to it. If the 'About' menu item is selected then an appropriate message is displayed using the MessageBox( ) function. A similar procedure is carried out if the user clicks on the 'Help' menu item. Note that it is necessary to call the base class's OnSysCommand( ) handler so that the selection of other system menu items gets properly processed.