Compare commits

1 Commits

Author SHA1 Message Date
451dc1f803 Allow user to quit in menu scree; better error handling.
I set up a try-catch to catch the exception thrown by the Server/Client when
it can't create a socket. I also used display_and_exit() to
automatically close the window after the text has been displayed.
2024-03-11 13:19:27 -05:00
15 changed files with 203 additions and 200 deletions

View File

@@ -1,5 +1,4 @@
#include <iostream> #include <iostream>
#include <cstdint>
#include "includes/easysock.h" #include "includes/easysock.h"
#include "includes/connect_code.hpp" #include "includes/connect_code.hpp"
#include "includes/server.hpp" #include "includes/server.hpp"
@@ -8,20 +7,19 @@
#include "includes/raygui/raygui.h" #include "includes/raygui/raygui.h"
#include "includes/exception_consts.hpp" #include "includes/exception_consts.hpp"
#include "includes/raygui_helpers.hpp" #include "includes/raygui_helpers.hpp"
#include "includes/display_text.hpp"
#include "includes/timer.h" #include "includes/timer.h"
GameType check_server(char* ip_text, char* port_text, const int if_mode) { GameType check_server(char* ip_text, char* port_text) {
GameType type; GameType type;
std::string addr; std::string addr;
uint16_t port; uint16_t port;
/* Check if IP address and port are in valid forms */ /* Check if IP address and port are in valid forms */
if (check_ip_ver(ip_text) < 0) { if (check_ip_ver(ip_text) < 0) {
throw std::invalid_argument("Invalid IP address."); throw std::invalid_argument("Invalid IP address");
} }
if (port_to_num(port_text) < 0) { if (port_to_num(port_text) < 0) {
throw std::invalid_argument("Invalid port."); throw std::invalid_argument("Invalid port");
} }
/* From here on, we assume that the IP and port are valid */ /* From here on, we assume that the IP and port are valid */
@@ -34,7 +32,7 @@ GameType check_server(char* ip_text, char* port_text, const int if_mode) {
/* Create server socket and wait for client to connect */ /* Create server socket and wait for client to connect */
Server* server = new Server(ES_UDP, addr.data(), port); Server* server = new Server(ES_UDP, addr.data(), port);
server->create_socket(); server->create_socket();
display_text("Your code is " + code + "\nWaiting for connection...", if_mode); display_text_centered("Your code is " + code + "\nWaiting for connection...");
std::string response = ""; std::string response = "";
char* temp_response = NULL; char* temp_response = NULL;
/* Wait for the client to connect. Since recvAll returns a char*, we need to create a temporary variable to check for NULL. /* Wait for the client to connect. Since recvAll returns a char*, we need to create a temporary variable to check for NULL.
@@ -45,31 +43,33 @@ GameType check_server(char* ip_text, char* port_text, const int if_mode) {
response = std::string(temp_response); response = std::string(temp_response);
server->sendAll("U2"); server->sendAll("U2");
display_text("Connection received from " + server->get_peer_addr(), if_mode); display_text_centered("Connection received from " + server->get_peer_addr());
Timer timer = timer_init(3);
while (!timer_done(timer)); // Wait for five seconds
type.mode = M_SERVER; type.mode = M_SERVER;
type.netsock = server; type.netsock = server;
return type; return type;
} }
GameType check_client(char* code_text, const int if_mode) { GameType check_client(char* code_text) {
GameType type; GameType type;
std::vector<std::string> addr_port; std::vector<std::string> addr_port;
std::string connect_code = std::string(code_text); /* The connect code is a special string, that contains the server address and port. It is given by the server. */ std::string connect_code = std::string(code_text); /* The connect code is a special string, that contains the server address and port. It is given by the server. */
try { try {
addr_port = connect_code::decode(connect_code); addr_port = connect_code::decode(connect_code);
if (check_ip_ver(addr_port[0].data()) < 0) {
throw std::invalid_argument("Invalid code entered.");
}
Client* client = new Client(ES_UDP, addr_port[0].data(), std::stoi(addr_port[1])); Client* client = new Client(ES_UDP, addr_port[0].data(), std::stoi(addr_port[1]));
client->create_socket(); client->create_socket();
/* Send a specific message to the server, and wait for the appropriate response, to know that the server is ready */ /* Send a specific message to the server, and wait for the appropriate response, to know that the server is ready */
client->sendAll("GG"); client->sendAll("GG");
// display_text_centered("Connecting...");
std::string msg_from_server = client->recvAll(); std::string msg_from_server = client->recvAll();
if (msg_from_server == "U2") { if (msg_from_server == "U2") {
display_text("Connection made", if_mode); display_text_centered("Connection made");
Timer timer = timer_init(3);
while (!timer_done(timer));
} else { } else {
throw std::invalid_argument("Server didn't respond with correct message."); throw EXCEPT_WRONGRESPONSE;
} }
type.mode = M_CLIENT; type.mode = M_CLIENT;
type.netsock = client; type.netsock = client;

View File

@@ -5,25 +5,15 @@
set -o errexit # Stop executing when a command fails set -o errexit # Stop executing when a command fails
BASE_DIR=$(dirname $0) BASE_DIR=$(dirname $0)
REL_DIR="$BASE_DIR/release/dist" REL_DIR="$BASE_DIR/release/dist"
RAYLIB_DLL="$BASE_DIR/build/subprojects/raylib/libraylib.dll"
mkdir -p "$REL_DIR" mkdir -p "$REL_DIR"
# Set up the build directory # Parse the output of the 'ldd' command, and create a file with the required DLL paths.
meson setup build/ ldd build/pong.exe | awk ' NF == 4 {print $3}' > "$BASE_DIR/tmp_file.txt"
# Build the application
meson compile -C build/
# Parse the output of the 'ldd' command (using only DLLs that are found) and create a file with the required DLL paths.
ldd build/pong.exe | awk ' NF == 4 {print $3}' | grep -i "dll" > "$BASE_DIR/tmp_file.txt"
# Copy the required DLLs. # Copy the required DLLs.
cp $(cat "$BASE_DIR/tmp_file.txt") "$REL_DIR" cp $(cat "$BASE_DIR/tmp_file.txt") "$REL_DIR"
# Copy the raylib DLL, if it does not exist in the directory
cp -n "$RAYLIB_DLL" "$REL_DIR"
# Copy the executable itself # Copy the executable itself
cp "$BASE_DIR/build/pong" "$REL_DIR" cp "$BASE_DIR/build/pong" "$REL_DIR"

View File

@@ -1,13 +0,0 @@
#include "includes/display_text.hpp"
#include <iostream>
#include <string>
void display_text(std::string to_disp, const int if_mode) {
if (if_mode == IF_CLI) {
std::cout << to_disp << std::endl;
}
if (if_mode == IF_GUI) {
display_text_raygui(to_disp);
}
return;
}

View File

@@ -52,23 +52,22 @@ SOCKET create_socket(int network, char transport) {
int newSock = socket(domain,type,0); int newSock = socket(domain,type,0);
/* Set REUSEADDR flag for TCP, allowing program to be run twice */ /* Set REUSEADDR flag, allowing program to be run twice */
if (transport == ES_TCP) { int set_opt = 1;
int set_opt = 1; setsockopt(newSock, SOL_SOCKET, SO_REUSEADDR, (char *)&set_opt, sizeof(set_opt));
setsockopt(newSock, SOL_SOCKET, SO_REUSEADDR, (char *)&set_opt, sizeof(set_opt));
}
return newSock; return newSock;
} }
int create_addr(int network, const char* address, int port,struct sockaddr_storage* dest) { int create_addr(int network, const char* address, int port,struct sockaddr* dest) {
if (network == 4) { if (network == 4) {
struct sockaddr_in listen_address; struct sockaddr_in listen_address;
listen_address.sin_family = AF_INET; listen_address.sin_family = AF_INET;
listen_address.sin_port = htons(port); listen_address.sin_port = htons(port);
inet_pton(AF_INET,address,&listen_address.sin_addr); inet_pton(AF_INET,address,&listen_address.sin_addr);
memcpy((struct sockaddr *)dest,&listen_address,sizeof(listen_address)); memcpy(dest,&listen_address,sizeof(listen_address));
return 0; return 0;
} else if (network == 6) { } else if (network == 6) {
@@ -76,7 +75,7 @@ int create_addr(int network, const char* address, int port,struct sockaddr_stora
listen_ipv6.sin6_family = AF_INET6; listen_ipv6.sin6_family = AF_INET6;
listen_ipv6.sin6_port = htons(port); listen_ipv6.sin6_port = htons(port);
inet_pton(AF_INET6,address,&listen_ipv6.sin6_addr); inet_pton(AF_INET6,address,&listen_ipv6.sin6_addr);
memcpy((struct sockaddr_in6 *)dest,&listen_ipv6,sizeof(listen_ipv6)); memcpy(dest,&listen_ipv6,sizeof(listen_ipv6));
return 0; return 0;
} else { } else {
@@ -85,7 +84,7 @@ int create_addr(int network, const char* address, int port,struct sockaddr_stora
} }
SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr_storage* addr_struct) { SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr* addr_struct) {
int socket = create_socket(network,transport); int socket = create_socket(network,transport);
if (socket < 0) { if (socket < 0) {
return (-1 * errno); return (-1 * errno);
@@ -104,14 +103,14 @@ SOCKET create_local (int network, char transport, const char* address, int port,
This should be set to the size of 'sockaddr_in' for IPv4, and 'sockaddr_in6' for IPv6. This should be set to the size of 'sockaddr_in' for IPv4, and 'sockaddr_in6' for IPv6.
See https://stackoverflow.com/questions/73707162/socket-bind-failed-with-invalid-argument-error-for-program-running-on-macos */ See https://stackoverflow.com/questions/73707162/socket-bind-failed-with-invalid-argument-error-for-program-running-on-macos */
int i = bind (socket,(struct sockaddr *)addr_struct,(socklen_t)addrlen); int i = bind (socket,addr_struct,(socklen_t)addrlen);
if (i < 0) { if (i < 0) {
return (-1 * errno); return (-1 * errno);
} }
return socket; return socket;
} }
SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr_storage* remote_addr_struct) { SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr* remote_addr_struct) {
struct addrinfo hints; /* Used to tell getaddrinfo what kind of address we want */ struct addrinfo hints; /* Used to tell getaddrinfo what kind of address we want */
struct addrinfo* results; /* Used by getaddrinfo to store the addresses */ struct addrinfo* results; /* Used by getaddrinfo to store the addresses */
@@ -131,7 +130,7 @@ SOCKET create_remote (int network,char transport, const char* address,int port,s
if (err_code != 0) { if (err_code != 0) {
return (-1 * err_code); return (-1 * err_code);
} }
remote_addr_struct = (struct sockaddr_storage *)results->ai_addr; remote_addr_struct = results->ai_addr;
network = inet_to_int(results->ai_family); network = inet_to_int(results->ai_family);
} else { } else {
create_addr(network,address,port,remote_addr_struct); create_addr(network,address,port,remote_addr_struct);

View File

@@ -17,14 +17,12 @@ typedef struct {
/* This function checks the IP address and port passed to it, and returns a struct, /* This function checks the IP address and port passed to it, and returns a struct,
that contains information about the game mode, and contains the server socket. that contains information about the game mode, and contains the server socket.
It assumes that both ip_text and port_text are non-null. It assumes that both ip_text and port_text are non-null
Any errors are printed using the display_text function, with the given if_type.
TODO - Add better error checking. */ TODO - Add better error checking. */
GameType check_server(char* ip_text, char* port_text, const int if_type); GameType check_server(char* ip_text, char* port_text);
/* NOT IMPLEMENTED YET - This function checks the code given to it, and returns a struct /* NOT IMPLEMENTED YET - This function checks the code given to it, and returns a struct
that contains information about the game mode, and contains the client socket. that contains information about the game mode, and contains the client socket. */
Any errors are printed using the display_text function, with the given if_type. */ GameType check_client(char* code);
GameType check_client(char* code, const int if_type);
#endif #endif

View File

@@ -1,10 +0,0 @@
#include "includes/raygui_helpers.hpp"
/* Constants that can be used by caller function. */
const int IF_CLI = 1;
const int IF_GUI = 2;
/* This function is used to display text. It is used to abstract the differences
between GUI invocation and CLI invocation. The if_mode parameter is used to
determine whether the game was launched from GUI or CLI. */
void display_text(std::string to_disp, const int if_mode);

View File

@@ -5,9 +5,6 @@ extern "C" {
#define EASYSOCK_H_ #define EASYSOCK_H_
#ifdef _WIN32 #ifdef _WIN32
#define NOGDI // All GDI defines and routines
#define NOUSER // All USER defines and routines
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h> #include <winsock2.h>
#include <winsock.h> #include <winsock.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
@@ -50,7 +47,7 @@ and dest is a pointer to the sockaddr struct that will be filled in.
The function returns with -202 if the network parameter contained neither '4' The function returns with -202 if the network parameter contained neither '4'
nor '6'. */ nor '6'. */
int create_addr(int network, const char* address, int port,struct sockaddr_storage* dest); int create_addr(int network, const char* address, int port,struct sockaddr* dest);
@@ -60,7 +57,7 @@ same as above.
It prints the error returned by 'bind' if something went wrong, and returns ( -1 * errno ).*/ It prints the error returned by 'bind' if something went wrong, and returns ( -1 * errno ).*/
SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr_storage* addr_struct); SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr* addr_struct);
/* This function utilizes the same functions as 'create_local' but _connects_ to the /* This function utilizes the same functions as 'create_local' but _connects_ to the
@@ -69,7 +66,7 @@ as above. This function needs an empty 'sockaddr *' structure passed to it, whic
If something goes wrong, this function returns with ( -1 * errno ). */ If something goes wrong, this function returns with ( -1 * errno ). */
SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr_storage* remote_addr_struct); SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr* remote_addr_struct);
/* check_ip_ver - This function checks if the given string is an IPv4 address (returns 4), /* check_ip_ver - This function checks if the given string is an IPv4 address (returns 4),
IPv6 address (returns 6) or neither (returns -1). */ IPv6 address (returns 6) or neither (returns -1). */

View File

@@ -4,8 +4,8 @@
/* Display the given text, centered on the screen, as a label. /* Display the given text, centered on the screen, as a label.
NEEDS RAYGUI LIBRARY. */ NEEDS RAYGUI LIBRARY. */
void display_text_raygui(std::string to_disp); void display_text_centered(std::string to_disp);
/* Display the given string, and exit the game after 'time' seconds. */ /* Display the given string, and exit the game after 'time' seconds. */
void display_and_exit_raygui(std::string to_disp, int time); void display_and_exit(std::string to_disp, int time);
#endif #endif

View File

@@ -23,7 +23,7 @@ protected:
int port; int port;
int sock_fd; int sock_fd;
std::string address; std::string address;
struct sockaddr_storage* dest; struct sockaddr* dest;
socklen_t addrlen; socklen_t addrlen;
int other_socket; // The peer socket (the client if this socket is a server, and the server if this socket is a client) */ int other_socket; // The peer socket (the client if this socket is a server, and the server if this socket is a client) */
@@ -49,8 +49,7 @@ public:
/* Method to receive data sent to the 'other_socket' socket */ /* Method to receive data sent to the 'other_socket' socket */
char* recvAll(); char* recvAll();
/* Non-blocking receive method - calls the method above after polling for data. Returns /* Non-blocking receive method - calls the method above after polling for data */
an empty string if there is nothing to read. */
char* recvAllNB(); char* recvAllNB();
/* Returns socket identifier */ /* Returns socket identifier */

210
main.cpp
View File

@@ -14,7 +14,6 @@
#include <iostream> #include <iostream>
#define _USE_MATH_DEFINES
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <ctime> #include <ctime>
@@ -32,7 +31,7 @@
#include "includes/server.hpp" #include "includes/server.hpp"
#include "includes/exception_consts.hpp" #include "includes/exception_consts.hpp"
#include "includes/check_input.hpp" #include "includes/check_input.hpp"
#include "includes/display_text.hpp" #include "includes/raygui_helpers.hpp"
#include "includes/easysock.h" #include "includes/easysock.h"
#include "includes/serialization.h" #include "includes/serialization.h"
#include "includes/timer.h" #include "includes/timer.h"
@@ -49,24 +48,6 @@ const float BASE_BOUNCE_RAD = (BASE_BOUNCE_DEG / 180.0) * M_PI;
const float BASE_SPEED_COMPONENTS = 15; const float BASE_SPEED_COMPONENTS = 15;
const float BASE_SPEED = sqrt(powf(BASE_SPEED_COMPONENTS, 2) * 2); const float BASE_SPEED = sqrt(powf(BASE_SPEED_COMPONENTS, 2) * 2);
std::string HELP_TEXT = "\nnetpong - A networked pong game for the internet era.\n"
"\n"
"Usage: \n"
"netpong [MODE] [ADDRESS PORT]|[CODE]\n"
"\n"
"MODE: \n"
"-S : Server mode. Starts a server to allow the other player to connect.\n"
"IP address and port must be specified.\n"
"\n"
"-C: Client mode. Connects to a server, using the provided connection code.\n"
"\n"
"If no mode is specified, single player mode is used as default.\n"
"\n"
"CONTROLS:"
"\'W\' and \'S\' control left paddle (AKA client paddle)\n"
"Up and Down arrow keys control right paddle (AKA server paddle)\n";
/* Simple function to return 1 if a value is positive, and -1 if it is negative */ /* Simple function to return 1 if a value is positive, and -1 if it is negative */
int signum(int num) { int signum(int num) {
int retval = 0; int retval = 0;
@@ -94,68 +75,142 @@ raylib::Vector2 changeVelocityAfterCollision(Paddle paddle, Ball ball) {
return raylib::Vector2(new_x_vel, new_y_vel); return raylib::Vector2(new_x_vel, new_y_vel);
} }
/* Checks the number and type of the command-line arguments. Throws an exception /* This function checks the command-line arguments passed to the program.
if the args are invalid. DOES NOT PROCESS VALID ARGUMENTS. */ It then decides whether the game is in Server or Client mode (or neither), and
void check_num_args(int argc, char** argv) { instantiates the appropriate object. The (uninitialized) objects are passed to the
if (argc > 4) { function as pointers. It returns a GameType struct, that indicates whether the game
throw std::invalid_argument("ARGUMENT ERROR: Too many arguments. To view syntax, use -h or --help."); is in server, client or single player mode, and contains the appropriate socket object. */
GameType check_server_client(int argc, char** argv) {
std::string connect_code;
std::vector<std::string> addr_port; /* Vector to store (IPv4) address and port */
GameType type;
if (argc < 2) { /* Game was not started in client or server mode */
type.mode = M_SINGLE;
type.netsock = nullptr;
return type;
} }
if (argc > 1) { // Either server or client mode
if (std::string(argv[1]) == "-S") { /* GAME STARTED IN CLIENT MODE */
if (argc < 4) { // Server mode but no address and/or port if (strcmp(argv[1],"-C") == 0) {
throw std::invalid_argument("ARGUMENT ERROR: Server mode specified without any address or port."); if (argc < 3) { /* No address was provided */
} throw EXCEPT_TOOFEWARGS;
} }
else if (std::string(argv[1]) == "-C") { connect_code = std::string(argv[2]); /* The connect code is a special string, that contains the server address and port. It is given by the server. */
if (argc < 3) { // Client mode but no code try {
throw std::invalid_argument("ARGUMENT ERRROR: Client mode specified without any code."); addr_port = connect_code::decode(connect_code);
/* Check IP address version */
if (check_ip_ver(addr_port[0].data()) < 0) {
throw std::invalid_argument("Invalid code entered.");
} }
Client* client = new Client(ES_UDP, addr_port[0].data(), std::stoi(addr_port[1]));
client->create_socket();
/* Send a specific message to the server, and wait for the appropriate response, to know that the server is ready */
client->sendAll("GG");
std::string msg_from_server = client->recvAll();
if (msg_from_server == "U2") {
std::cout << "Connection made. Waiting for server to begin game..." << std::endl;
} else {
throw EXCEPT_WRONGRESPONSE;
}
type.mode = M_CLIENT;
type.netsock = client;
return type;
} catch (int e) {
throw;
} catch (std::exception& e) {
throw;
} }
else if (std::string(argv[1]) == "-h" || std::string(argv[1]) == "--help") { }
throw std::invalid_argument(HELP_TEXT); // I am abusing the exception mechanism here, so that I can ensure that the caller quits the program after printing the help message.
/* GAME STARTED IN SERVER MODE */
else if (strcmp(argv[1],"-S") == 0) {
std::string addr;
uint16_t port;
/* No IP address or port specified */
if (argc < 3) {
throw EXCEPT_TOOFEWARGS;
}
/* IP address but no port */
else if (argc < 4) {
std::cout << "No port specified, using 6500..." << std::endl;
addr = std::string(argv[2]);
port = 6500;
} else { } else {
throw std::invalid_argument("Unrecognized argument."); addr = std::string(argv[2]);
port = std::stoi(std::string(argv[3]));
} }
/* Check if IP is valid */
if (check_ip_ver(addr.data()) < 0) {
throw EXCEPT_INVALIDIP;
}
std::string code = connect_code::encode(addr, std::to_string(port));
std::cout << "Your code is " << code << std::endl;
/* Create server socket and wait for client to connect */
Server* server = new Server(ES_UDP, addr.data(), port);
server->create_socket();
std::cout << "Waiting for connection..." << std::endl;
std::string response = "";
char* temp_response = NULL;
/* Wait for the right client to connect. Since recvAll returns a char*, we need to create a temporary variable to check for NULL.
TODO - Check that the client actually sends 'GG'. */
do {
temp_response = server->recvAll();
} while (temp_response == NULL);
response = std::string(temp_response);
std::cout << "Connection received from " << server->get_peer_addr() << std::endl;
server->sendAll("U2");
type.mode = M_SERVER;
type.netsock = server;
return type;
} }
return;
else {
throw EXCEPT_INVALIDARGS;
}
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
/* Check the number and validity of command-line arguments. Invalid arguments /* Check if game was started in server or client mode, and set appropriate variables */
will throw an exception. */
try { /* GameType struct, to define whether the game is in single or muilti-player mode, and
check_num_args(argc, argv);
} catch (std::invalid_argument& inv) {
std::cout << inv.what() << std::endl;
return -1;
}
/* From here on, we assume that:
a. The program was started with no arguments (User is prompted in GUI), OR
b. The program was started in server mode, and an additional was given, OR
c. The program was started in client mode, and an additional argument was given. */
/* GameType struct, to define whether the game is in single or multi-player mode, and
to hold the appropriate socket */ to hold the appropriate socket */
GameType type; GameType type;
/* Check if game was started in server or client mode, and call the appropriate function to process the arguments. try {
If game was started in single-player mode (i.e. with no arguments), then the user is prompted in the GUI. */ type = check_server_client(argc, argv);
try { // I put this try-catch block outside the if-statement because the exception handling is the same for both client and server. } catch(int e) {
if (argc > 1) { // Server or client mode if (e == EXCEPT_TOOFEWARGS) {
if (std::string(argv[1]) == "-S") { // Server mode std::cout << "Started in client mode, but no address was specified." << std::endl;
type = check_server(argv[2], argv[3], IF_CLI); return -1;
}
if (std::string(argv[1]) == "-C") { // Client mode
type = check_client(argv[2], IF_CLI);
}
} }
} catch (std::invalid_argument& inv) { if (e == EXCEPT_INVALIDARGS) {
std::cout << inv.what() << std::endl; std::cout << "Invalid argument." << std::endl;
return -1; return -2;
} catch (int err) { }
std::cout << strerror(err) << std::endl; if (e == EXCEPT_INVALIDIP) {
return -1; std::cout << "Invalid IP address provided." << std::endl;
return -5;
}
if (e == EXCEPT_WRONGRESPONSE) {
std::cout << "The server didn't respond with the correct message. Are you sure you have used the right server?" << std::endl;
return -6;
}
else {
std::cout << strerror(e) << std::endl;
return -7;
}
} catch(std::invalid_argument& inv) {
std::cout << inv.what() << std::endl;
return -8;
} }
/* Initialize window and other variables */ /* Initialize window and other variables */
@@ -264,16 +319,12 @@ int main(int argc, char** argv) {
} }
try { try {
type = check_server(ip_text, port_text, IF_GUI); type = check_server(ip_text, port_text);
} catch (int e) { } catch (int e) {
display_and_exit_raygui(std::string(std::strerror(e)) + "\nClosing game...", 2); // The server constructor throws the errno if it cannot create a socket display_and_exit(std::string(std::strerror(e)) + "\nClosing game...", 2); // The server constructor throws the errno if it cannot create a socket
free(ip_text);
free(port_text);
return -1; return -1;
} catch (std::invalid_argument& inv) { } catch (std::invalid_argument& inv) {
display_and_exit_raygui(std::string(inv.what()) + "\nClosing game...", 2); display_and_exit(std::string(inv.what()) + "\nClosing game...", 2);
free(ip_text);
free(port_text);
return -1; return -1;
} }
free(ip_text); free(ip_text);
@@ -304,12 +355,9 @@ int main(int argc, char** argv) {
EndDrawing(); EndDrawing();
} }
try { try {
type = check_client(code_text, IF_GUI); type = check_client(code_text);
} catch (int e) { } catch (int e) {
display_and_exit_raygui(std::string(std::strerror(e)) + "\nClosing game...", 2); // The client constructor throws the errno if it cannot create a socket display_and_exit(std::string(std::strerror(e)) + "\nClosing game...", 2); // The client constructor throws the errno if it cannot create a socket
return -1;
} catch (std::invalid_argument& inv) {
display_and_exit_raygui(std::string(inv.what()) + "\nClosing game...", 2);
return -1; return -1;
} }
free(code_text); free(code_text);

View File

@@ -29,12 +29,12 @@ endif
ws2_dep = compiler.find_library('ws2_32', required: false) ws2_dep = compiler.find_library('ws2_32', required: false)
winmm = compiler.find_library('winmm', required: false) winmm = compiler.find_library('winmm', required: false)
if build_machine.system() == 'windows' if build_machine.system() == 'windows'
add_project_arguments('-Wl,--subsystem,windows', '-mwindows', language: ['cpp', 'c']) # Prevent opening console when game is run add_global_arguments('-Wl,--subsystem,windows', '-mwindows', language: ['cpp', 'c']) # Prevent opening console when game is run
endif endif
executable('pong', executable('pong',
'main.cpp', 'sock.cpp','paddle.cpp', 'ball.cpp', 'numeric_base.cpp', 'connect_code.cpp', 'server.cpp', 'client.cpp', 'check_input.cpp', 'raygui_helpers.cpp', 'display_text.cpp', 'main.cpp', 'sock.cpp','paddle.cpp', 'ball.cpp', 'numeric_base.cpp', 'connect_code.cpp', 'server.cpp', 'client.cpp', 'check_input.cpp', 'raygui_helpers.cpp',
'serialization.c', 'timer.c', 'easysock.c', 'serialization.c', 'timer.c', 'easysock.c',
dependencies: [raylib, ws2_dep, winmm] dependencies: [raylib, ws2_dep, winmm]
) )

View File

@@ -1,7 +1,7 @@
#include "includes/raygui_helpers.hpp" #include "includes/raygui_helpers.hpp"
#include "includes/raygui/raygui.h" #include "includes/raygui/raygui.h"
#include "includes/timer.h" #include "includes/timer.h"
void display_text_raygui(std::string to_disp) { void display_text_centered(std::string to_disp) {
const char* to_disp_cstr = to_disp.c_str(); const char* to_disp_cstr = to_disp.c_str();
Vector2 label_size = MeasureTextEx(GetFontDefault(), to_disp_cstr, GuiGetStyle(DEFAULT, TEXT_SIZE)+1, GuiGetStyle(DEFAULT, TEXT_SPACING)+1); // The '+1' is there to account for any rounding errors Vector2 label_size = MeasureTextEx(GetFontDefault(), to_disp_cstr, GuiGetStyle(DEFAULT, TEXT_SIZE)+1, GuiGetStyle(DEFAULT, TEXT_SPACING)+1); // The '+1' is there to account for any rounding errors
@@ -12,8 +12,8 @@ void display_text_raygui(std::string to_disp) {
return; return;
} }
void display_and_exit_raygui(std::string to_disp, int time) { void display_and_exit(std::string to_disp, int time) {
display_text_raygui(to_disp); display_text_centered(to_disp);
Timer timer = timer_init(time); Timer timer = timer_init(time);
while (!timer_done(timer)); while (!timer_done(timer));
return; return;

View File

@@ -43,14 +43,11 @@ char* Server::recvAll() {
if (this->ip_ver == 4) { if (this->ip_ver == 4) {
/* FOR IPv4 */ /* FOR IPv4 */
struct sockaddr_in* temp_struct = (struct sockaddr_in*)this->dest; struct sockaddr_in* temp_struct = (struct sockaddr_in*)this->dest;
/* Convert the s_addr field of the casted struct to host network-byte, and convert it to a dotted decimal */ /* Convert the s_addr field of the caseted struct to host network-byte, and convert it to a dotted decimal */
peer_addr = connect_code::dec_to_dotted_dec(std::to_string(htonl(temp_struct->sin_addr.s_addr))); peer_addr = connect_code::dec_to_dotted_dec(std::to_string(htonl(temp_struct->sin_addr.s_addr)));
} else { } else {
/* FOR IPv6 - Use the inet_ntop function, and convert the struct's address into a string */ /* FOR IPv6 */
struct sockaddr_in6* temp_struct = (struct sockaddr_in6*)this->dest; peer_addr = "IPV6 NOT SUPPORTED YET";
char* temp_buf = (char *)malloc(sizeof(char) * (INET6_ADDRSTRLEN + 1));
peer_addr = std::string(inet_ntop(AF_INET6, temp_struct->sin6_addr.s6_addr, temp_buf, INET6_ADDRSTRLEN));
free(temp_buf);
} }
return to_return; return to_return;
@@ -72,11 +69,8 @@ char* Server::recvAllNB() {
/* Convert the s_addr field of the caseted struct to host network-byte, and convert it to a dotted decimal */ /* Convert the s_addr field of the caseted struct to host network-byte, and convert it to a dotted decimal */
peer_addr = connect_code::dec_to_dotted_dec(std::to_string(htonl(temp_struct->sin_addr.s_addr))); peer_addr = connect_code::dec_to_dotted_dec(std::to_string(htonl(temp_struct->sin_addr.s_addr)));
} else { } else {
/* FOR IPv6 - Use the inet_ntop function, and convert the struct's address into a string */ /* FOR IPv6 */
struct sockaddr_in6* temp_struct = (struct sockaddr_in6*)this->dest; peer_addr = "IPV6 NOT SUPPORTED YET";
char* temp_buf = (char *)malloc(sizeof(char) * (INET6_ADDRSTRLEN + 1));
peer_addr = std::string(inet_ntop(AF_INET6, temp_struct->sin6_addr.s6_addr, temp_buf, INET6_ADDRSTRLEN));
free(temp_buf);
} }
return to_return; return to_return;
@@ -89,7 +83,7 @@ is thrown as an exception. */
void Server::wait_for_peer() { void Server::wait_for_peer() {
if (this->protocol == ES_TCP) { if (this->protocol == ES_TCP) {
this->other_socket = accept(this->sock_fd, (struct sockaddr *)dest, &addrlen); this->other_socket = accept(this->sock_fd, dest, &addrlen);
if (this->other_socket < 0) { if (this->other_socket < 0) {
throw errno; throw errno;
} }
@@ -101,7 +95,8 @@ called immediately after the constructor. If the socket is TCP, it also sets the
socket to listen for incoming connections. This function throws an exception if socket to listen for incoming connections. This function throws an exception if
the socket could not be created. The excpetion is an integer corresponding to the errno the socket could not be created. The excpetion is an integer corresponding to the errno
of the failing function, and enables the caller to print a corresponding error message by of the failing function, and enables the caller to print a corresponding error message by
'catching' the thrown exception and using strerror().*/ 'catching' the thrown exception and using strerror().
This function also sets a timeout of 100ms for UDP sockets */
void Server::create_socket() { void Server::create_socket() {
Sock::create_socket(); Sock::create_socket();

View File

@@ -8,7 +8,7 @@
extend this function, and create the appropriate sockets. */ extend this function, and create the appropriate sockets. */
void Sock::create_socket() { void Sock::create_socket() {
dest = (struct sockaddr_storage *)malloc(sizeof(struct sockaddr_storage)); dest = (struct sockaddr *)malloc(sizeof(struct sockaddr));
addrlen = sizeof(*dest); addrlen = sizeof(*dest);
} }
@@ -20,6 +20,7 @@ Sock::~Sock() {}
parameters. The address version (IPv4 or IPv6) is determined based on the given address. */ parameters. The address version (IPv4 or IPv6) is determined based on the given address. */
Sock::Sock(char protocol, const char* address, int port) { Sock::Sock(char protocol, const char* address, int port) {
/* Error checking */
this->ip_ver = check_ip_ver(address); this->ip_ver = check_ip_ver(address);
if (ip_ver != 4 && ip_ver != 6) { if (ip_ver != 4 && ip_ver != 6) {
@@ -32,6 +33,7 @@ Sock::Sock(char protocol, const char* address, int port) {
throw std::invalid_argument("Invalid protocol"); throw std::invalid_argument("Invalid protocol");
} }
this->ip_ver = ip_ver;
this->protocol = protocol; this->protocol = protocol;
this->port = port; this->port = port;
this->address = std::string(address); this->address = std::string(address);
@@ -48,9 +50,7 @@ void Sock::sendAll(std::string to_send) {
/* For UDP sockets */ /* For UDP sockets */
if (this->protocol == ES_UDP) { if (this->protocol == ES_UDP) {
if (sendto(this->sock_fd, to_send.data(), str_length, 0, (struct sockaddr *)dest, addrlen) == -1) { sendto(this->sock_fd, to_send.data(), str_length, 0, dest, addrlen);
throw errno;
}
} }
/* For TCP sockets */ /* For TCP sockets */
else { else {
@@ -58,7 +58,7 @@ void Sock::sendAll(std::string to_send) {
/* Send the data to the 'other_socket' variable, which should be set by the client and server methods */ /* Send the data to the 'other_socket' variable, which should be set by the client and server methods */
num_bytes_sent = send(this->other_socket, to_send.substr(total_bytes_sent).c_str(), str_length - total_bytes_sent, 0); num_bytes_sent = send(this->other_socket, to_send.substr(total_bytes_sent).c_str(), str_length - total_bytes_sent, 0);
if (num_bytes_sent < 0) { if (num_bytes_sent < 0) {
throw errno; throw errno * -1;
} }
total_bytes_sent += num_bytes_sent; total_bytes_sent += num_bytes_sent;
} }
@@ -81,17 +81,14 @@ This function also needs more testing for TCP. */
char* Sock::recvAll() { char* Sock::recvAll() {
int num_bytes_received = 0; int num_bytes_received = 0;
int total_bytes_received = 0; int total_bytes_received = 0;
char* buffer = (char *)malloc(150 * sizeof(char)); char* buffer = (char *)malloc(100);
bool has_been_read = false; bool has_been_read = false;
if (this->protocol == ES_UDP) { if (this->protocol == ES_UDP) {
num_bytes_received = recvfrom(this->sock_fd, buffer, 99, 0, (struct sockaddr *)dest, &addrlen); num_bytes_received = recvfrom(this->sock_fd, buffer, 99, 0, dest, &addrlen);
if (num_bytes_received == 0) { if (num_bytes_received == 0) {
return NULL; return NULL;
} }
if (num_bytes_received < 0) {
throw errno;
}
/* Null-terminate the string */ /* Null-terminate the string */
*(buffer + num_bytes_received) = '\0'; *(buffer + num_bytes_received) = '\0';
return buffer; return buffer;
@@ -109,7 +106,7 @@ char* Sock::recvAll() {
} }
if (num_bytes_received < 0) { if (num_bytes_received < 0) {
throw errno; throw errno * -1;
} }
total_bytes_received += num_bytes_received; total_bytes_received += num_bytes_received;
has_been_read = true; has_been_read = true;
@@ -120,8 +117,7 @@ char* Sock::recvAll() {
return buffer; return buffer;
} }
/* Non-blocking recv call - Uses 'select' to poll for data from the FD. Returns an empty string if there /* Non-blocking recv call - Uses 'select' to poll for data from the FD. */
is nothing to read. */
char* Sock::recvAllNB() { char* Sock::recvAllNB() {
struct timeval tv; struct timeval tv;
fd_set readfs; fd_set readfs;
@@ -135,7 +131,7 @@ char* Sock::recvAllNB() {
if (FD_ISSET(this->sock_fd, &readfs)) { if (FD_ISSET(this->sock_fd, &readfs)) {
return Sock::recvAll(); return Sock::recvAll();
} else { } else {
return (char *)""; return NULL;
} }
} }

View File

@@ -1,10 +1,14 @@
1. Sign Windows executable, to remove 'Unknown Publisher' warnings. 1. Try to make the ball go between screens.
2. Add 'install' target to Meson, to allow the user to install the game. This should also copy the .so files to the right locations. 3. Sign Windows executable, to remove 'Unknown Publisher' warnings.
3. Use free() to free allocated memory. 4. Figure out how to build statically-linked Mac binary, and create a build script for packaging it.
4. Use the struct to establish a connection, and to start each round (instead of sending strings). 5. ----IN PROGRESS---- Figure out how to input game mode and (if applicable) IP address and port through the GUI, instead of the command-line.
5. Figure out how to build statically-linked Mac binary, and create a build script for packaging it. 6. Clean up / refactor the raygui code in main.cpp, that asks user for game mode. Instead of just having a giant blob of code in main.cpp, maybe split it into a function, or move it to another file. It should be easy to split it into a different function, since none of the functions take any specific parameters. The text box function, for example, only takes in the rectangle coordinates, and the text to display. I can move the code to a function, and then pass in any parameters that I need to pass in (I don't think I need to pass many parameters, though).
6. Communicate the paddle reset position to the peer, after a round. 7. Allow the user to quit before the game actually starts i.e. while they are inputting the game mode.
7. Clean up / refactor the raygui code in main.cpp, that asks user for game mode. Instead of just having a giant blob of code in main.cpp, maybe split it into a function, or move it to another file. It should be easy to split it into a different function, since none of the functions take any specific parameters. The text box function, for example, only takes in the rectangle coordinates, and the text to display. I can move the code to a function, and then pass in any parameters that I need to pass in (I don't think I need to pass many parameters, though). 8. Add better error checking in check_server and check_client functions in check_input.cpp. e.g. If client fails to connect, display a message.
8. Allow the user to specify which paddle they want to control, in multi-player mode. 9. Add 'install' target to Meson, to allow the user to install the game. This should also copy the .so files to the right locations.
9. Try to make the ball go between screens. 10. Allow the user to specify which paddle they want to control, in multi-player mode.
10. Change the networking code, so that a single server can connect two clients with each other. The server should provide player 1 with a code, and player 2 can connect with player 1 using that code (essentially like a room). 11. Add IPv6 support for the server and client sockets (and everything that goes along with it, such as error handling for IP addresses).
12. Communicate the paddle reset position to the peer, after a round.
13. Test with valgrind.
14. Use the struct to establish a connection, and to start each round (instead of sending strings).
15. Use check_client() and check_server() for CLI invocation as well, and pass a flag that indicataes whether the parameters were entered through GUI or CLI (also probably create a function to handle printing vs. GUI display).