This is the first little project I've made that didn't feel it was complete gibberish. But I couldn't tell.
The biggest problem I had with this was using the BoardValue
enum
working like I wanted to. I understand that classes should have a level of abstraction to them and I suspect that the way I implemented the at(int)
returning a char
over a BoardValue
took away from that. However, I though having to convert the return from at(int)
to a char if it returned a BoardValue
would be redundant. For example, using a statement like this:
char print_char = Board.at(some_index) == BoardValue::o ? 'O' : 'X';
I hope I've done a decent job describing my dilemma.
Overall, I'm hoping for some overall general code style tips and pointers on how to write better code from here.
tictactoe.h
#ifndef TICTACTOE
#define TICTACTOE
#include <array>
#include <iostream>
enum BoardValue : char{ none = ' ', o = 'O', x = 'X' };
class Board
{
public:
Board()
{
for(auto begin = board.begin(),end = board.end();begin != end; ++begin)
*begin = BoardValue::none;
}
char at(int index) const{ return board.at(index); }
inline bool check_win(BoardValue) const;
bool place(int, BoardValue);
private:
bool check_diagonals(BoardValue) const;
bool check_horizontals(BoardValue) const;
bool check_verticals(BoardValue) const;
std::array<char, 9> board{};
};
inline bool Board::check_win(BoardValue check) const
{
if(check == BoardValue::none)
throw "Trying to check_win for check == BoardValue::none!";
return check_diagonals(check) || check_horizontals(check) || check_verticals(check);
}
#endif
tictactoe.cpp
#include "tictactoe.h"
#include <iostream>
//returns false if index is occupied
bool Board::place(int index, BoardValue value)
{
if(board.at(index) != BoardValue::none)
return false;
board.at(index) = value;
return true;
}
bool Board::check_diagonals(BoardValue check) const
{
//if middle is not check no diagnols will pass
if(board.at(4) != check)
return false;
//backward diagonal '\'
if(board.at(0) == check && board.at(4) == check)
return true;
//forward diaganol '/'
if(board.at(2) == check && board.at(6) == check)
return true;
return false;
}
bool Board::check_horizontals(BoardValue check) const
{
for(int row = 0; row < 3; ++row){
if(board.at(row) == check &&
board.at(row + 3) == check &&
board.at(row + 6) == check)
return true;
}
return false;
}
bool Board::check_verticals(BoardValue check) const
{
for(int col = 0; col < 3; ++col){
if(board.at(col * 3) == check &&
board.at(col * 3 + 1) == check &&
board.at(col * 3 + 2 ) == check)
return true;
}
return false;
}
main.cpp
#include "tictactoe.h"
#include <iostream>
int ask_input(char player, bool retry = false)
{
if(!retry)
std::cout << "It's " << player
<< "'s turn. Where do you want to go(e.g. A1 B3 C2)? ";
else
std::cout << "No, no, no " << player
<< "! Input a letter followed bt a number: ";
std::string input;
std::cin >> input;
if(input.size() < 2)
return ask_input(player, true);
int col_input{};
switch(*input.begin())
{
case 'A':
case 'a':
col_input = 0;
break;
case 'B':
case 'b':
col_input = 1;
break;
case 'C':
case 'c':
col_input = 2;
break;
default:
return ask_input(player, true);
}
int row_input = *(input.begin() + 1) - '0'; //convers char '1' to int 1
--row_input;
return col_input * 3 + row_input;
}
BoardValue ask_turn() //ask whos first if return true O goes first
{
BoardValue turn;
std::string input;
std::cout << "Who goes first(X or O)? ";
for(bool valid_input{false}; !valid_input;)
{
std::cin >> input;
switch(input.front()) //input cannot be null at this point
{
case 'x':
case 'X':
valid_input = true;
turn = BoardValue::x;
break;
case '0':
case 'o':
case 'O':
valid_input = true;
turn = BoardValue::x;
break;
default:
std::cout << "Invalid input! Try X or O :";
}
}
return turn;
}
std::ostream &print_board(std::ostream &os,const Board &board)
{
os << " |A|B|C\n";
for(int row = 0; row < 3; ++row)
{
os << std::string( 8, '-') << '\n';
os << row + 1 << '|';
for(int col = 0; col < 3; ++col)
{
char follow_char{ col == 2 ? '\n' : '|' };
os << board.at(col * 3 + row) << follow_char;
}
}
os << std::endl;
return os;
}
int main(){
Board board{};
BoardValue turn{ ask_turn() };
//turn will be set back to appropriate value at start of game loop
turn = turn == BoardValue::o ? BoardValue::x : BoardValue::o;
int turn_count{0};
while(board.check_win(turn) == false)
{
turn = turn == BoardValue::o ? BoardValue::x : BoardValue::o;
print_board(std::cout, board);
bool input_valid{false};
while(input_valid == false)
{
int input;
input = ask_input(turn);
input_valid = board.place(input, turn);
if( input_valid == false )
std::cout << "That place is take! Try again..\n";
}
if(++turn_count == 9) //max amount of turns game is tie
break;
}
print_board(std::cout, board);
if(turn_count == 9)//game is tie
std::cout << "Looks like its a tie...\n";
else
std::cout << (char)turn << " wins!\n";
}