Last active
March 10, 2021 12:16
-
-
Save kernzerfall/456fe9fedd77ab468547a60b4218c13c to your computer and use it in GitHub Desktop.
TicTacToe over a Network
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include<cstdlib> | |
#include<iostream> | |
#include<vector> | |
#include<array> | |
#include<limits> | |
#include<memory> | |
#include<ios> | |
#include<signal.h> | |
#include<cstring> | |
#include<sstream> | |
#define __UNIX (defined(__unix__) && defined(__GNUC__)) | |
// Networking stuff n shit | |
#if __UNIX | |
#include<sys/socket.h> | |
#include<netinet/in.h> | |
#include<arpa/inet.h> | |
#else | |
//compile with mingw "-lws2_32" | |
#include <winsock2.h> | |
#include <ws2tcpip.h> | |
#include <stdio.h> | |
#pragma comment(lib, "Ws2_32.lib") | |
#endif | |
// Sleep stuff | |
#if __UNIX | |
#include<unistd.h> | |
#define SLEEP(x) usleep(x*1e+6) | |
#else | |
#include<windows.h> | |
#define SLEEP(x) Sleep(x*1e+3) | |
#endif | |
#define INFO "[+] " | |
#define ERR "[!] " | |
#define POBJ "Player Object:: " | |
#define BORD "Board Object:: " | |
#define POBJ_ENINIT ERR POBJ "Tried to retrieve a property of an Non-initialized Player object" | |
#define POBJ_EMAXPT ERR POBJ "Max score reached" | |
#define ARR_OOB "Array Index Out-of-Bounds" | |
#define BORD_CLASET ERR BORD "Cell is already set" | |
// Commonly used datatypes | |
using byte = uint8_t; | |
using u64 = uint64_t; | |
using u32 = uint32_t; | |
using s32 = int32_t; | |
using u16 = int16_t; | |
using Location = std::array<u64, 2>; | |
namespace TicTacToe { | |
class Player { | |
private: | |
u64 score; | |
byte signum = 0x00; | |
public: | |
auto getSignum() -> byte { | |
if(!signum) | |
throw new std::runtime_error(POBJ_ENINIT); | |
return signum; | |
} | |
Player(byte signum) { | |
this->signum = signum; | |
#ifdef __DBG | |
std::cerr<<INFO POBJ<<"<"<<signum<<"> created\n"; | |
#endif | |
} | |
~Player(){ | |
signum = 0x00; | |
#ifdef __DBG | |
std::cerr<<INFO POBJ<<"<"<<signum<<"> destroyed\n"; | |
#endif | |
} | |
auto getScore() -> u64 { | |
if(!signum) | |
throw new std::runtime_error(POBJ_ENINIT); | |
return this-> score; | |
} | |
auto incScore() -> u64 { | |
if(!signum) | |
throw new std::runtime_error(POBJ_ENINIT); | |
u64 m = 0; | |
m-=2; | |
if(this->score > m) | |
std::runtime_error(POBJ_EMAXPT); | |
return ++(this->score); | |
} | |
void claimCell(byte x, byte y); | |
auto operator++() -> u64 { | |
return incScore(); | |
} | |
auto friend operator<(Player p1, Player p2) -> bool { | |
return p1.getScore() < p2.getScore(); | |
} | |
auto friend operator<(Player p1, u64 value) -> bool { | |
return p1.getScore() < value; | |
} | |
auto friend operator>(Player p1, Player p2) -> bool { | |
return p1.getScore() > p2.getScore(); | |
} | |
auto friend operator>(Player p1, u64 value) -> bool { | |
return p1.getScore() > value; | |
} | |
auto friend operator<<(std::ostream& stream, Player p1) -> std::ostream& { | |
stream<<p1.getScore(); | |
return stream; | |
} | |
}; | |
class Board { | |
private: | |
const char fmtString[122] = "-|---|---|---|-\n" | |
" | %c | %c | %c | \n" | |
"-|---|---|---|-\n" | |
" | %c | %c | %c | \n" | |
"-|---|---|---|-\n" | |
" | %c | %c | %c | \n" | |
"-|---|---|---|-\n"; | |
static const byte Size = 3; | |
static const byte NPlayers = 2; | |
std::array<std::array<byte,Size>,Size> matrix; | |
std::vector<Player*> Players; | |
public: | |
Board(Player* p1, Player* p2){ | |
Players.push_back(p1); | |
Players.push_back(p2); | |
for(byte x = 0; x < Board::Size; ++x) | |
for(byte y = 0; y < Board::Size; ++y) | |
this->matrix[x][y] = 0x20; | |
#ifdef __DBG | |
std::cerr<<INFO BORD<<"constructed\n"; | |
} | |
~Board(){ | |
std::cerr<<INFO BORD<<"destructed\n"; | |
#endif | |
} | |
auto isFull() -> bool { | |
for(std::array<byte, Board::Size> row: matrix) | |
for(byte cell: row) | |
if(cell == 0x20){ | |
return false; | |
} | |
return true; | |
} | |
// Works only with Size == 3 for now; maybe fix this? idk... | |
byte wins() { | |
// Check Rows | |
for(std::array<byte, Board::Size> row: matrix) if( | |
row[0] != 0x20 | |
&& row[0] == row[1] | |
&& row[1] == row[2] | |
) return row[0]; | |
// Check Columns | |
for(u64 i = 0; i < Board::Size; ++i) if( | |
matrix[0][i] != 0x20 | |
&& matrix[0][i] == matrix[1][i] | |
&& matrix[1][i] == matrix[2][i] | |
) return matrix[0][i]; | |
// Check Diagonally /* | |
if( matrix[0][2] != 0x20 | |
&& matrix[0][2] == matrix[1][1] | |
&& matrix[1][1] == matrix[2][0] | |
) return matrix[0][2]; | |
// Check Diagonally \* | |
if( matrix[0][0] != 0x20 | |
&& matrix[0][0] == matrix[1][1] | |
&& matrix[1][1] == matrix[2][2] | |
) return matrix[0][0]; | |
// If no streak found, return 0; | |
return 0; | |
} | |
auto getCell(byte x, byte y) -> byte { | |
if(x < Board::Size && y < Board::Size) return this->matrix[x][y]; | |
else throw new std::runtime_error(ERR BORD ARR_OOB); | |
} | |
bool isFree(Location xy){ | |
byte x = xy[0]; | |
byte y = xy[1]; | |
if(x < Board::Size && y < Board::Size) | |
return this->matrix[x][y] == 0x20; | |
else | |
throw new std::runtime_error(ERR BORD ARR_OOB); | |
} | |
void setCell(Location xy, byte val) { | |
byte x = static_cast<byte>(xy[0]); | |
byte y = static_cast<byte>(xy[1]); | |
if(x < Board::Size && y < Board::Size){ | |
byte* cell = &matrix[x][y]; | |
// convert to capital | |
// no need to check if val is actually a letter at this point | |
// since we directly check if the converted value is X or O | |
if (val > 'Z') val -= 0x20; | |
if(val == 'X' || val == 'O'){ | |
if(*cell == 0x20) | |
matrix[x][y] = val; | |
else | |
throw new std::runtime_error(BORD_CLASET); | |
} | |
}else{ | |
throw new std::runtime_error(ERR BORD ARR_OOB); | |
} | |
} | |
auto operator[](byte x) -> std::array<byte, Board::Size> { | |
if(x < Board::Size) return this->matrix[x]; | |
else throw new std::runtime_error(ERR BORD ARR_OOB); | |
} | |
auto friend operator<<(std::ostream& stream, Board b) -> std::ostream& { | |
char temp[122] = {0x00}; | |
sprintf(temp, b.fmtString, | |
b[0][0], b[0][1], b[0][2], | |
b[1][0], b[1][1], b[1][2], | |
b[2][0], b[2][1], b[2][2] | |
); | |
stream<<temp; | |
return stream; | |
} | |
}; | |
auto getInputFromPlayer(byte signum) -> Location { | |
std::cout<<signum<<" > "; | |
u64 x, y; | |
std::cin>>x>>y; | |
while(!std::cin || x < 1 || x > 3 || y < 1 || y > 3){ | |
std::cin.clear(); | |
std::cin.ignore(100, '\n'); | |
std::cerr<<ERR<<"Try again!\n"; | |
std::cout<<signum<<" > "; | |
std::cin>>x>>y; | |
} | |
return { x - 1, y - 1 }; | |
} | |
void PlayAlone(){ | |
u64 move_ticker = 0; | |
Player* p1 = new Player('X'); | |
Player* p2 = new Player('O'); | |
Board* board = new Board(p1, p2); | |
Player* curr = p1; | |
std::cout<<"\n"<<*board<<"\n"; | |
while(1){ | |
try { | |
Location xy = getInputFromPlayer(curr->getSignum()); | |
while(!board->isFree(xy)){ | |
std::cerr<<ERR "taken; try again\n"; | |
xy = getInputFromPlayer(curr->getSignum()); | |
} | |
byte signum = curr->getSignum(); | |
board->setCell(xy, signum); | |
} catch (std::runtime_error* e){ | |
std::cerr<<e->what(); | |
continue; | |
} | |
byte f = board->wins(); | |
if(f){ | |
std::cout<<"\n"<< *board<<"\n"; | |
std::cout<<INFO<<f<<" won!\n"; | |
break; | |
} | |
else if(board->isFull()) { | |
std::cout<<"\n"<<*board<<"\n"; | |
std::cout<<INFO<<"It's a tie!\n"; | |
break; | |
} | |
std::cout<<"\n"<<*board<<"\n\n"; | |
curr = (curr == p1)? p2: p1; | |
move_ticker++; | |
} | |
delete board, p1, p2; | |
} | |
auto terminateBoth(s32 sock){ | |
send(sock, "\0\0\0\0\0\0\0\0", 8, 0); | |
exit(EXIT_FAILURE); | |
} | |
#if __UNIX | |
auto NetworkPlay(s32 sock, bool first){ | |
#else | |
auto NetworkPlay(SOCKET sock, bool first){ | |
#endif | |
u32 move_ticker = 0; | |
Player* p1 = new Player('X'); | |
Player* p2 = new Player('O'); | |
const Player* local = (first)? p1 : p2; | |
Board* board = new Board(p1, p2); | |
Player* curr = p1; | |
std::cout<<"\n"<<*board<<std::endl; | |
while(1){ | |
if(curr != local){ | |
std::cerr<<INFO "waiting for the other player's move\n"; | |
byte buf[8] = {0x00}; | |
#if !__UNIX | |
recv(sock, (char*)buf, 8, 0); | |
#else | |
recv(sock, buf, 8, 0); | |
#endif | |
u32 mT = 0; | |
mT += (buf[0] << 24) + (buf[1] << 16) + (buf[2] << 8) + buf[3]; | |
if(mT <= move_ticker){ | |
std::cerr <<ERR "remote sent an invalid move_ticker\n"; | |
// COMMENCE RECIPROCITY EH; | |
terminateBoth(sock); | |
} | |
Location xy = { buf[4], buf[5] } ; | |
try{ | |
board->setCell(xy, curr->getSignum()); | |
move_ticker = mT; | |
}catch (std::runtime_error* e){ | |
std::cerr<<e->what(); | |
exit(EXIT_FAILURE); | |
} | |
} | |
else { | |
Location xy = getInputFromPlayer(curr->getSignum()); | |
while(!board->isFree(xy)){ | |
std::cerr<<ERR "taken; try again\n"; | |
xy = getInputFromPlayer(curr->getSignum()); | |
} | |
try{ | |
board->setCell(xy, curr->getSignum()); | |
move_ticker++; | |
byte buf[8] = {0x00}; | |
buf[0] = (move_ticker >> 24) & 0xff; | |
buf[1] = (move_ticker >> 16) & 0xff; | |
buf[2] = (move_ticker >> 8) & 0xff; | |
buf[3] = move_ticker & 0xff; | |
buf[4] = xy[0]; | |
buf[5] = xy[1]; | |
buf[6] = 0x00; | |
buf[7] = 0x00; | |
#if __UNIX | |
send(sock, buf, 8, 0); | |
#else | |
send(sock, (char*)buf, 8, 0); | |
#endif | |
}catch(std::runtime_error* e){ | |
std::cerr<<e->what(); | |
terminateBoth(sock); | |
} | |
} | |
curr = (curr==p1)? p2 : p1; | |
byte f = board->wins(); | |
if(f){ | |
std::cout<<"\n"<< *board<<"\n"; | |
std::cout<<INFO<<f<<" won!\n"; | |
break; | |
} | |
else if(board->isFull()) { | |
std::cout<<"\n"<<*board<<"\n"; | |
std::cout<<INFO<<"It's a tie!\n"; | |
break; | |
} | |
std::cout<<"\n"<<*board<<"\n\n"; | |
} | |
delete p1, p2, board; | |
shutdown(sock, 2); | |
} | |
} | |
auto signalHandler(s32 signal){ | |
#if __UNIX | |
static u64 signalCounter = 0; | |
switch(signalCounter){ | |
case 0: | |
std::cerr<<"\nU NO LONGA WANNA PLEH?\n"; break;; | |
case 1: | |
std::cerr<<"\nYOU CAN'T ESCAPE THAT EASILY...\n"; break;; | |
case 2: | |
std::cerr<<"\nFINE"<<std::flush; | |
for(s32 i = 0; i < 3; i++){ | |
std::cerr<<"."; | |
std::cerr<<std::flush; | |
SLEEP(1); | |
} | |
exit(signal); | |
} | |
signalCounter++; | |
#else | |
for(char c: "U NO LONGA WANNA PLEH...\nFINE..."){ | |
SLEEP(1e-1); | |
std::cerr<<c; | |
} | |
std::cerr<<std::endl; | |
exit(signal); | |
#endif | |
} | |
void badArg(const char* err, const char* ppath){ | |
std::cerr<<ERR<<err<<"\nUsage: "<<ppath<<" s IP_ADDRESS PORT ->server mode\n"; | |
size_t a0 = strlen(ppath); | |
for(s32 i = 0; i < a0 + 8; ++i) std::cerr<<" "; | |
std::cerr<<"c IP_ADDRESS PORT ->client mode\n"; | |
for(s32 i = 0; i < a0 + 8; ++i) std::cerr<<" "; | |
std::cerr<<"l ->local mode\n"; | |
exit(EXIT_FAILURE); | |
} | |
#if __UNIX | |
auto getServerSocket(const char* addr, u16 port) -> s32 { | |
std::cout<<INFO "server mode selected\n"; | |
s32 server_fd, sock, readValue; | |
struct sockaddr_in address; | |
s32 opt = 1; | |
s32 addrlen = sizeof(address); | |
if((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0){ | |
std::cerr<<ERR<<"socket failed\n"; | |
exit(EXIT_FAILURE); | |
} else { | |
std::cerr<<INFO "socket created successfully\n"; | |
} | |
if(setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))){ | |
std::cerr<<"setsockopt failed\n"; | |
exit(EXIT_FAILURE); | |
} else { | |
std::cerr<<INFO "socket options set successfully\n"; | |
} | |
address.sin_family = AF_INET; | |
address.sin_port = htons(port); | |
if(inet_pton(AF_INET, addr, &address.sin_addr) <= 0){ | |
std::cerr<<ERR "invalid/unsupported address\n"; | |
exit(EXIT_FAILURE); | |
} | |
if(bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0){ | |
std::cerr<<ERR "bind failed\n"; | |
exit(EXIT_FAILURE); | |
} else { | |
std::cerr<<INFO "socket bound successfully\n"; | |
} | |
if(listen(server_fd, 3) < 0){ | |
std::cerr<<ERR "listen failed\n"; | |
exit(EXIT_FAILURE); | |
} else { | |
std::cerr<<INFO "listening on port "<<port<<"\n"; | |
} | |
if((sock = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0){ | |
std::cerr<<ERR "accept failed\n"; | |
exit(EXIT_FAILURE); | |
} else { | |
std::cerr<<INFO "inbound connection from "<<inet_ntoa(address.sin_addr)<<":"<<address.sin_port<<"\n"; | |
} | |
return sock; | |
} | |
auto getClientSocket(const char* addr, u16 port){ | |
std::cout<<INFO "client mode selected\n"; | |
s32 server_fd, sock, readValue; | |
struct sockaddr_in address; | |
s32 opt = 1; | |
s32 addrlen = sizeof(address); | |
if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0){ | |
std::cerr<<ERR<<"socket failed\n"; | |
exit(EXIT_FAILURE); | |
} else { | |
std::cerr<<INFO "socket created successfully\n"; | |
} | |
address.sin_family = AF_INET; | |
address.sin_port = htons(port); | |
if(inet_pton(AF_INET, addr, &address.sin_addr) <= 0){ | |
std::cerr<<ERR "invalid/unsupported address\n"; | |
exit(EXIT_FAILURE); | |
} | |
if(connect(sock, (struct sockaddr*)&address, sizeof(address)) < 0){ | |
std::cerr<<ERR "connection failed\n"; | |
exit(EXIT_FAILURE); | |
} else { | |
std::cerr<<INFO "connected successfully to "<<addr<<":"<<port<<"\n"; | |
} | |
return sock; | |
} | |
#else | |
auto getServerSocket(PCSTR addr, PCSTR port) -> SOCKET { | |
WSADATA wsaData; | |
auto iResult = WSAStartup(MAKEWORD(2,2), &wsaData); | |
if(iResult != 0){ | |
std::cerr<<ERR "WSAStartup failed\n"; | |
exit(EXIT_FAILURE); | |
} | |
struct addrinfo *result = nullptr, *prt = nullptr, hints; | |
ZeroMemory(&hints, sizeof(hints)); | |
hints.ai_family = AF_INET; | |
hints.ai_socktype = SOCK_STREAM; | |
hints.ai_protocol = IPPROTO_TCP; | |
iResult = getaddrinfo(addr, port, &hints, &result); | |
if(iResult != 0){ | |
std::cerr<<ERR "getaddrinfo failed\n"; | |
exit(EXIT_FAILURE); | |
} | |
SOCKET server_fd = INVALID_SOCKET; | |
if((server_fd = socket(result->ai_family, result->ai_socktype, result->ai_protocol)) == INVALID_SOCKET){ | |
std::cerr<<ERR "socket failed\n"; | |
exit(EXIT_FAILURE); | |
}else{ | |
std::cerr<<INFO "socket created successfully\n"; | |
} | |
if(bind(server_fd, result->ai_addr, (int)result->ai_addrlen) == SOCKET_ERROR){ | |
std::cerr<<ERR "socket couldn't be bound\n"; | |
exit(EXIT_FAILURE); | |
}else{ | |
std::cerr<<INFO "socket bound successfully to port "<<port<<"\n"; | |
} | |
freeaddrinfo(result); | |
if(listen(server_fd, SOMAXCONN) == SOCKET_ERROR){ | |
std::cerr<<ERR "listen failed\n"; | |
exit(EXIT_FAILURE); | |
}else{ | |
std::cerr<<INFO "listening on port "<<port<<"\n"; | |
} | |
SOCKET sock = INVALID_SOCKET; | |
struct sockaddr_in client = {0}; | |
socklen_t socklen = sizeof(client); | |
if((sock = accept(server_fd, (struct sockaddr*)&client, &socklen)) == INVALID_SOCKET){ | |
std::cerr<<ERR "accept failed\n"; | |
exit(EXIT_FAILURE); | |
}else{ | |
std::cerr<<INFO "inbound connection from "<<inet_ntoa(client.sin_addr)<<":"<<client.sin_port<<"\n"; | |
} | |
return sock; | |
} | |
auto getClientSocket(PCSTR addr, PCSTR port) -> SOCKET { | |
WSADATA wsaData; | |
auto iResult = WSAStartup(MAKEWORD(2,2), &wsaData); | |
if(iResult != 0){ | |
std::cerr<<ERR "WSAStartup failed\n"; | |
exit(EXIT_FAILURE); | |
} | |
struct addrinfo *result = nullptr, *ptr = nullptr, hints; | |
ZeroMemory(&hints, sizeof(hints)); | |
hints.ai_family = AF_INET; | |
hints.ai_socktype = SOCK_STREAM; | |
hints.ai_protocol = IPPROTO_TCP; | |
iResult = getaddrinfo(addr, port, &hints, &result); | |
if(iResult != 0){ | |
std::cerr<<ERR "getaddrinfo failed\n"; | |
exit(EXIT_FAILURE); | |
} | |
SOCKET sock = INVALID_SOCKET; | |
sock = socket(result->ai_family, result->ai_socktype, result->ai_protocol); | |
if(sock == INVALID_SOCKET){ | |
std::cerr<<ERR "socket failed\n"; | |
exit(EXIT_FAILURE); | |
}else{ | |
std::cerr<<INFO "socket created successfully\n"; | |
} | |
iResult = connect(sock, result->ai_addr, (int)result->ai_addrlen); | |
if(iResult == SOCKET_ERROR){ | |
std::cerr<<ERR "connection error\n"; | |
exit(EXIT_FAILURE); | |
}else{ | |
std::cerr<<INFO "connected successfully to "<<addr<<":"<<port<<"\n"; | |
} | |
return sock; | |
} | |
#endif | |
auto main(s32 argc, char** argv, char** env) -> s32{ | |
signal(SIGINT, signalHandler); | |
if(argc > 4 || argc < 2){ | |
badArg("Incorrect arg count", argv[0]); | |
} | |
switch(argv[1][0]){ | |
case 'c': | |
case 'C':{ | |
if(argc != 4){ | |
badArg("Incorrect arg count", argv[0]); | |
} | |
std::istringstream parsePort(argv[3]); | |
u16 port; | |
if(parsePort>>port){ | |
#if __UNIX | |
s32 sock = getClientSocket(argv[2], port); | |
#else | |
SOCKET sock = getClientSocket(argv[2], argv[3]); | |
#endif | |
TicTacToe::NetworkPlay(sock, false); | |
}else{ | |
std::cerr<<ERR "invalid port number (must be 0-65535)\n"; | |
exit(EXIT_FAILURE); | |
} | |
} break;; | |
case 's': | |
case 'S':{ | |
if(argc != 4){ | |
badArg("Incorrect arg count", argv[0]); | |
} | |
std::istringstream parsePort(argv[3]); | |
u16 port; | |
if(parsePort>>port){ | |
#if __UNIX | |
s32 sock = getServerSocket(argv[2], port); | |
#else | |
SOCKET sock = getServerSocket(argv[2], argv[3]); | |
#endif | |
TicTacToe::NetworkPlay(sock, true); | |
}else{ | |
std::cerr<<ERR "invalid port number (must be 0-65535)\n"; | |
exit(EXIT_FAILURE); | |
} | |
} break;; | |
case 'l': | |
case 'L':{ | |
TicTacToe::PlayAlone(); | |
} break;; | |
default: badArg("Bad argument", argv[0]); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment