Coming from JavaScript, I needed to learn C++ recently, so I thought a great way to start is to create a simple terminal-based tic-tac-toe game. I encountered some difficulties especially with multi-dimensional arrays. I got it to work, but I don't think it was done very elegantly. I also experimented with the OOP C++ provides. Here's my code:
main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string>
#include "colors.h"
#include "term.h"
using namespace std;
void playerNames(string &p1, string &p2) {
cout << "Enter name of player 1 (O): ";
getline(cin, p1);
cout << "Enter name of player 2 (X): ";
getline(cin, p2);
}
class Game {
public:
Game(string p1, string p2) {
clear();
this->p1 = p1;
this->p2 = p2;
};
int play() {
while (1) {
if (this->moves == 9 && !won) {
printBoard();
return -1;
}
printBoard();
input();
if (won) {
printBoard();
break;
}
}
return !turn;
}
private:
string p1, p2;
bool turn = 1;
bool won = 0;
int moves = 0;
int win[8][3][2] = {{{0, 0}, {0, 1}, {0, 2}}, {{1, 0}, {1, 1}, {1, 2}},
{{2, 0}, {2, 1}, {2, 2}}, {{0, 0}, {1, 0}, {2, 0}},
{{0, 1}, {1, 1}, {2, 1}}, {{0, 2}, {1, 2}, {2, 2}},
{{0, 0}, {1, 1}, {2, 2}}, {{0, 2}, {1, 1}, {2, 0}}};
char board[3][3] = {{' ', ' ', ' '}, {' ', ' ', ' '}, {' ', ' ', ' '}};
void printBoard(int r = 1, int k = 1) {
auto printInstructions = [&]() {
if (turn == 1) {
cout << p1 << "'s (O) turn";
} else {
cout << p2 << "'s (X) turn";
}
cout << endl
<< "Use the arrow keys to move, press enter to confirm your choice";
};
clear();
string nl[3][3];
for (int u = 0; u < 3; ++u) {
nl[u][0] = " " + string{board[u][0]} + " ";
nl[u][1] = " " + string{board[u][1]} + " ";
nl[u][2] = " " + string{board[u][2]} + " ";
}
nl[r][k] = BRE + nl[r][k] + RST;
cout << nl[0][0] << "|" << nl[1][0] << "|" << nl[2][0] << endl;
cout << "-----------" << endl;
cout << nl[0][1] << "|" << nl[1][1] << "|" << nl[2][1] << endl;
cout << "-----------" << endl;
cout << nl[0][2] << "|" << nl[1][2] << "|" << nl[2][2] << endl;
cout << endl << endl;
printInstructions();
cout << endl << endl;
};
bool testWin() {
const int winSize = 8;
char player = turn ? 'O' : 'X';
for (int i = 0; i < winSize; i++) {
bool cond = (board[win[i][0][0]][win[i][0][1]] == player) &&
(board[win[i][1][0]][win[i][1][1]] == player) &&
(board[win[i][2][0]][win[i][2][1]] == player);
if (cond) return true;
}
return false;
};
void input() {
int pos[2] = {1, 1};
char c;
while (1) {
c = arrow();
if (c == '\n') {
if (board[pos[0]][pos[1]] != ' ') {
continue;
}
board[pos[0]][pos[1]] = turn ? 'O' : 'X';
break;
}
if (c == '\0') continue;
switch (c) {
case 'u':
pos[1] = pos[1] == 0 ? 0 : pos[1] - 1;
break;
case 'd':
pos[1] = pos[1] == 2 ? 2 : pos[1] + 1;
break;
case 'l':
pos[0] = pos[0] == 0 ? 0 : pos[0] - 1;
break;
case 'r':
pos[0] = pos[0] == 2 ? 2 : pos[0] + 1;
break;
}
printBoard(pos[0], pos[1]);
};
if (testWin()) won = 1;
moves++;
turn = !turn;
};
};
int main() {
clear();
cout << "Welcome to TicTacTerm!" << endl;
cout << "Enter the player details below. You can exit at any time by presing "
"ESC."
<< endl
<< "Use the arrow keys to navigate, press enter to confirm." << endl;
string p1, p2;
playerNames(p1, p2);
cout << endl << endl;
Game game(p1, p2);
int winner = game.play();
if (winner == -1) {
cout << "Game over. TIE!" << endl;
} else if (winner) {
cout << "Game over. " << p1 << " [O] won!" << endl;
} else {
cout << "Game over. " << p2 << " [X] won!" << endl;
}
cout << "Press enter to exit..." << endl;
cin.ignore();
return 0;
}
term.h
#include <conio.h>
#define KEY_UP 72
#define KEY_DOWN 80
#define KEY_LEFT 75
#define KEY_RIGHT 77
#define KEY_ENTER 13
#define KEY_ESC 27
#ifdef _WIN32
void clear() { system("cls"); }
void home() { printf("\x1b[H"); }
char arrow() {
int c = _getch();
if (c == KEY_ESC) exit(0); // in case you want to exit the program...
switch (c) {
case KEY_UP:
return 'u';
case KEY_DOWN:
return 'd';
case KEY_LEFT:
return 'l';
case KEY_RIGHT:
return 'r';
case KEY_ENTER:
return '\n';
default:
return '\0';
}
}
#else
throw "Only Windows OS supported right now.";
#endif
colors.h
#ifndef _COLORS_
#define _COLORS_
#define RST "\x1B[0m"
#define KRED "\x1B[31m"
#define KGRN "\x1B[32m"
#define KYEL "\x1B[33m"
#define KBLU "\x1B[34m"
#define KMAG "\x1B[35m"
#define KCYN "\x1B[36m"
#define KWHT "\x1B[37m"
#define BYEL "\u001b[43m"
#define BRE "\u001b[48;5;240m"
#define FRED(x) KRED x RST
#define FGRN(x) KGRN x RST
#define FYEL(x) KYEL x RST
#define FBLU(x) KBLU x RST
#define FMAG(x) KMAG x RST
#define FCYN(x) KCYN x RST
#define FWHT(x) KWHT x RST
#define BOLD(x) "\x1B[1m" x RST
#define UNDL(x) "\x1B[4m" x RST
#endif
Difficulties I couldn't solve:
- I wasn't able to get the length of the multidimensional array (with
arr.size()
orarr.length()
)... it seems to throw an error. So I just hard coded the length values - I'm not very experienced with other OS's, so this is platform specific to Windows
- I didn't want to hard code the length of the multi-dimensional arrays, but I couldn't find a way to do it (trying to achieve something like
int arr[][][] = {{{...}, {...}}, {{...}, {...}}, {{...}, {...}}};
instead ofint arr[3][3][2] = {...};
) - It seems like when the user is going to input arrow keys to move, Ctrl+C didn't work in terminating the program (on PowerShell at least), so I just manually implemented an exit with Esc
I'm also not sure how "right", i.e. proper my implementation is. Any suggestions are welcome, gratefully.