7
\$\begingroup\$

I've made a Tic-Tac-Toe program (non-AI) for 2 human players. I will implement AI for a computer player later on. I am a beginner programmer and am also new to classes, which I've implemented in this program. I want to know how this could be optimized.

//the main class is all the way at the end

#include "stdafx.h"
#include <iostream>
#include <string>
#include <Windows.h>

//To Implement Color Mechanics
HANDLE hCon;
enum Color { DARKBLUE = 1, DARKGREEN, DARKTEAL, DARKRED, DARKPINK, DARKYELLOW, GRAY, DARKGRAY, BLUE, GREEN, TEAL, RED, PINK, YELLOW, WHITE };

void SetColor(Color c){
        if(hCon == NULL)
                hCon = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(hCon, c);
}

//As of now the program asks for player names but this functionality was added later on, but that disrupted some methods in the game class
//so i declared these global variables to keep the changes to a minimum
std::string player1, player2;


//its the game class, all the methods and the constructor are written right after  the class
class game{
public:
     game();
     int insert(int,char);
     int win();
     void win_display();
private: 
    char board[9];
    void color_conv(char);
    void Winner(int);
};



//constructor for the class game
game::game(){
    SetColor(DARKGREEN);
    std::cout<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Tic-Tac-Taoe~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"<<std::endl;
    std::cout<<"*By Default Player 1 is 'O' and Player 2 is 'X'\n";
    std::cout<<"*The choice of cells is mapped out in the following legend\n\n\n";
    std::cout<<" 1  | 2  | 3  \n____|____|____\n 4  | 5  | 6  \n____|____|____\n 7  | 8  | 9  \n    |    |    \n\n";
    std::cout<<"********************************************************************************\n\n\n";

    SetColor(WHITE);

    //This fills up the board array with '-' a filler character, also it looks good :)
    for(int k=0;k<9;k++){
        board[k]='-';
    }
}



//this function in game class is used to insert the right mark, i.e 'O' or 'X' , in the player's choice of position
//the return values are used in the main program to determine a valid choice 
//if there is an invalid choice the current player is given another choice until he makes a valid choice 
int game::insert(int choice, char mark){

    SetColor(WHITE);
    if(choice<=9 && board[choice-1]=='-'){
        board[choice-1]=mark;
        std::cout<<" "<<board[0]<<"  | "<<board[1]<<"  | "<<board[2]<<"  \n____|____|____\n "<<board[3]<<"  | "<<board[4]<<"  | "<<board[5]<<"  \n____|____|____\n "<<board[6]<<"  | "<<board[7]<<"  | "<<board[8]<<"  \n    |    |    \n\n";
        return 1;
    }
    else if(choice>9){
        SetColor(RED);
        std::cout<<"Valid choices are only 1 through 9....\n\n";
        SetColor(WHITE);
        return 0;
    }

    else if(board[choice-1]!='-'){
        SetColor(RED);
        std::cout<<"This choice was already taken....\n\n";
        SetColor(WHITE);
        return 0;
    }
}

//this funtion determines if there is a win 
//return values are 1 for win, 0 for loss, 2 for draw
int game::win(){

    //the conditions listed are for row1,row2,row3,col1,col2,col3,diagonal1 and diagonal2 respectively                                     
    if(   (board[0]==board[1] && board[1]==board[2] && board[0]!='-')
        ||(board[3]==board[4] && board[4]==board[5] && board[3]!='-')
        ||(board[6]==board[7] && board[7]==board[8] && board[6]!='-')
        ||(board[0]==board[3] && board[3]==board[6] && board[0]!='-')
        ||(board[1]==board[4] && board[4]==board[7] && board[1]!='-')
        ||(board[2]==board[5] && board[5]==board[8] && board[2]!='-')
        ||(board[0]==board[4] && board[4]==board[8] && board[0]!='-')
        ||(board[2]==board[4] && board[4]==board[6] && board[2]!='-')){

            return 1;
    }

    //condition for draw----> if its not a win and the board is full i.e. no '-' chars 
    while(true){
    for(int k=0;k<9;k++){
        //if there are any '-' chars then the game is not complete thus we return 0
        if(board[k]=='-'){
            return 0;
        }
    }
    return 2;
    }

    return 0; 
}

//changes the color of the input character
//this is implemented by the "win_display()" 
void game::color_conv(char c){
    SetColor(TEAL);
    std::cout<<c;
    SetColor(WHITE);
}

//display mechanism for a win/draw situation
//this is implemented to selectively color the winning condition like row1, col1, etc
void game::win_display(){
    if((board[0]==board[1] && board[1]==board[2])){
        std::cout<<" ";
        color_conv(board[0]);
        std::cout<<"  | ";
        color_conv(board[1]);
        std::cout<<"  | ";
        color_conv(board[2]);
        std::cout<<"  \n____|____|____\n "<<board[3]<<"  | "<<board[4]<<"  | "<<board[5]<<"  \n____|____|____\n "<<board[6]<<"  | "<<board[7]<<"  | "<<board[8]<<"  \n    |    |    \n\n";
        Winner(0);
    }

    else if((board[3]==board[4] && board[4]==board[5])){
        std::cout<<" "<<board[0]<<"  | "<<board[1]<<"  | "<<board[2]<<"  \n____|____|____\n ";
        color_conv(board[3]);
        std::cout<<"  | ";
        color_conv(board[4]);
        std::cout<<"  | ";
        color_conv(board[5]);
        std::cout<<"  \n____|____|____\n "<<board[6]<<"  | "<<board[7]<<"  | "<<board[8]<<"  \n    |    |    \n\n";
        Winner(3);
    }

    else if((board[6]==board[7] && board[7]==board[8])){
        std::cout<<" "<<board[0]<<"  | "<<board[1]<<"  | "<<board[2]<<"  \n____|____|____\n "<<board[3]<<"  | "<<board[4]<<"  | "<<board[5]<<"  \n____|____|____\n ";
        color_conv(board[6]);
        std::cout<<"  | ";
        color_conv(board[7]);
        std::cout<<"  | ";
        color_conv(board[8]);
        std::cout<<"  \n    |    |    \n\n";
        Winner(6);
    }

    else if((board[0]==board[3] && board[3]==board[6])){
        std::cout<<" ";
        color_conv(board[0]);
        std::cout<<"  | "<<board[1]<<"  | "<<board[2]<<"  \n____|____|____\n ";
        color_conv(board[3]);
        std::cout<<"  | "<<board[4]<<"  | "<<board[5]<<"  \n____|____|____\n ";
        color_conv(board[6]);
        std::cout<<"  | "<<board[7]<<"  | "<<board[8]<<"  \n    |    |    \n\n";
        Winner(0);
    }

    else if((board[1]==board[4] && board[4]==board[7])){
        std::cout<<" "<<board[0]<<"  | ";
        color_conv(board[1]);
        std::cout<<"  | "<<board[2]<<"  \n____|____|____\n "<<board[3]<<"  | ";
        color_conv(board[4]);
        std::cout<<"  | "<<board[5]<<"  \n____|____|____\n "<<board[6]<<"  | ";
        color_conv(board[7]);
        std::cout<<"  | "<<board[8]<<"  \n    |    |    \n\n";
        Winner(1);
    }

    else if((board[2]==board[5] && board[5]==board[8])){
        std::cout<<" "<<board[0]<<"  | "<<board[1]<<"  | ";
        color_conv(board[2]);
        std::cout<<"  \n____|____|____\n "<<board[3]<<"  | "<<board[4]<<"  | ";
        color_conv(board[5]);
        std::cout<<"  \n____|____|____\n "<<board[6]<<"  | "<<board[7]<<"  | ";
        color_conv(board[8]);
        std::cout<<"  \n    |    |    \n\n";
        Winner(2);
    }

    else if((board[0]==board[4] && board[4]==board[8])){
        std::cout<<" ";
        color_conv(board[0]);
        std::cout<<"  | "<<board[1]<<"  | "<<board[2]<<"  \n____|____|____\n "<<board[3]<<"  | ";
        color_conv(board[4]);
        std::cout<<"  | "<<board[5]<<"  \n____|____|____\n "<<board[6]<<"  | "<<board[7]<<"  | ";
        color_conv(board[8]);
        std::cout<<"  \n    |    |    \n\n";
        Winner(0);
    }

    else if((board[2]==board[4] && board[4]==board[6])){
        std::cout<<" "<<board[0]<<"  | "<<board[1]<<"  | ";
        color_conv(board[2]);
        std::cout<<"  \n____|____|____\n "<<board[3]<<"  | ";
        color_conv(board[4]);
        std::cout<<"  | "<<board[5]<<"  \n____|____|____\n ";
        color_conv(board[6]);
        std::cout<<"  | "<<board[7]<<"  | "<<board[8]<<"  \n    |    |    \n\n";
        Winner(2);
    }
}

//again this function is implemented by "win_display()" to display which player won
void game::Winner(int k){
    if(board[k]=='O'){
            SetColor(YELLOW);
            std::cout<<player1<<" Won....\n";
            SetColor(WHITE);
        }
        else if(board[k]=='X'){
            SetColor(YELLOW);
            std::cout<<player2<<" Won....\n";
            SetColor(WHITE);
        }
}


// main function
int main(){
    int choice,i=1,turn_check;
    int check=0;
    game current_game;

    SetColor(DARKPINK);
    std::cout<<"Enter the 1st Player's Name....\n\n";
    std::getline(std::cin,player1);
    std::cout<<"Enter the 2nd Player's Name....\n\n";
    std::getline(std::cin,player2);
    SetColor(WHITE);

    //to play turn wise I implemented an odd\even check
    //as such when a valid choice is not made by the player, which we will know from the return value of game::insert()
    //the counter i is set decremented by 1 to give the current player another chance
    while(check==0){
    //player 1's turn
    if(i%2!=0){
    std::cout<<player1<<": Enter Your Choice...."<<std::endl;
    std::cin>>choice;
    turn_check=current_game.insert(choice,'O');
    }

    //check if the game is won
    check=current_game.win();

    //player 2's turn
    if(i%2==0){
    std::cout<<player2<<": Enter Your Choice...."<<std::endl;
    std::cin>>choice;
    turn_check=current_game.insert(choice,'X');
    }
    //check if the game is won
    check=current_game.win();

    //increment i for the next player's turn
    i++;

    //if player has not made a valid chance give the player another chance
    if(turn_check==0){
        i--;
    }
    }

    //at this point check=1 or 2 i.e. game is won or draw the following conditional statement check for this and displays appropriate message
    if(check==1){
    current_game.win_display();
    }

    else {
        SetColor(PINK);
        std::cout<<"The Game is a Draw!!!\n";
        SetColor(WHITE);
    }

    return 0;
}

//The End
\$\endgroup\$
0

2 Answers 2

3
\$\begingroup\$

Pseudo code for a simple 'AI':

// If there is a winning move
//    Take it.
// Otherwise, if there is a blocking move (opponent has 2 in a row with open square)
//    Take it.
// Otherwise, take one of these spots, if it is available, in this order:
//    Middle, Middle.
//    Any corner. //  <-- this can be randomized for different games.
//    Any middle edge. //  <-- same as above.

The corner and middle edges can be decided randomly so that the player has more fun via variety.

For another twist, and making the 'AI' both smarter, and dumber in ways, you could the above pseudo code so that there is a chance (random) of picking an action from a different 'tier'. The 'AI' would be smarter because it has a wider range of options, but dumber because they don't work as well.

\$\endgroup\$
3
  • \$\begingroup\$ ok got it....that's actually pretty simple i can use till learn the minimax algorithm or any thing else useful...but that's probably gonna take a while \$\endgroup\$ Aug 22, 2013 at 20:24
  • \$\begingroup\$ If you want an 'AI' that changes its behavior based on previous games, then yea, thats a bunch harder. \$\endgroup\$
    – Shade
    Aug 22, 2013 at 23:08
  • \$\begingroup\$ that seems advanced ....but right now my problem it seems is implementing class header ....its letting me use accessor methods to set strings ....gahh! it's so frustrating but i bet its like one really tiny mistake .... \$\endgroup\$ Aug 23, 2013 at 1:06
2
\$\begingroup\$
  • insert() and win() don't need to return something like an int, so they can both be void.

    For insert(), on the other hand, you have another return option: bool. If the insertion was successful, return true. If it failed, return false. I should note that this is purely for conditional circumstances, and this doesn't make your code exception-safe. I won't get into that here, though.

    Example of insert() returning bool, for your code:

    bool game::insert(int choice, char mark) {
    
        if (choice <= 9 && board[choice-1] == '-') {
            // do stuff for TRUE condition
            return true;
        }
        else if (choice > 9) {
            // do stuff for FALSE condition
            return false;
        }
        else if (board[choice-1] != '-') {
            // do other stuff for FALSE condition
            return false;
        }
    }
    

    For win(), you have different options:

    1. only test the condition at the call (as a bool)
    2. return a "Boolean enum" and test that at the call

    Let's look at both options:

    Testing at the call:

    // play the game...
    
    if (win())
    {
        // do something
    }
    else
    {
       // do something else
    }
    

    It doesn't even cover the draw, huh? Let's try the "Boolean enum":

    First, declare the enum:

    // you could put this inside the class or in a namespace
    // you do NOT need to assign these numbers in this case
    // I've done so anyway for illustration purposes
    
    enum Outcome { LOSE=0, WIN=1, DRAW=2 };
    

    Next, set up the function:

    Outcome game::win()
    {
        if (/* winning condition here */)
        {
            // do some stuff
            return WIN;
        }
        else if (/* losing condition here */)
        {
            // do some stuff
            return LOSE;
        }
        else // draw condition
        {
            // do some stuff
            return DRAW;
        }
    }
    

    Notice here that I've used an else instead of another if else. This is because the function could technically still not meet any of these conditions, thus it won't be able to return anything. And, since this is clearly not a void function (return doesn't matter), that would be bad. If your compiler is set to report warnings, then it'll tell you about this if violated.

    Finally, test this at the call:

    if (win() == WIN)
    {
        // stuff
    }
    else if (win() == LOSE)
    {
        // stuff
    }
    else if (win() == DRAW)
    {
        // and more stuff
    }
    

    Looks better, huh? Try both options and see which one you prefer. Also, yes, you're right; win() should be renamed. You could name it something like outcome().

\$\endgroup\$
7
  • \$\begingroup\$ sweet ....the enum was there in the code all along in the color function but never really noticed it ....thanks man i can think of a lot of previous programs that could benefit from this ....i think this makes the program more readable and neat ....thanks jamal ...you are great :) \$\endgroup\$ Aug 22, 2013 at 19:57
  • \$\begingroup\$ You're welcome! I may add more to this as I see fit. Someone else may also contribute more or add/improve on what I've mentioned. \$\endgroup\$
    – Jamal
    Aug 22, 2013 at 20:07
  • \$\begingroup\$ Awesome....I will make the necessary changes and re-post the code again sometime in the night by then i will probably get all the comments i need :) \$\endgroup\$ Aug 22, 2013 at 20:10
  • \$\begingroup\$ If you mean you'll edit the same code block, don't do that. Doing so (in general) will invalidate any answers. Instead, you may add the revised code blocks below the original. \$\endgroup\$
    – Jamal
    Aug 22, 2013 at 20:13
  • \$\begingroup\$ no no that's what i had in mind ....i was gonna do the latter \$\endgroup\$ Aug 22, 2013 at 20:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.