Skip to content

Instantly share code, notes, and snippets.

@kernzerfall
Last active March 10, 2021 12:16
Show Gist options
  • Save kernzerfall/456fe9fedd77ab468547a60b4218c13c to your computer and use it in GitHub Desktop.
Save kernzerfall/456fe9fedd77ab468547a60b4218c13c to your computer and use it in GitHub Desktop.
TicTacToe over a Network
#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