Previous question (without AI): Tic-Tac-Toe optimization
This new code has a main file, a base game
class, and two derived human
and computer
classes.
I welcome any suggestions for improving my code. Also, if I'm being needlessly complex on writing the code, let me know. That is my main concern.
Here, I have the welcome screen and I present the player with options. The function setcolor()
is defined in game.h
, but outside the game
class scope, so that I can use it since I've included that header file. It gives the player 3 options: human vs human, comp vs human, and exit.
tic-tac-toeV2.0.cpp
#include "stdafx.h"
#include <iostream>
#include <string>
#include <Windows.h>
#include "game.h"
#include "human.h"
#include "computer.h"
#include <sstream>
void main_play();
int main(){
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);
main_play();
return 0;
}
void main_play(){
int choice,replay;
std::cout<<"What do You want to Do?\n 1.Play Human vs. Human(2 Player) \n 2.Computer vs. Human(1 Player)\n 3.Exit\n\n";
std::cin>>choice;
if(choice<4){
switch(choice){
case 1:
{
human two_player;
two_player.play();
break;
}
case 2:
{
computer one_player;
one_player.play();
break;
}
case 3:
{
SetColor(GREEN);
std::cout<<"Thank You for Playing :)\n\n";
SetColor(WHITE);
return;
}
}
SetColor(DARKTEAL);
std::cout<<"Do You Want to Play Another Game?\n 1.Yes \n 2.No \n";
SetColor(WHITE);
std::cin>>replay;
if(replay==1){
main_play();
}
else{
SetColor(GREEN);
std::cout<<"Thank You for Playing :)\n\n";
SetColor(WHITE);
return;
}
}
SetColor(RED);
std::cout<<"That is not a Valid Choice....\n Please Make a Valid Choice....\n\n";
SetColor(WHITE);
main_play();
}
The game
class is the base class and has functions that are necessary for both modes of play.
It has a function for inserting human player's choice on the board. It has another function for checking if there is a win and then displays the appropriate messages.
The win-display function displays the win situation with colored row, col or diagonal, and it uses the color converter and winner functions to do this.
The are_elements_equal()
function checks if all the elements in a row, col or diagonal are equal.
game.h
#ifndef __GAME_H_INCLUDE
#define __GAME_H_INCLUDE
#include "stdafx.h"
#include <iostream>
#include <string>
#include <time.h>
#include <map>
#include <Windows.h>
#include <array>
#include <sstream>
enum win_associations {R1=1,R2,R3,C1,C2,C3,D1,D2};
enum state {WIN=0,DRAW,KEEP_PLAYING};
enum Color { DARKBLUE = 1, DARKGREEN, DARKTEAL, DARKRED, DARKPINK, DARKYELLOW, GRAY, DARKGRAY, BLUE, GREEN, TEAL, RED, PINK, YELLOW, WHITE };
void SetColor(Color c);
class game
{
public:
game();
bool is_the_move_valid(int);
bool are_elements_equal(std::array<char*,3>);
state state_of_game();
void display_board();
void win_display();
void color_conv(char);
void Winner(char);
protected:
std::string player1,player2,dummy;
char board[9];
char* p;
std::array<char*,3> row1;
std::array<char*,3> row2;
std::array<char*,3> row3;
std::array<char*,3> col1;
std::array<char*,3> col2;
std::array<char*,3> col3;
std::array<char*,3> diag1;
std::array<char*,3> diag2;
bool board_full();
};
#endif
game.cpp
#include "stdafx.h"
#include "game.h"
#include <sstream>
HANDLE hCon;
void SetColor(Color c){
if(hCon == NULL)
hCon = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hCon, c);}
//constructor for the class game
game::game(){
//This fills up the board array with '-' a filler character, also it looks good :)
for(int k=0;k<9;k++){
board[k]='-';
}
//makes arrays of pointers corrsponding to rows, columns and diagonals of the board
p=board; //pointer initialization
row1[0]=p;row1[1]=p+1;row1[2]=p+2; //starting from here the pointers arrays corrspond to rows1,2,3:cols 1,2,3 nad diagonals 1 & 2 respectively
row2[0]=p+3;row2[1]=p+4;row2[2]=p+5;
row3[0]=p+6;row3[1]=p+7;row3[2]=p+8;
col1[0]=p;col1[1]=p+3;col1[2]=p+6;
col2[0]=p+1;col2[1]=p+4;col2[2]=p+7;
col3[0]=p+2;col3[1]=p+5;col3[2]=p+8;
diag1[0]=p;diag1[1]=p+4;diag1[2]=p+8;
diag2[0]=p+2;diag2[1]=p+4;diag2[2]=p+6;
}
bool game::is_the_move_valid(int move){
if(board[move]=='-'){
return true;
}
if(move<9 && board[move]!='-'){
SetColor(RED);
std::cout<<"That Position is alraedy taken!!!\n\n";
SetColor(WHITE);
}
if(move>9){
SetColor(RED);
std::cout<<"That is an illegal choice!!!\n\n";
SetColor(WHITE);
}
return false;
}
bool game::are_elements_equal(std::array<char*,3> k){
std::array<char*,3>::iterator p;
p=k.begin();
if(**p==**(p+1) && **(p+1)==**(p+2)){
return true;
}
else {
return false;
}
}
state game::state_of_game(){
if( (are_elements_equal(row1)==true && *row1[0]!='-')
||(are_elements_equal(row2)==true && *row2[0]!='-')
||(are_elements_equal(row3)==true && *row3[0]!='-')
||(are_elements_equal(col1)==true && *col1[0]!='-')
||(are_elements_equal(col2)==true && *col2[0]!='-')
||(are_elements_equal(col3)==true && *col3[0]!='-')
||(are_elements_equal(diag1)==true && *diag1[0]!='-')
||(are_elements_equal(diag2)==true && *diag2[0]!='-')){
return WIN;
}
else if(board_full()==true){
return DRAW;
}
else{
return KEEP_PLAYING;
}
}
bool game::board_full(){
for(int i=0;i<9;i++){
if(board[i]=='-'){
return false;
}
}
return true;
}
void game::display_board(){
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";
}
void game::win_display(){
if(are_elements_equal(row1)==true){
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(*row1[0]);
}
else if(are_elements_equal(row2)==true){
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(*row2[0]);
}
else if(are_elements_equal(row3)==true){
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(*row3[0]);
}
else if(are_elements_equal(col1)==true){
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(*col1[0]);
}
else if(are_elements_equal(col2)==true){
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(*col2[0]);
}
else if(are_elements_equal(col3)==true){
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(*col3[0]);
}
else if(are_elements_equal(diag1)==true){
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(*diag1[0]);
}
else if(are_elements_equal(diag2)==true){
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(*diag2[0]);
}
}
void game::color_conv(char c){
SetColor(TEAL);
std::cout<<c;
SetColor(WHITE);
}
void game::Winner(char c){
if(c=='O'){
SetColor(YELLOW);
std::cout<<player1<<" Won....\n";
SetColor(WHITE);
}
else if(c=='X'){
SetColor(YELLOW);
std::cout<<player2<<" Won....\n";
SetColor(WHITE);
}
}
The human
class has one function, play()
, which makes sure the players play in a turn-by-turn fashion.
This is implemented by odd/even checks of the integer i
. If a player makes an invalid move, a warning is given and the player gets another chance.
human.h
#ifndef __HUMAN_H_INCLUDE
#define __HUMAN_H_INCLUDE
#include "game.h"
#include <sstream>
class human :
public game
{
public:
void play();
};
#endif
human.cpp
#include "stdafx.h"
#include "human.h"
#include <sstream>
void human::play(){
int i=1,player_choice;
char mark;
std::string current_palyer;
SetColor(DARKYELLOW);
std::cout<<"Player 1:Enter your Name\n";
getline(std::cin, dummy);// weird problem with getline if not for this line it won't accept player1's name
getline(std::cin, player1);
std::cout<<"Player 2:Enter your Name\n";
getline(std::cin, player2);
SetColor(WHITE);
while(state_of_game()==KEEP_PLAYING){
if(i%2!=0){
current_palyer=player1;
mark='O';
}
else if(i%2==0){
current_palyer=player2;
mark='X';
}
std::cout<<current_palyer<<":Enter your Choice....\n";
std::cin>>player_choice;
if(is_the_move_valid(player_choice-1)==true){
board[player_choice-1]=mark;
display_board();
}
else {
i--;
}
i++;
}
if(state_of_game()==DRAW){
SetColor(PINK);
std::cout<<"The Game is a Draw!!!\n";
SetColor(WHITE);
}
else if(state_of_game()==WIN){
win_display();
}
}
This class implements AI for the computer player and the AI rules. As described by @shade:
- if there is a possibility of a win, take it
- if there is a block, take it
- if the center is available, take it
- if any of the the corners are available, take it randomly
- if any of the middle edges (i.e positions 1,3,5,7) are available, take it randomly
computer.h
#ifndef __COMPUTER_H_INCLUDE
#define __COMPUTER_H_INCLUDE
#include "game.h"
#include <vector>
#include <time.h>
enum specifier {CORNERS=0, MIDDLE_CORNERS};
enum status {WINS,BLOCK,NONE};
class computer :
public game
{
protected:
struct win_block_info{
bool wins;
bool blocks;
int place;
};
struct corner_info{
bool corners;
bool mid_corners;
std::vector<char*> empty_pos;
};
std::vector<std::array<char*,3>> list;
std::vector<status> status_list;
std::array<char*,4> corners;
std::array<char*,4> middle_corners;
public:
computer();
void play();
void comp_choice();
win_block_info possible_wins_or_blocks();
corner_info corner_placement();
bool any_two_elements_equal(std::array<char*,3>);
void test();
std::vector<char*> empty_places_list(specifier);
~computer();
};
#endif
computer.cpp
#include "stdafx.h"
#include "computer.h"
computer::computer(){
srand((time(NULL)));
list.push_back(row1);
list.push_back(row2);
list.push_back(row3);
list.push_back(col1);
list.push_back(col2);
list.push_back(col3);
list.push_back(diag1);
list.push_back(diag2);
corners[0]=p;corners[1]=p+2;corners[2]=p+6;corners[3]=p+8;
middle_corners[0]=p+1;middle_corners[1]=p+3;middle_corners[2]=p+5;middle_corners[3]=p+7;
}
void computer::play(){
int i=1,player_choice;
char mark;
SetColor(DARKYELLOW);
std::cout<<"Player: Enter your Name\n";
getline(std::cin, dummy);// weird problem with getline if not for this line it won't accept player1's name
getline(std::cin, player1);
player2="Computer";
SetColor(WHITE);
while(state_of_game()==KEEP_PLAYING){
if(i%2!=0){
mark='O';
std::cout<<player1<<":Enter your Choice....\n";
std::cin>>player_choice;
if(is_the_move_valid(player_choice-1)==true){
board[player_choice-1]=mark;
display_board();
}
else {
i--;
}
}
else if(i%2==0){
std::cout<<player2<<":has made the Choice....\n";
comp_choice();
display_board();
}
i++;
}
if(state_of_game()==DRAW){
SetColor(PINK);
std::cout<<"The Game is a Draw!!!\n";
SetColor(WHITE);
}
else if(state_of_game()==WIN){
win_display();
}
}
void computer::comp_choice(){
win_block_info status;
corner_info corner_status;
status=possible_wins_or_blocks();
if(status.wins==true){
for(int k=0;k<3;k++){
if(*list[status.place][k]=='-'){
*list[status.place][k]='X';
list.erase(list.begin()+status.place);
return;
}
}
}
if(status.blocks==true){
for(int k=0;k<3;k++){
if(*list[status.place][k]=='-'){
*list[status.place][k]='X';
list.erase(list.begin()+status.place);
return;
}
}
}
if(board[4]=='-'){
board[4]='X';
return;
}
corner_status=corner_placement();
if(corner_status.corners==true){
int ran=std::rand()%corner_status.empty_pos.size();
*corner_status.empty_pos[ran]='X';
corner_status.empty_pos.clear();
return;
}
if(corner_status.mid_corners==true){
int ran=std::rand() % corner_status.empty_pos.size();
*corner_status.empty_pos[ran]='X';
corner_status.empty_pos.clear();
return;
}
}
computer::win_block_info computer::possible_wins_or_blocks(){
win_block_info status;
status.blocks=false;
status.wins=false;
status.place=10000;
for(int i=0;i<list.size();i++){
if(any_two_elements_equal(list[i])==true){
status.blocks=true;
status.place=i;
if(*list[i][0]=='X' || *list[i][1]=='X'){
status.wins=true;
status.blocks=false;
status.place=i;
return status;
}
}
}
return status;
}
bool computer::any_two_elements_equal(std::array<char*,3> current){
if( (*current[0]==*current[1] && *current[0]!='-')
||(*current[1]==*current[2] && *current[1]!='-')
||(*current[2]==*current[0] && *current[2]!='-')){
if(*current[0]=='-'||*current[1]=='-'||*current[2]=='-'){
return true;
}
}
return false;
}
computer::corner_info computer::corner_placement(){
corner_info status;
status.corners=false;
status.mid_corners=false;
for(int i=0;i<4;i++){
if(*corners[i]=='-'){
status.corners=true;
status.empty_pos.push_back(corners[i]);
}
}
if(status.corners==true){
return status;
}
for(int i=0;i<4;i++){
if(*middle_corners[i]=='-'){
status.mid_corners=true;
status.empty_pos.push_back(corners[i]);
}
}
return status;
}
void computer::test(){
std::cout<<possible_wins_or_blocks().wins<<" "<<possible_wins_or_blocks().place<<std::endl;
comp_choice();
display_board();
}
computer::~computer(){
list.clear();
}