Commit ca0bd04a authored by Ari Trachtenberg's avatar Ari Trachtenberg
Browse files

working library

parent 1bd76680
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
//
// Created by Ari on 10/1/24.
//

#include "HttpConnect.h"

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <arpa/inet.h>
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
+30 −0
Original line number Diff line number Diff line
//
// Created by Ari Trachtenberg on 10/1/24, based on ChatGPT.
//
#include <iostream>
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

ttt_interface/ttt.cpp

0 → 100644
+216 −0
Original line number Diff line number Diff line
//
// Created by Ari on 10/1/24.
//

#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <map>
#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<string, string> parseJson(const std::string& jsonString) {
  map<string, string> 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<string, std::string> 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<string,string> 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

ttt_interface/ttt.h

0 → 100644
+129 −0
Original line number Diff line number Diff line
//
// Created by Ari on 10/1/24.
//
#include <iostream>
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