diff --git a/ttt_interface/HttpConnect.cpp b/ttt_interface/HttpConnect.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c23b7b850e114f25e1de3b76298399f885d3ca8c --- /dev/null +++ b/ttt_interface/HttpConnect.cpp @@ -0,0 +1,70 @@ +// +// Created by Ari on 10/1/24. +// + +#include "HttpConnect.h" + +#include +#include +#include +#include +#include +#include +#include +using namespace std; + + string HttpConnect::makeRequest(string host, string path, string port) { + // Step 1: Create a socket + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + throw new HttpConnect::ConnectionError("Failed to create socket"); + } + + // Step 2: Resolve hostname to an IP address + struct addrinfo hints, *res; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; // IPv4 + hints.ai_socktype = SOCK_STREAM; // TCP + + if (getaddrinfo(host.c_str(), port.c_str(), &hints, &res) != 0) { + close(sock); + throw new HttpConnect::ConnectionError("Failed to resolve hostname"); + } + + // Step 3: Connect to the server + if (connect(sock, res->ai_addr, res->ai_addrlen) != 0) { + close(sock); + throw new HttpConnect::ConnectionError("Failed to connect to server"); + } + + freeaddrinfo(res); // Free memory that is no longer needed after connecting + + // Step 4: Send HTTP GET request + std::string request = "GET " + path + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; + + if (send(sock, request.c_str(), request.size(), 0) == -1) { + close(sock); + throw new HttpConnect::ConnectionError("Failed to send request"); + } + + // Step 5: Receive and print the response + const int buffer_size = 4096; + char buffer[buffer_size]; + std::string response; + + ssize_t bytesReceived; + while ((bytesReceived = recv(sock, buffer, buffer_size - 1, 0)) > 0) { + buffer[bytesReceived] = '\0'; // Null-terminate the buffer + response.append(buffer); + } + + if (bytesReceived < 0) { + close(sock); + throw new HttpConnect::ConnectionError("Error receiving response"); + } else { + close(sock); + return response; + } + } \ No newline at end of file diff --git a/ttt_interface/HttpConnect.h b/ttt_interface/HttpConnect.h new file mode 100644 index 0000000000000000000000000000000000000000..0f58ba70ab76d9666d6fc5515cb6cf6141c3dfb8 --- /dev/null +++ b/ttt_interface/HttpConnect.h @@ -0,0 +1,30 @@ +// +// Created by Ari Trachtenberg on 10/1/24, based on ChatGPT. +// +#include +using namespace std; + +#ifndef HW3_STAFF_HTTPCONNECT_H +#define HW3_STAFF_HTTPCONNECT_H + +class HttpConnect { + public: + // METHODS +/** + * Makes a request from https://[host]:[port]/[path] and returns the response. + * @param host The host to which to connect. + * @param port The port on that host to which to connect. + * @param path The path to request on that host. + * @return The response to the query. + * @throws ConnectionError upon an error. + */ + static string makeRequest(string host, string path, string port = "80"); + + // SUBCLASSES + class ConnectionError:public runtime_error { + public: + ConnectionError(string err): runtime_error(err) {} + }; +}; + +#endif //HW3_STAFF_HTTPCONNECT_H diff --git a/ttt_interface/ttt.cpp b/ttt_interface/ttt.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c29075a76f8ae22ca70ca50cb7c0bbcea4d8dcf7 --- /dev/null +++ b/ttt_interface/ttt.cpp @@ -0,0 +1,216 @@ +// +// Created by Ari on 10/1/24. +// + +#include +#include +#include +#include +#include +#include "HttpConnect.h" +#include "ttt.h" + +// HELPERS - mostly by ChatGPT +// trim whitespace +std::string trim(const std::string& str) { + size_t start = str.find_first_not_of(" \t\n\r\f\v"); + size_t end = str.find_last_not_of(" \t\n\r\f\v"); + return (start == std::string::npos || end == std::string::npos) ? "" : str.substr(start, end - start + 1); +} + +// Function to remove surrounding quotes from a string +std::string removeQuotes(const std::string& str) { + if (str.front() == '"' && str.back() == '"') { + return str.substr(1, str.size() - 2); + } + return str; +} + +// Basic JSON parser for flat key-value pairs +map parseJson(const std::string& jsonString) { + map result; + string trimmedJson = trim(jsonString); + + // Remove the surrounding curly braces + if (trimmedJson.front() == '{' && trimmedJson.back() == '}') { + trimmedJson = trimmedJson.substr(1, trimmedJson.size() - 2); + } else { + throw runtime_error("Cannot decode JSON: "+jsonString); + } + + stringstream ss(trimmedJson); + string item; + + while (getline(ss, item, ',')) { + size_t colonPos = item.find(':'); + if (colonPos == std::string::npos) { + // ignore invalide key-value pair + // std::cerr << "Invalid key-value pair." << std::endl; + continue; + } + + std::string key = trim(item.substr(0, colonPos)); + std::string value = trim(item.substr(colonPos + 1)); + + key = removeQuotes(key); // Remove surrounding quotes from key + value = removeQuotes(value); // Remove surrounding quotes from value if it's a string + + result[key] = value; + } + + return result; +} + +// parses an HTTP response and returns the body of the response +string parseHttpResponse(const string &httpResponse) { + std::istringstream responseStream(httpResponse); + std::string line; + + // 1. Parse the Status Line + std::string httpVersion; + int statusCode; + std::string statusMessage; + + // Get the first line (status line) + std::getline(responseStream, line); + istringstream statusLineStream(line); + statusLineStream >> httpVersion >> statusCode; + std::getline(statusLineStream, statusMessage); + +// std::cout << "HTTP Version: " << httpVersion << std::endl; +// std::cout << "Status Code: " << statusCode << std::endl; +// std::cout << "Status Message: " << statusMessage << std::endl; + + // 2. Parse Header +// map headers; + while (std::getline(responseStream, line) && line != "\r") { +// if (line.find(":") != std::string::npos) { +// std::string headerName = line.substr(0, line.find(":")); +// std::string headerValue = line.substr(line.find(":") + 2); // Skip the ": " characters +// headers[headerName] = headerValue; +// } + } + +// std::cout << "\nHeaders:\n"; +// for (const auto& header : headers) { +// std::cout << header.first << ": " << header.second << std::endl; +// } + + // 3. Parse Body (if any) + string body; + getline(responseStream, body, '\0'); // Read the rest of the content as the body + return body; +} + +// Courtesy of ChatGPT +string url_encode(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + + for (char c: value) { + // Alphanumeric characters don't need to be escaped + if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + escaped << c; + } else { + // Escape any other character + escaped << '%' << std::setw(2) << (int) (unsigned char) c; + } + } + + return escaped.str(); +} + +/** + * Run a script on the server + * @param script The script to request. + * @return The Http response. + */ +string requestTTT(string script) { + // cout << "Trying " +script << endl; + string answer = + parseHttpResponse(HttpConnect::makeRequest("agile.bu.edu", "/backend/web/" + script, "8013")); + //parseHttpResponse(HttpConnect::makeRequest("localhost", "/" + script, "8080")); + return answer; +} + +int strToInt(string theStr) { + try { + return stoi(theStr); + } + catch (invalid_argument e) { return -1; } + catch (out_of_range e) { return -1; } +} + + +// IMPLEMENTATIONS + +PLAYER_CODE newPlayer(string name) { + return requestTTT("newPlayer.pl"); +} + +string getName(PLAYER_CODE thePlayerCode) { + return requestTTT("lookupCode.pl?playerCode=" + thePlayerCode); +} + +string getPlayerStatus(PLAYER_CODE thePlayerCode) { + return requestTTT("playerStatus.pl?playerCode=" + thePlayerCode); +} + +string getTopRatings() { + return requestTTT("topRatings.pl"); +} + +GAME_CODE newGame(PLAYER_CODE xPlayerCode, PLAYER_CODE yPlayerCode) { + string gameCode = requestTTT("newGame.pl"); + // set X and Y players + requestTTT("changeGameStatus.pl?gameCode=" + gameCode + + "&setX=" + xPlayerCode + + "&setY=" + yPlayerCode); + return gameCode; +} + +SQUARE getWhoToPlay(GAME_CODE theGameCode) { + return requestTTT("gameSide.pl")[0]; +} + +string getModTime(GAME_CODE theGameCode) { + return requestTTT("getModeType.pl?gameCode=" + theGameCode); +} + +string getGameStatus(GAME_CODE theGameCode) { + return requestTTT("gameStatus.pl?gameCode=" + theGameCode); +} + +map parseGameStatus(GAME_CODE theGameCode) { + return parseJson(requestTTT("gameStatus.pl?gameCode=" + theGameCode)); +} + +string makeMove(GAME_CODE theGameCode, PLAYER_CODE thePlayerCode, SQUARE theSide, int row, int col) { + return requestTTT("changeGameStatus.pl?gameCode=" + theGameCode + + "&" + (theSide == 'x' ? "makeMoveX" : "makeMoveY") + + "&playerCode=" + thePlayerCode + + "&row=" + to_string(row) + + "&col=" + to_string(col) + ); +} + +int getRows(GAME_CODE theGameCode) { + return strToInt(parseGameStatus(theGameCode)["rows"]); +} + +int getCols(GAME_CODE theGameCode) { + return strToInt(parseGameStatus(theGameCode)["cols"]); +} + +string getBoard(GAME_CODE theGameCode) { + return parseGameStatus(theGameCode)["board"]; +} + +SQUARE getHasWon(GAME_CODE theGameCode) { + return parseGameStatus(theGameCode)["hasWon"][0]; +} + +SQUARE getToMove(GAME_CODE theGameCode) { + return parseGameStatus(theGameCode)["toMove"][0]; +} \ No newline at end of file diff --git a/ttt_interface/ttt.h b/ttt_interface/ttt.h new file mode 100644 index 0000000000000000000000000000000000000000..c326b708feb60e03f24462e755533e8403801416 --- /dev/null +++ b/ttt_interface/ttt.h @@ -0,0 +1,129 @@ +// +// Created by Ari on 10/1/24. +// +#include +using namespace std; + +#ifndef HW3_STAFF_TTT_H +#define HW3_STAFF_TTT_H + +// various types. +using PLAYER_CODE = string; // represents the code of a player +using GAME_CODE = string; // represents the code of a game +using SQUARE = char; // 'x', 'y', '-' or '*' + +// PLAYER functions +/** + * Creates a new player with name [name]. + * @param name The name of the new player. + * @return A code for the new player. + */ +PLAYER_CODE newPlayer(string name); + +/** + * @param thePlayerCode The code for the player in question. + * @return The name associated with code [thePlayerCode]. + */ +string getName(PLAYER_CODE thePlayerCode); + +/** + * @param thePlayerCode The code for the player in question. + * @return Status information about the player with code [thePlayerCode] in JSON format. + * @see https://en.wikipedia.org/wiki/JSON - a description of JSON + * @see https://www.w3schools.com/whatis/whatis_json.asp - descriptions through examples + */ +string getPlayerStatus(PLAYER_CODE thePlayerCode); + +/** + * @return A human readable list of the top players in the system. + */ +string getTopRatings(); + + + + +// GAME FUNCTIONS +/** + * Creates a new game. + * @param xPlayerCode The code of the player playing for 'x'. + * @param yPlayerCode The code of the player playing for 'y'. + * @return a code for the new game. + */ +GAME_CODE newGame(PLAYER_CODE xPlayerCode, PLAYER_CODE yPlayerCode); + +/** + * @param theGameCode The code of the Game in question. + * @return The side that is currently set to play in the game with code [theGameCode]. + */ +SQUARE getWhoToPlay(GAME_CODE theGameCode); + +/** + * @param theGameCode The code of the Game in question. + * @return How many of the same-type square in a row are needed to win. + */ +int getInARow(GAME_CODE theGameCode); + +/** + * @param theGameCode The code of the Game in question. + * @return The date-time when the game with code [theGameCode] was last modified. + */ +string getModTime(GAME_CODE theGameCode); + +/** + * @param theGameCode The code of the Game in question. + * @return The current status of the game with code [theGameCode] in JSON format. + * @see https://en.wikipedia.org/wiki/JSON - a description of JSON + * @see https://www.w3schools.com/whatis/whatis_json.asp - descriptions through examples + */ +string getGameStatus(GAME_CODE theGameCode); + +/** + * @param theGameCode The code of the Game in question. + * @return The number of rows on the board of the current game. + */ +int getRows(GAME_CODE theGameCode); + +/** + * @param theGameCode The code of the Game in question. + * @return The number of columns on the board of the game with the given game code. + */ +int getCols(GAME_CODE theGameCode); + +/** + * @param theGameCode The code of the Game in question. + * @return The next player to move in the game with the given game code. + */ +SQUARE getToMove(GAME_CODE theGameCode); + +/** + * @param theGameCode The code of the Game in question. + * @return A tstring representing the current board in the game with the given game code. + */ +string getBoard(GAME_CODE theGameCode); + +/** + * @param theGameCode The code of the Game in question. + * @return Who has won the game, or '-' if no one has won yet. + */ +SQUARE getHasWon(GAME_CODE theGameCode); + +/** + * Makes a move for side [side] by the player with code [thePlayerCode] + * at row [row] and column [col] of the board for the game with + * code [theGameCode]. + * @param theGameCode The code of the game being played. + * @param thePlayerCode The code of the player making the move. + * @param theSize The side that is playing. + * @param row The row at which the move is made, starting at 0. + * @param col The column at which the move is made, starting at 0. + * @return a description of an error, if an error occurred (and an empty string otherwise). + * Errors include playing out of turn, playing for the wrong side, + * or playing to an occupied square, among others. + */ +string makeMove(GAME_CODE theGameCode, + PLAYER_CODE thePlayerCode, + SQUARE theSide, + int row, + int col); + +#endif //HW3_STAFF_TTT_H