Since completing a console version of Tic Tac Toe I decided to work on a WIN32 version. I've now completed it to a level I am happy with. I know I could add better graphics, sound, menus etc. I'm leaving that for my next Connect Four project.
As this is my first project in WIN32 I've not been hundred percent certain on how I've been doing things. I was hoping some more experience WIN32 programmers can take a look and see if what I'm doing is correct especially with my game loop. I know it's a bit crappy but it's been a massive learning curve for me :)
My next Connect Four project will be based kinda on this project so I don't want to carry across any bad habits or in-effecient, ugly coding practises. I've tried to make the coding as elegant as my knowledge / experience permits.
Be a massive help if someone can take a quick look.
[Edited by - RedKMan on July 19, 2010 12:27:02 PM]
Please critique my C++ WIN32 Tic Tac Toe code
You're likely to get better feedback if you include your source inline using [ source ] and [ /source ] tags (without spaces) for each file.
GameBoard.h
Player.h
GameBoard.CPP
Player.cpp
WinMain.cpp
//---------------------------------------------------------------// File: GameBoard.h//// Desc: The class that deals with all gameboard activity.//// Date: January 2nd, 2010.//---------------------------------------------------------------#ifndef GAMEBOARD_H#define GAMEBOARD_H#include <iostream>#include <vector>#include <math.h>#include <tchar.h>#include <windows.h>#include <stdio.h>#include <stdlib.h>#include <string>class GameBoard{public: GameBoard(); // Get the board size. const unsigned getBoardSize(); // Get the board dimensions. const unsigned getBoardDims(); // Get the board const std::vector<char>& getBoard(); // Return the value stored at x, y of the board. const char getBoard(unsigned row, unsigned column); // Initialise the board void initBoard(); // Convert 2D Row / Column Index to 1D Value. unsigned convertIndex(unsigned row, unsigned column); // Update the instance variable board with the current player's marker in the relevant // position in the array. bool setBoard(unsigned row, unsigned column, WCHAR playerMarker); // Update the instance variable board with the AI players marker in // the relevant position in the array. bool setBoard(unsigned move, WCHAR playerMarker); // Update the instance variable board by setting the location row, col to blank. // This is for use with the WinMain.cpp function evaluateAI(). void unsetBoard(unsigned row, unsigned column); // Check to see if there are any places left on the board. const bool gameOver(); // Check to see if the player has produced a winning game state. const bool checkWinningLine(WCHAR playerMarker);private: unsigned boardSize; std::vector<char> board; unsigned boardDims;};#endif
Player.h
//---------------------------------------------------------------// File: Player.h//// Desc: The class that represents the players.//// Date: January 2nd, 2010.//---------------------------------------------------------------#ifndef PLAYER_H#define PLAYER_H#include <string>#include <tchar.h>#include "GameBoard.h"class Player{public: // Player Class Constructor. Player(); // Get the players name. const std::wstring& getName(); // Get the players marker. const WCHAR getMarker(); // Get the players current row selection. const int getRow(); // Get the players current column selection. const int getColumn(); // Get the number of games won. const int getGamesWon(); // Get the players state. const bool getState(); // Set the players name. void setName(const std::wstring &playerName); // Set the players marker. void setMarker(WCHAR playerMarker); // Set the players row selection. void setRow(int playerRow); // Set the players column selection. void setColumn(int playerColumn); // Set the state of the player. void setState(bool playerState); // Increments the number of games the player has won by one. void incrementGamesWon(); // Clears the number of games the player has won to zero. void clearGamesWon();private: std::wstring name; // The name of the player. WCHAR marker; // The mark this player will place on the board. Can either be X or O. int row; // Holds the current row choice. int column; // Holds the current column choice. int gamesWon; // Holds the number of times this player has won. bool state; // True if it's this players turn, false otherwise.};#endif
GameBoard.CPP
//---------------------------------------------------------------// File: GameBoard.cpp//// Desc: Implements the game board.//// Date: January 2nd, 2010.//---------------------------------------------------------------#include "GameBoard.h"//---------------------------------------------------------------// Name: Constructor()// Desc: Takes an integer value which is used to set the size of// the board. Calls initBoard() to initialise or reset the// game board.//---------------------------------------------------------------GameBoard::GameBoard() : boardSize(3 * 3), boardDims(3), board(3 * 3, '\0'){ }//---------------------------------------------------------------// Name: getBoardSize()// Desc: Returns the size of the board.//---------------------------------------------------------------const unsigned GameBoard::getBoardSize() { return boardSize;}//---------------------------------------------------------------// Name: getBoardDims()// Desc: Returns the dimensions of the board.//---------------------------------------------------------------const unsigned GameBoard::getBoardDims(){ return boardDims;}//---------------------------------------------------------------// Name: getBoard()// Desc: Returns the board.//---------------------------------------------------------------const std::vector<char>& GameBoard::getBoard() { return board;}//---------------------------------------------------------------// Name: getBoard()// Desc: Return the value stored at x, y of the board.//---------------------------------------------------------------const char GameBoard::getBoard(unsigned row, unsigned column){ return board[convertIndex(row, column)];}//---------------------------------------------------------------// Name: initBoard()// Desc: Initialise / clear the game board//---------------------------------------------------------------void GameBoard::initBoard(){ board.assign(boardSize, '\0');}//---------------------------------------------------------------// Name: convertIndex()// Desc: Convert 2D Row / Column Index to 1D Value.//---------------------------------------------------------------unsigned GameBoard::convertIndex(unsigned row, unsigned column){ return row * (boardDims) + column;}//---------------------------------------------------------------// Name: setBoard()// Desc: Checks to make sure the choice is empty and valid,// e.g. within the range of 0 and 2 for both row and column// and does not allready contain a O or X.// Sets the location row, column to either X or O// depending on the argument marker.//---------------------------------------------------------------bool GameBoard::setBoard(unsigned row, unsigned column, WCHAR playerMarker){ // Check to see if either row or column is greater than // the actual board size. if((convertIndex(row, column) < 0) || (convertIndex(row, column) > boardSize)) { return false; } // Check to see if the players choice is an empty position. if(getBoard(row, column) != '\0') { return false; } // The user choice is valid and the location is empty so update // the game board with the players relevant marker either X or O. board[convertIndex(row, column)] = playerMarker; return true;}//---------------------------------------------------------------// Name: setBoard()// Desc: ** Temp solution for setting board for AI **//---------------------------------------------------------------bool GameBoard::setBoard(unsigned move, WCHAR playerMarker){ // Check to see if the players choice is an empty position. if(board[move] != '\0') { return false; } board[move] = playerMarker; return true;}//---------------------------------------------------------------// Name: unsetBoard()// Desc: Sets the board location row, col to empty.//---------------------------------------------------------------void GameBoard::unsetBoard(unsigned row, unsigned column){ board[convertIndex(row, column)] = '\0';}//---------------------------------------------------------------// Name: gameOver()// Desc: Returns true if there are still spaces available or false// otherwise.//---------------------------------------------------------------const bool GameBoard::gameOver(){ for(unsigned index = 0; index < boardSize; index++) { // Check to see if there are spaces still available. if(board[index] == '\0') { return true; } } return false;}//---------------------------------------------------------------// Name: checkWinningLine()// Desc: Returns true if player has winning line or false otherwise.//---------------------------------------------------------------const bool GameBoard::checkWinningLine(WCHAR playerMarker){ int rowCheck; int colCheck; int diagCheck = 0; int diagCheck1 = 0; int y = 0; int c = 2; for(int i = 0; i < 3; i++, y++, c--) { rowCheck = 0; colCheck = 0; if(getBoard(i, y) == playerMarker) diagCheck++; if(getBoard(i, c) == playerMarker) diagCheck1++; for(int j = 0; j < 3; j++) { if(getBoard(i, j) == playerMarker) rowCheck++; if(getBoard(j, i) == playerMarker) colCheck++; } if((rowCheck == 3) || (colCheck == 3)) return true; } if((diagCheck == 3) || (diagCheck1 == 3)) return true; return false;}
Player.cpp
//---------------------------------------------------------------// File: Player.cpp//// Desc: Implements the players.//// Date: January 2nd, 2010.//---------------------------------------------------------------#include "Player.h"//---------------------------------------------------------------// Name: Constructor()// Desc: Player Class Constructor//---------------------------------------------------------------Player::Player() : gamesWon(0){ }//---------------------------------------------------------------// Name: getName()// Desc: Returns the value held in the attribute playerName.//---------------------------------------------------------------const std::wstring& Player::getName(){ return name;}//---------------------------------------------------------------// Name: getMarker()// Desc: Returns the value held in the attribute playerMarker.//---------------------------------------------------------------const WCHAR Player::getMarker(){ return marker;}//---------------------------------------------------------------// Name: getRow()// Desc: Returns the value held in the attribute playerRow.//---------------------------------------------------------------const int Player::getRow(){ return row;}//---------------------------------------------------------------// Name: getColumn()// Desc: Returns the value held in the attribute playerColumn.//---------------------------------------------------------------const int Player::getColumn(){ return column;}//---------------------------------------------------------------// Name: getGamesWon()// Desc: Returns the value held in the attribute gamesWon.//---------------------------------------------------------------const int Player::getGamesWon(){ return gamesWon;}//---------------------------------------------------------------// Name: getState()// Desc: Returns the value held in the attribute State.//---------------------------------------------------------------const bool Player::getState(){ return state;}//---------------------------------------------------------------// Name: setName()// Desc: Sets the attribute playerName.//---------------------------------------------------------------void Player::setName(const std::wstring &playerName){ name = playerName;}//---------------------------------------------------------------// Name: setMarker()// Desc: Sets the attribute playerMarker to either X or O.//---------------------------------------------------------------void Player::setMarker(WCHAR playerMarker){ marker = playerMarker;}//---------------------------------------------------------------// Name: setRow()// Desc: Sets the attribute playerRow to either 0, 1 or 2.//---------------------------------------------------------------void Player::setRow(int playerRow){ row = playerRow;}//---------------------------------------------------------------// Name: setColumn()// Desc: Sets the attribute playerColumn to either 0, 1 or 2.//---------------------------------------------------------------void Player::setColumn(int playerColumn){ column = playerColumn;}//---------------------------------------------------------------// Name: setState()// Desc: Sets the attribute state to either true or false.//---------------------------------------------------------------void Player::setState(bool playerState){ state = playerState;}//---------------------------------------------------------------// Name: incrementGamesWon()// Desc: Increments the number of games the player has won by one.//---------------------------------------------------------------void Player::incrementGamesWon(){ gamesWon++;}//---------------------------------------------------------------// Name: clearGamesWon()// Desc: Clears the number of games the player has won to zero.//---------------------------------------------------------------void Player::clearGamesWon(){ gamesWon = 0;}
WinMain.cpp
//---------------------------------------------------------------// File: WinMain.cpp//// Desc: Game Application// // Date: January 2nd, 2010.//---------------------------------------------------------------#include <windows.h>#include <tchar.h>#include <time.h>#include <stdio.h>#include <stdlib.h>#include "GameBoard.h"#include "Player.h"#include "Resource.h"// Windows application variables.HWND g_hWnd = 0; HINSTANCE g_hInstance = 0; BOOL g_bActive = FALSE; BOOL g_bDone = FALSE;TCHAR g_szAppClass[] = TEXT( "Application" );TCHAR g_szAppTitle[] = TEXT( "Tic Tac Toe V0.1" );// Tic Tac Toe game application variables.GameBoard TicTacToe; // Instantiate an object of GameBoard for the game.Player players[2]; // Array of two Player objects.bool onePlayerGame; // If True then single player game, if false then two player game.std::wstring names[2]; // Array holding the players names.int currentPlayer; // Determines if its player 0 or player 1's turn.bool bValidMove; // Is the move valid.bool gameOver; // Is the game over, true if the board is full or someone has won.int aiMove; // Holds the computers choice.WCHAR marker[2] = {'X', 'O'}; // Array holding the players markers.bool state[2] = {true, false}; // Array holding the players state. True = current active player.// Tic Tac Toe game application function prototypes.void initGame();const void displayBoard(GameBoard &gameBoard, Player &playerOne, Player &playerTwo, HWND hWnd);bool gameState(GameBoard &TicTacToe, Player &player, HWND hWnd);int random();bool placeMarker(GameBoard &TicTacToe, Player &player, LPARAM lParam, HWND hWnd);int evaluateAI(GameBoard &TicTacToe, int Player, int &bestMove);bool placeMarkerAI(GameBoard &TicTacToe, Player &player, int aiMove, HWND hWnd);void drawX(int row, int column, HDC hdc);void drawO(int row, int column, HDC hdc);bool replayGame();void finalScores();int setCurrentPlayer();// Windows application function prototypes.INT WINAPI WinMain( HINSTANCE, HINSTANCE, LPSTR, INT );LRESULT WINAPI MsgProc( HWND, UINT, WPARAM, LPARAM );BOOL CALLBACK IntroDlgProc( HWND hIntroDlg, UINT Message, WPARAM wParam, LPARAM lParam );//---------------------------------------------------------------// Name: WinMain()// Desc: Called as soon as the Application is loaded.//---------------------------------------------------------------int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ){ WNDCLASSEX wndclass; HWND hWnd; MSG msg; // Setup the window attributes wndclass.cbSize = sizeof( wndclass ); wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS; wndclass.lpfnWndProc = MsgProc; wndclass.cbClsExtra = 0; wndclass.cbWndExtra = 0; wndclass.hInstance = hInstance; wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ); wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ); wndclass.hbrBackground = ( HBRUSH ) ( COLOR_WINDOW ); wndclass.lpszMenuName = NULL; wndclass.lpszClassName = g_szAppClass; // Registered class name wndclass.hIconSm = LoadIcon( NULL, IDI_APPLICATION ); // Register the new window if( RegisterClassEx( &wndclass ) == 0 ) exit(1); // Create the main window. Window parameters make the main window // 640X480 only hWnd = CreateWindow( g_szAppClass, // Class Name g_szAppTitle, // Name displayed on title bar WS_OVERLAPPEDWINDOW, // Window style, using Popup 0, // X Position (Top Left) 0, // Y Position (Top Right) 640, // X Position (Bottom Right) 640, // Y Position (Bottom Left) NULL, NULL, hInstance, NULL ); // Tell windows to show the application ShowWindow( hWnd, nCmdShow ); // Update the client by sending an initial WM_PAINT msg UpdateWindow( hWnd ); // Save the main window handle g_hWnd = hWnd; // Save the main application instance g_hInstance = hInstance; // call device selection dialog INT nResult = DialogBox( g_hInstance, MAKEINTRESOURCE( IDD_DIALOG1 ), hWnd, IntroDlgProc ); if( nResult == -1 ) { MessageBox( hWnd, L"IntroDialog() Error", L"WinMain()", MB_OK | MB_ICONINFORMATION ); return E_FAIL; } if( nResult == 2 ) { MessageBox( hWnd, L"IntroDialog() Cancelled by user", L"WinMain()", MB_OK | MB_ICONINFORMATION ); return S_FALSE; } initGame(); // Main Message Loop. Keep looping through this application until // the user chooses to exit. When the user chooses exit by clicking // the red X box top right of the window for example. A WM_QUIT // message will be sent to the application which prompts the application // to exit. ZeroMemory( &msg, sizeof( msg ) ); while( !g_bDone ) { while( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } if(msg.message == WM_QUIT) { break; } // Set the current player. currentPlayer = setCurrentPlayer(); if( g_bActive ) { if(onePlayerGame) { if(players[currentPlayer].getName() == L"Computer") { evaluateAI(TicTacToe, currentPlayer, aiMove); bValidMove = placeMarkerAI(TicTacToe, players[currentPlayer], aiMove, hWnd); if(bValidMove) { if(players[0].getState()) { players[0].setState(false); players[1].setState(true); } else if(!players[0].getState()) { players[0].setState(true); players[1].setState(false); } } else { std::wstring pErrorText = players[currentPlayer].getName() + L" please try again!. "; MessageBox( hWnd, pErrorText.c_str(), L"MsgProc WM_LBUTTONDOWN Error", MB_OK | MB_ICONINFORMATION ); } InvalidateRect(hWnd, 0, TRUE); gameOver = gameState(TicTacToe, players[currentPlayer], hWnd); if(gameOver) { if(!replayGame()) { g_bDone = TRUE; finalScores(); PostMessage( g_hWnd, WM_CLOSE, 0, 0 ); } else { bValidMove = FALSE; TicTacToe.initBoard(); InvalidateRect(hWnd, 0, TRUE); } } } } } } UnregisterClass( g_szAppClass, hInstance ); // Return back to Windows return( ( int ) msg.wParam );}//---------------------------------------------------------------// Name: MsgProc()// Desc: This is the main window message handling function//---------------------------------------------------------------LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam ){ PAINTSTRUCT ps; HDC hdc; bool bValidMove; bool gameOver; int aiMove; switch( msg ) { case WM_LBUTTONDOWN: currentPlayer = setCurrentPlayer(); if(players[currentPlayer].getName() != L"Computer") { bValidMove = placeMarker(TicTacToe, players[currentPlayer], lParam, hWnd); } if(bValidMove) { if(players[0].getState()) { players[0].setState(false); players[1].setState(true); } else if(!players[0].getState()) { players[0].setState(true); players[1].setState(false); } } else { std::wstring pErrorText = players[currentPlayer].getName() + L" please try again!. "; MessageBox( hWnd, pErrorText.c_str(), L"MsgProc WM_LBUTTONDOWN Error", MB_OK | MB_ICONINFORMATION ); } InvalidateRect(hWnd, 0, TRUE); gameOver = gameState(TicTacToe, players[currentPlayer], hWnd); if(gameOver) { if(!replayGame()) { g_bDone = TRUE; finalScores(); PostMessage( g_hWnd, WM_CLOSE, 0, 0 ); } else { bValidMove = FALSE; TicTacToe.initBoard(); InvalidateRect(hWnd, 0, TRUE); } } break; case WM_PAINT: hdc = BeginPaint(hWnd, &ps); displayBoard(TicTacToe, players[0], players[1], hWnd); // Draw Xs and Os for (int iX = 0; iX < 3; iX++) { for (int iY = 0; iY < 3; iY++) { if(TicTacToe.getBoard(iY, iX) == 'X') { drawX(iY, iX, hdc); } else if (TicTacToe.getBoard(iY, iX) == 'O') { drawO(iY, iX, hdc); } } } EndPaint(hWnd, &ps); break; case WM_ACTIVATE: // The message WM_ACTIVE is sent whenever the application has focus. // For example if the application is minimised then it is not in focus. // As the application is not in focus it is pointless wasting clock // cycles and graphics hardware to render to what cannot be seen. g_bActive = ( BOOL ) wParam; break; case WM_KEYDOWN: // Whenever a key is pressed (non-accelerated ie shift and f5, then // this message is sent. switch( wParam ) { case VK_ESCAPE: // If the user presses the Escape key then we will quit the // application g_bDone = TRUE; PostMessage( g_hWnd, WM_CLOSE, 0, 0 ); return 0; }; break; case WM_DESTROY: // The message WM_DESTROY is sent by the Windows Messaging System // when our message queue recieves the WM_QUIT message from the // application. It just cleans everything up and quits the // application g_bDone = TRUE; PostQuitMessage( 0 ); return 1; break; default: break; } // If any messages reach here then it was not handled by our application. // So we will return it back to the queue so the next Windows application // like a background task. return DefWindowProc( hWnd, msg, wParam, lParam );}//---------------------------------------------------------------// Name: IntroDlgProc()// Desc: IntroDialog Message Handler//---------------------------------------------------------------BOOL CALLBACK IntroDlgProc( HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam ){ HWND hP1Name = GetDlgItem( hDlg, IDC_P1Name ); HWND hP2Name = GetDlgItem( hDlg, IDC_P2Name ); HWND h1PGame = GetDlgItem( hDlg, IDC_1PGame ); HWND h2PGame = GetDlgItem( hDlg, IDC_2PGame ); TCHAR P1Name[128]; TCHAR P2Name[128]; switch( Message ) { case WM_INITDIALOG: // Set the radio button to Two Player SendMessage( h2PGame, BM_SETCHECK, BST_CHECKED, 0 ); return TRUE; break; case WM_COMMAND: switch( LOWORD( wParam ) ) { case IDOK: GetDlgItemText ( hDlg , IDC_P1Name , P1Name , 128 ); GetDlgItemText ( hDlg , IDC_P2Name , P2Name , 128 ); names[0] = P1Name; names[1] = P2Name; onePlayerGame = SendMessage( h2PGame, BM_GETCHECK, 0, 0 ) != BST_CHECKED; EndDialog( hDlg, IDOK ); return TRUE; break; case IDCANCEL: EndDialog( hDlg, IDCANCEL ); return TRUE; break; case IDC_P1Name: break; case IDC_P2Name: break; case IDC_1PGame: if( SendMessage( h1PGame, BM_GETCHECK, 0, 0 ) == BST_CHECKED ) SetWindowText(hP2Name, L"Computer"); onePlayerGame = TRUE; break; case IDC_2PGame: if( SendMessage( h2PGame, BM_GETCHECK, 0, 0 ) == BST_CHECKED ) SetWindowText(hP2Name, L""); onePlayerGame = FALSE; break; } break; } return FALSE;}//---------------------------------------------------------------// Name: initGame()// Desc: //---------------------------------------------------------------void initGame(){ srand ( time(NULL) ); // Initialises the random seed for the random() function. if(random() % 2) { std::swap(marker[0], marker[1]); } if(random() % 2) { std::swap(state[0], state[1]); } players[0].setName(names[0]); players[0].setMarker(marker[0]); players[0].setState(state[0]); players[1].setName(names[1]); players[1].setMarker(marker[1]); players[1].setState(state[1]); if(players[0].getState()) { std::wstring pText = players[0].getName() + L" you will go first and your marker will be an " + players[0].getMarker() + L"\n\n" + players[1].getName() + L" you will go second and your marker will be an " + players[1].getMarker(); MessageBox( g_hWnd, pText.c_str(), L"Player Settings", MB_OK | MB_ICONINFORMATION ); currentPlayer = 0; } else { std::wstring pText = players[1].getName() + L" you will go first and your marker will be an " + players[1].getMarker() + L"\n\n" + players[0].getName() + L" you will go second and your marker will be an " + players[0].getMarker(); MessageBox( g_hWnd, pText.c_str(), L"Player Settings", MB_OK | MB_ICONINFORMATION ); currentPlayer = 1; } // Initialise the gameboard TicTacToe.initBoard(); InvalidateRect(g_hWnd, 0, TRUE);}//---------------------------------------------------------------// Name: placeMarker()// Desc: //---------------------------------------------------------------bool placeMarker(GameBoard &TicTacToe, Player &player, LPARAM lParam, HWND hWnd){ int x = LOWORD(lParam); int y = HIWORD(lParam); bool posOK = true; // If the player has not clicked on the actual board but somewhere else // on the screen then display an error message. if( ( ( y < 100 ) || ( y > 550 ) || ( x < 95 ) || ( x > 545 ) ) ) { std::wstring pErrorText = players[currentPlayer].getName() + L" please click on the board. "; MessageBox( hWnd, pErrorText.c_str(), L"Selection Error", MB_OK | MB_ICONINFORMATION ); posOK = false; } // If the user has clicked on the gameboard then work out where they have // clicked by converting the x and y LPARAM coordinates into suitable // row and column coordinates and then set the current players row // and column attributes to those coordinates. else { if( ( y >= 100 ) && ( y <= 250 ) ) { if( ( x >= 95 ) && ( x <= 245 ) ) { players[currentPlayer].setRow(0); players[currentPlayer].setColumn(0); } if( ( x >= 246 ) && ( x <= 395 ) ) { players[currentPlayer].setRow(0); players[currentPlayer].setColumn(1); } if( ( x >= 396 ) && ( x <= 545 ) ) { players[currentPlayer].setRow(0); players[currentPlayer].setColumn(2); } } if( ( y >= 251 ) && ( y <= 400 ) ) { if( ( x >= 95 ) && ( x <= 245 ) ) { players[currentPlayer].setRow(1); players[currentPlayer].setColumn(0); } if( ( x >= 246 ) && ( x <= 395 ) ) { players[currentPlayer].setRow(1); players[currentPlayer].setColumn(1); } if( ( x >= 396 ) && ( x <= 545 ) ) { players[currentPlayer].setRow(1); players[currentPlayer].setColumn(2); } } if( ( y >= 401 ) && ( y <= 550 ) ) { if( ( x >= 95 ) && ( x <= 245 ) ) { players[currentPlayer].setRow(2); players[currentPlayer].setColumn(0); } if( ( x >= 246 ) && ( x <= 395 ) ) { players[currentPlayer].setRow(2); players[currentPlayer].setColumn(1); } if( ( x >= 396 ) && ( x <= 545 ) ) { players[currentPlayer].setRow(2); players[currentPlayer].setColumn(2); } } if(!TicTacToe.setBoard(players[currentPlayer].getRow(), players[currentPlayer].getColumn(), players[currentPlayer].getMarker())) { std::wstring pError = L"The values can only be between 0 and 2 and the position must be empty!"; MessageBox( hWnd, pError.c_str(), L"Player Settings", MB_OK | MB_ICONINFORMATION ); posOK = false; } } return posOK;}//---------------------------------------------------------------// Name: evaluateAI()// Desc: Uses basic Negamax algorithim to determine where the // AI places it's next move.//---------------------------------------------------------------int evaluateAI(GameBoard &TicTacToe, int Player, int &bestMove ){ int bestScore; int score; #define WIN 1; #define LOSS -1; #define DRAW 0; if(!TicTacToe.gameOver()) { if(TicTacToe.checkWinningLine(players[Player].getMarker())) { return WIN; } if(TicTacToe.checkWinningLine(players[1 - Player].getMarker())) { return LOSS; } return DRAW; } else if(TicTacToe.checkWinningLine(players[Player].getMarker())) { return WIN; } else if(TicTacToe.checkWinningLine(players[1 - Player].getMarker())) { return LOSS; } else { bestScore = LOSS; int notUsed; for(int row = 0; row < 3; row++) { for(int col = 0; col < 3; col++) { if(TicTacToe.getBoard(row, col) == '\0') { TicTacToe.setBoard(row, col, players[Player].getMarker()); score = -evaluateAI(TicTacToe, 1 - Player, notUsed); if(score > bestScore) { bestMove = TicTacToe.convertIndex(row, col); bestScore = score; } TicTacToe.unsetBoard(row, col); } } } } return bestScore;}//---------------------------------------------------------------// Name: placeMarkerAI()// Desc: Places the AI's marker once evaluateAI() has// returned an appropriate value.//---------------------------------------------------------------bool placeMarkerAI(GameBoard &TicTacToe, Player &player, int aiMove, HWND hWnd){ bool posOK = true; if(!TicTacToe.setBoard(aiMove, players[currentPlayer].getMarker())) { std::wstring pError = L"The values can only be between 0 and 2 and the position must be empty!"; MessageBox( hWnd, pError.c_str(), L"Player Settings", MB_OK | MB_ICONINFORMATION ); posOK = false; } return posOK;}//---------------------------------------------------------------// Name: random()// Desc: Return a random number.//---------------------------------------------------------------int random(){ return rand() % 10 + 1;}//---------------------------------------------------------------// Name: displayBoard()// Desc: Display the game board.//---------------------------------------------------------------const void displayBoard(GameBoard &gameBoard, Player &playerOne, Player &playerTwo, HWND hWnd){ const std::vector<char> &board = gameBoard.getBoard(); HDC hdc = GetDC(hWnd); SetTextColor(hdc, RGB(131, 139, 131)); SetBkColor(hdc, RGB(0, 0, 0)); SetBkMode(hdc, TRANSPARENT); std::wstring title = L"Tic Tac Toe by Paul Kirkbride"; TextOut(hdc, 215, 20, title.c_str() ,title.length()); SetTextColor(hdc, RGB(49, 79, 79)); SetBkColor(hdc, RGB(0, 0, 0)); SetBkMode(hdc, TRANSPARENT); std::wstring playerInfo = L"Player1: " + playerOne.getName() + L" ( " + playerOne.getMarker() + L" ) Player2: " + playerTwo.getName() + L" ( " + playerTwo.getMarker() + L" ) "; TextOut(hdc, 150, 50, playerInfo.c_str() ,playerInfo.length()); HPEN black_pen = CreatePen(PS_SOLID, 3, RGB(0, 0, 0)); HPEN old_pen = (HPEN)SelectObject(hdc, black_pen); // Top Horizontal Line MoveToEx(hdc, 95, 250, NULL); LineTo(hdc, 545, 250); // Bottom Horizontal Line MoveToEx(hdc, 95, 400, NULL); LineTo(hdc, 545, 400); // First Vertical Line MoveToEx(hdc, 245, 100, NULL); LineTo(hdc, 245, 550); // Second Vertical Line MoveToEx(hdc, 395, 100, NULL); LineTo(hdc, 395, 550);}//---------------------------------------------------------------// Name: gameState()// Desc: Checks to see if the current player has won the game.//---------------------------------------------------------------bool gameState(GameBoard &TicTacToe, Player &player, HWND hWnd){ bool gameOver = false; bool won = false; if(TicTacToe.checkWinningLine(player.getMarker())) { std::wstring pWon = L"Congratulations " + player.getName() + L" you have won the game."; MessageBox( hWnd, pWon.c_str(), L"Game State", MB_OK | MB_ICONINFORMATION ); player.incrementGamesWon(); gameOver = true; won = true; } if((!TicTacToe.gameOver()) && (!won)) { std::wstring pWon = L"Unlucky " + players[0].getName() + L" and " + players[1].getName() + L" you have tied."; MessageBox( hWnd, pWon.c_str(), L"Game State", MB_OK | MB_ICONINFORMATION ); gameOver = true; } return gameOver;}//---------------------------------------------------------------// Name: drawX()// Desc: //---------------------------------------------------------------void drawX(int row, int column, HDC hdc){ const int squareSize = 150; HPEN hPenOld; HPEN hLinePen; COLORREF lineColor; lineColor = RGB(255, 0, 0); const int penWidth = 10; hLinePen = CreatePen(PS_SOLID, penWidth, lineColor); hPenOld = (HPEN)SelectObject(hdc, hLinePen); // Get Bounds. const int lowX = column * squareSize + 20 * penWidth; const int highX = (column + 1) * squareSize - 2 * penWidth; const int lowY = row * squareSize + 20 * penWidth; const int highY = (row + 1) * squareSize - 2 * penWidth; MoveToEx(hdc, lowX, lowY, NULL); LineTo(hdc, highX, highY); MoveToEx(hdc, lowX, highY, NULL); LineTo(hdc, highX, lowY); SelectObject(hdc, hPenOld); DeleteObject(hLinePen);}//---------------------------------------------------------------// Name: drawO()// Desc: //---------------------------------------------------------------void drawO(int row, int column, HDC hdc){ const int squareSize = 150; HPEN hPenOld; HPEN hLinePen; COLORREF lineColor; lineColor = RGB(0, 0, 255); const int penWidth = 10; hLinePen = CreatePen(PS_SOLID, penWidth, lineColor); hPenOld = (HPEN)SelectObject(hdc, hLinePen); // Get Bounds. const int lowX = column * squareSize + 20 * penWidth; const int highX = (column + 1) * squareSize - 2 * penWidth; const int lowY = row * squareSize + 20 * penWidth; const int highY = (row + 1) * squareSize - 2 * penWidth; Arc(hdc, lowX, lowY, highX, highY, 0, 0, 0, 0); SelectObject(hdc, hPenOld); DeleteObject(hLinePen);}//---------------------------------------------------------------// Name: replayGame()// Desc: Once the game is over, either one has won or its a tie// then do we want to play again.//---------------------------------------------------------------bool replayGame(){ int response; bool replay = false; response = MessageBox( g_hWnd, L"Would you like to play again?", L"Replay Game", MB_YESNO | MB_ICONINFORMATION ); if( response == IDYES) { replay = true; } return replay;}//---------------------------------------------------------------// Name: finalScores()// Desc: Just before the application closes we let the players// know their final scores.//---------------------------------------------------------------void finalScores(){ std::wstring pScores; if(players[0].getGamesWon() > players[1].getGamesWon()) { pScores = L"Congratulations " + players[0].getName() + L" you have won the most games."; } if(players[0].getGamesWon() == players[1].getGamesWon()) { pScores = players[0].getName() + L" you have tied with " + players[1].getName(); } if(players[1].getGamesWon() > players[0].getGamesWon()) { pScores = L"Congratulations " + players[1].getName() + L" you have won the most games."; } MessageBox( g_hWnd, pScores.c_str(), L"Final Scores", MB_OK | MB_ICONINFORMATION );}//---------------------------------------------------------------// Name: setCurrentPlayer()// Desc: Switches the current player.//---------------------------------------------------------------int setCurrentPlayer(){ int curPlayer; if(players[0].getState()) { curPlayer = 0; } else { curPlayer = 1; } return curPlayer;}
The main thing that jumps out me is all of the unnecessary comments. Try to comment your code in obscure places where you think you may forget what the code does later on. For example, in Player.h:
The comment is completely unnecessary. Player() is nothing but a constructor.
Also, the Draw0 and DrawX could've been combined into a single function with a switch that determines either X or Y.
Also, here:
I think you meant "Tell the application to show the window" ;] Either way, the comment is unnecessary since it's present in almost all Win32API games so you're unlikely to forget what it does.
It is my own custom to include the main logic functions at the top of the source file because they are the ones that I'm mainly working on and having to scroll all the way down past the initializing sequences drives me nuts. I hear that people consider not using function prototypes a big no-no, but I find it inconvenient and unnecessary. For example, in the WinMain file, it is clear that you have been mostly working on the window procedure functions and other control functions, therefore, it would make sense to put them before WinMain so you have access to them first and don't have to scroll down to find them. It doesn't make so much of a difference code-wise, but it makes our life easier by sparing us some time while programming.
// Player Class constructorPlayer();
The comment is completely unnecessary. Player() is nothing but a constructor.
Also, the Draw0 and DrawX could've been combined into a single function with a switch that determines either X or Y.
Also, here:
// Tell the window to show the applicationShowWindow(hWnd, iCmdShow);
I think you meant "Tell the application to show the window" ;] Either way, the comment is unnecessary since it's present in almost all Win32API games so you're unlikely to forget what it does.
It is my own custom to include the main logic functions at the top of the source file because they are the ones that I'm mainly working on and having to scroll all the way down past the initializing sequences drives me nuts. I hear that people consider not using function prototypes a big no-no, but I find it inconvenient and unnecessary. For example, in the WinMain file, it is clear that you have been mostly working on the window procedure functions and other control functions, therefore, it would make sense to put them before WinMain so you have access to them first and don't have to scroll down to find them. It doesn't make so much of a difference code-wise, but it makes our life easier by sparing us some time while programming.
Okay. So I've got time for a few:
Starting with GameBoard:
1. boardSize and boardDims.
First, boardDims is confusingly named.
Second. boardSize is unnecessary. It is always the same as you can get the same exact value with board.size()
Third, since they are set during the constructor, I can't help but wonder why they are set at all. Couldn't they be anonymous enum constants?
2. WCHAR
First, board is a vector<char>, but you are using WCHAR as the marker.
Second, you are using a character as a representation of a marker on the board. Use an named enum:
3. getBoardSize, getBoardDims, getBoard, initBoard, setBoard, unsetBoard
You have a lot of functions with the word "Board" within in.
Your class is already called GameBoard.
Chances are the instances of this class will be called something with "board" in it.
The individual sections of the board are squares or cells. The function initBoard needs only be called init, or even "initialize", or even something like "clear" or "reset". Don't be redundant, but don't be afraid of the keyboard either.
4. convertIndex
should the user of the class have this function exposed to him?
5. Range checking
I'll grand that you do perform some range checking. However, if I were to call setBoard with a row of 0 and a column of 8, it would still pass your range checking.
-----
And I'll stop there. That's all the time I have.
You've got a basic command of syntax, and can put together something that works. That says something. You actually understand what you are doing.
Now you need to work on how to properly abstract things in a way that makes sense, and takes into account the needs and uses of a class.
You also need to decouple in your thinking the idea that because you are showing "X"s and "O"s in your game that you need to represent these entities as actual "X"s and "O"s. You do not need to do this. You could simply have had 0, 1, and 2 represent empty, x, and o. This is a common problem, and relates to my point about abstraction.
Good luck.
Starting with GameBoard:
1. boardSize and boardDims.
First, boardDims is confusingly named.
Second. boardSize is unnecessary. It is always the same as you can get the same exact value with board.size()
Third, since they are set during the constructor, I can't help but wonder why they are set at all. Couldn't they be anonymous enum constants?
enum{board_width=3,board_height=3};
2. WCHAR
First, board is a vector<char>, but you are using WCHAR as the marker.
Second, you are using a character as a representation of a marker on the board. Use an named enum:
enum marker_t{marker_none,marker_x,marker_o};
3. getBoardSize, getBoardDims, getBoard, initBoard, setBoard, unsetBoard
You have a lot of functions with the word "Board" within in.
Your class is already called GameBoard.
Chances are the instances of this class will be called something with "board" in it.
The individual sections of the board are squares or cells. The function initBoard needs only be called init, or even "initialize", or even something like "clear" or "reset". Don't be redundant, but don't be afraid of the keyboard either.
4. convertIndex
should the user of the class have this function exposed to him?
5. Range checking
I'll grand that you do perform some range checking. However, if I were to call setBoard with a row of 0 and a column of 8, it would still pass your range checking.
-----
And I'll stop there. That's all the time I have.
You've got a basic command of syntax, and can put together something that works. That says something. You actually understand what you are doing.
Now you need to work on how to properly abstract things in a way that makes sense, and takes into account the needs and uses of a class.
You also need to decouple in your thinking the idea that because you are showing "X"s and "O"s in your game that you need to represent these entities as actual "X"s and "O"s. You do not need to do this. You could simply have had 0, 1, and 2 represent empty, x, and o. This is a common problem, and relates to my point about abstraction.
Good luck.
I have copied down to my PC and had a more detailed look into it to give perhaps some more insightful commentary. I'm basing this on my own experiences I've had and frankly, I'm still a noob in this myself so don't take my word too seriously :D Just a couple of my own thoughts on how I'd do things differently.
In WinMain.cpp
-As I've mentioned, there are a lot of functions here that could have been condensed or taken care of somewhere else, mainly the DrawO and DrawX functions. The same goes for the variables. For example, you have a class for a Player, but you are still storing. bValidMove shouldn't be a global variable if you ask me. It should be the return value of a move function or something that should be checked immediately after it was received and taken action to handle it; the same thing goes for gameOver. I can't figure out why there is an integer holding the computer's move; it should have probably taken care of in the evaluateAI function. I'm not sure how the marker and state arrays could be fixed, so I'll come back to them when I looked through the rest of the code, but, at first glance, I would use enumerators for marker and completely remove state[2] since you already have currentPlayer in use.
-Moving out into to the functions. Many of these could have been part of the GameBoard class, mainly displayBoard, drawX/O, or placeMarker/AI functions. In those drawing functions, you could call the member function from the WM_PAINT message and passed in the HDC as an argument. This wouldn't make your WinMain file as crowded and would be easy to deal with. SetCurrentPlayer is unecessary as it's own function. This:
players[0].getState() == 0 ? currentPlayer = 1 : currentPlayer = 0;
This way you don't have to call a separate function. This would rid of 6 functions in your code. Also, you don't have to make a function prototype for WinMain.
--WinMain
This function is your usual starting point for win32 applications. You should probably leave this function all the way at the bottom and put the game loop in it's own function so that you don't have to bother with the overhead code. So everything starting with initGame() to the end of the function should go in it's own function called by the return statement, which will receive the return value of that function. This is what I'm talking about:
return Run();
You pass in arguments to Run as you see fit. I don't think you have to UnregisterClass unless you are planning on using the same running program to handle a completely different task and no longer need the registered class.
------
I think I'm going to stop here :D There is a lot of things that I handled differently and I'd be here until tomorrow describing all.
In WinMain.cpp
-As I've mentioned, there are a lot of functions here that could have been condensed or taken care of somewhere else, mainly the DrawO and DrawX functions. The same goes for the variables. For example, you have a class for a Player, but you are still storing. bValidMove shouldn't be a global variable if you ask me. It should be the return value of a move function or something that should be checked immediately after it was received and taken action to handle it; the same thing goes for gameOver. I can't figure out why there is an integer holding the computer's move; it should have probably taken care of in the evaluateAI function. I'm not sure how the marker and state arrays could be fixed, so I'll come back to them when I looked through the rest of the code, but, at first glance, I would use enumerators for marker and completely remove state[2] since you already have currentPlayer in use.
-Moving out into to the functions. Many of these could have been part of the GameBoard class, mainly displayBoard, drawX/O, or placeMarker/AI functions. In those drawing functions, you could call the member function from the WM_PAINT message and passed in the HDC as an argument. This wouldn't make your WinMain file as crowded and would be easy to deal with. SetCurrentPlayer is unecessary as it's own function. This:
players[0].getState() == 0 ? currentPlayer = 1 : currentPlayer = 0;
This way you don't have to call a separate function. This would rid of 6 functions in your code. Also, you don't have to make a function prototype for WinMain.
--WinMain
This function is your usual starting point for win32 applications. You should probably leave this function all the way at the bottom and put the game loop in it's own function so that you don't have to bother with the overhead code. So everything starting with initGame() to the end of the function should go in it's own function called by the return statement, which will receive the return value of that function. This is what I'm talking about:
return Run();
You pass in arguments to Run as you see fit. I don't think you have to UnregisterClass unless you are planning on using the same running program to handle a completely different task and no longer need the registered class.
------
I think I'm going to stop here :D There is a lot of things that I handled differently and I'd be here until tomorrow describing all.
This topic is closed to new replies.
Advertisement
Popular Topics
Advertisement