Compare commits

66 Commits

Author SHA1 Message Date
f42ac94a45 Added check to release script, to check if DLL exists 2024-03-19 09:02:48 -05:00
4ff840e91e Fixed boneheaded mistake, where I set REUSEADDR for UDP instead of TCP 2024-03-18 16:03:19 -04:00
fd4ad04aeb Removed unnecessary comment 2024-03-18 16:02:57 -04:00
d842485103 Throw errno instead of errno * -1, if an error is encountered with sending or receiving 2024-03-18 13:43:09 -04:00
3bf65ab8f9 Updated TODO 2024-03-18 13:27:14 -04:00
f3dcbc3b3e Removed check_server_client() function.
It has been replaced by check_server() and check_client().
2024-03-18 13:25:51 -04:00
463dfbd3e5 IPv6 support for RecvAllNB() 2024-03-18 13:22:26 -04:00
d3716536f9 Minor changes 2024-03-18 13:22:12 -04:00
8805402241 Return after catching exception 2024-03-18 13:22:01 -04:00
3d0aeac943 Ensure that REUSEADDR flag is only applied for TCP 2024-03-18 13:21:42 -04:00
c490eaa301 Updated TODO 2024-03-17 23:35:05 -04:00
0e7ebb4d78 Finished integrating check_server() and check_client(), check_server_client() has been commented out. 2024-03-17 23:34:24 -04:00
c94138ad8b Included new file in compilation process 2024-03-17 23:32:56 -04:00
cfbc726dca Renamed functions to make their purpose more explicit 2024-03-17 23:32:37 -04:00
ec2f3320e3 Replaced display_text_centered() with the environment-agnostic display_text() function; Changed function to include parameter to indicate environment type 2024-03-17 23:31:56 -04:00
43ba4aba0c Created a file that contains functions for agnostic text output 2024-03-17 18:09:07 -04:00
c2bedb0601 Wrote the check_num_args() function; still need to integrate it into the program. Also wrote a help text that is printed with -h flag. 2024-03-17 00:30:25 -04:00
77a147e08f Updated TODO 2024-03-17 00:27:35 -04:00
26999a1145 Started working on function to check the number of command-line arguments 2024-03-16 10:49:21 -05:00
f41c3d22e2 Updated TODO 2024-03-16 10:24:59 -05:00
9f1f313091 Cast sockaddr to sockaddr_storage 2024-03-13 15:14:38 -05:00
8401f74922 Define WIN32_LEAN_AND_MEAN to avoid including windows.h, when including winsock2.h 2024-03-13 15:10:16 -05:00
d2dd95b7cc Replaced global_args with project_args to prevent build error on MinGW 2024-03-13 15:09:48 -05:00
5cf11ac014 Added stdint header file 2024-03-13 14:36:24 -05:00
0dbf8936fd Added a #define for math constants on MinGW 2024-03-13 14:36:10 -05:00
3ab97b3853 Compile the application if it isn't already compiled 2024-03-13 13:00:28 -05:00
aea8f3dfd2 Updated TODO 2024-03-12 10:05:39 -05:00
94e08f3863 Used blocking recv here, to ensure that we wait for the server to respond 2024-03-12 10:02:53 -05:00
a847da5339 Added support for printing out the peer's IPv6 address; replaced struct sockaddr with struct sockaddr_storage 2024-03-12 10:02:23 -05:00
550643281e Replace all instances of sockaddr with sockaddr_storage 2024-03-12 09:44:41 -05:00
ef869710e5 Replaced recvAll call with recvAllNB 2024-03-12 09:44:15 -05:00
00d20ebc88 Used the non-blocking recv function, to ensure that the game doesn't hang if the server doesn't respond. 2024-03-12 09:43:48 -05:00
839efc3c44 Replaced all instances of struct sockaddr with struct sockaddr_storage, since it can fit v6 addresses as well. Cast values accordingly. 2024-03-12 09:43:16 -05:00
0a1934fdf9 Updated TODO 2024-03-12 09:42:32 -05:00
24b2a83044 Used a sockaddr_storage struct instead of a sockaddr struct, since the latter is not big enough for IPv6 2024-03-12 00:25:41 -05:00
7d4fd929c7 Updated TODO 2024-03-12 00:25:11 -05:00
54f7dbe7ee Modified recvAllNB() to return an empty string (instead of NULL) if there is nothing to read 2024-03-12 00:13:56 -05:00
06f44d385d Allow user to quit in menu screen; 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:45:05 -05:00
6f292699f8 Updated header file to reflect new function 2024-03-11 13:19:07 -05:00
6a40a596c1 Added new function to display text then exit 2024-03-11 13:18:48 -05:00
fc59a7221b Removed unnecessary #include 2024-03-11 13:18:31 -05:00
7f0898c81e Updated TODO 2024-03-11 13:17:18 -05:00
4001135451 Integrated upstream changes, since I forgot to pull before making local changes. 2024-03-11 01:32:56 -05:00
66d7585297 Removed IP version checking code (since this is handled in the Sock constructor), and allowed server (but not client, yet) to quit game before round start 2024-03-11 01:29:56 -05:00
727aeafdb9 Updated Server and Client constructor calls 2024-03-11 01:28:35 -05:00
3bdfdb114c Updated TODO 2024-03-11 01:28:04 -05:00
f840ff9c00 Updated comment explaining function 2024-03-10 21:57:58 -05:00
986e386098 Updated Server and Client constructor calls, so that they don't pass in the IP version 2024-03-10 21:56:03 -05:00
53282727ec Removed testing code 2024-03-10 21:55:09 -05:00
d43dc41f25 Updated TODO 2024-03-10 21:54:47 -05:00
0058e7e411 Removed ip_ver parameter
I removed this because I realized I could just check the IP version inside
the constructor. The Sock constructor now checks the address passed to it.
Like before, if the address is neither v4 nor v6, an exception is thrown.
Since the Server and Client constructors call the Sock constructor, no change
was required in these files, except passing the right number of parameters.
2024-03-10 21:53:06 -05:00
764f343f5d Updated TODO 2024-03-10 19:27:46 -05:00
cdd1db6808 Implemented IPv6 address decoding algorithm 2024-03-10 19:27:17 -05:00
ae044c1905 Convert character to upper-case before converting to decimal 2024-03-10 19:26:39 -05:00
8011c5e8b9 Finished script to create and package statically linked binary on Linux 2024-03-10 16:13:45 -05:00
24eda2d16a Updated TODO 2024-03-10 16:13:26 -05:00
418579a627 Added C++ standard as a compiler flag, which is apparently needed by compilers on macOS 2024-03-10 01:09:31 -06:00
6acbf90d80 Updated UNIX macro checks, to account for MacOS 2024-03-10 00:51:20 -06:00
2c7d1d0b43 Started working on client-side decoding of IPv6 code 2024-03-09 21:34:47 -05:00
00b83e6de2 Added bash flag to fail if any command in the script fails 2024-03-09 21:34:09 -05:00
9881567009 Created a script to build statically-linked binaries on Linux 2024-03-09 21:33:49 -05:00
f23f307e17 Updated TODO 2024-03-09 21:33:22 -05:00
dd658c9c1d WROTE IN AIRPLANE: Checked edge case where the number is zero 2024-03-09 21:05:38 -05:00
52f8034f4e Rudimentary support for IPv6 in server socket creation
I haven't completely implemented it yet, but I did come up with a basic algorithm
to convert the IPv6 address into a 'code' form. I still have to write the code to
actually create the socket, though.
2024-03-09 19:32:45 -05:00
8758060bfb Updated TODO 2024-03-09 19:32:03 -05:00
10f91fafd4 Updated README 2024-03-09 19:31:51 -05:00
23 changed files with 413 additions and 243 deletions

View File

@@ -9,7 +9,7 @@ The game has only one runtime dependency: The [raylib](https://www.raylib.com/)
## Building
This application uses [Meson](https://mesonbuild.com/) as a build system. To build the application:
1. Install __meson__ from the link above.
1. Install meson from the link above.
2. Set up the build directory.
```
meson setup build

View File

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

View File

@@ -1,6 +1,5 @@
#include <fcntl.h>
#include "includes/client.hpp"
#include "includes/exception_consts.hpp"
#include "includes/sock.hpp"
#include "includes/easysock.h"

View File

@@ -1,10 +1,21 @@
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <iomanip>
#include <cstring>
#include <cstdint>
#include "includes/connect_code.hpp"
#include "includes/numeric_base.hpp"
#include "includes/easysock.h"
#if defined(_WIN32)
#include <In6addr.h>
#include <Ws2tcpip.h>
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#endif
namespace connect_code {
@@ -44,13 +55,69 @@ namespace connect_code {
uint32_t addr_val = (std::stoul(octets[0]) << 24) + (std::stoul(octets[1]) << 16) + (std::stoul(octets[2]) << 8) + (std::stoul(octets[3]));
return std::to_string(addr_val);
}
/* Expand an IPv6 address (expand '::' into ':0000:', for example).
This is done by first converting the address into a binary representation,
and then printing every character of the binary representation into a string. */
std::string expand_ip6_addr(std::string addr) {
char ip6_string[40]; // 32 characters + 7 colons
struct in6_addr* ip6_s_ptr = (struct in6_addr *)malloc(sizeof(in6_addr)); // Struct pointer, to store the binary representation of the address
inet_pton(AF_INET6, addr.data(), ip6_s_ptr); // Convert the string representation into a binary form
/* This abomination, converts the binary representation into a string.
It uses sprintf to print every byte in the binary representation into a string.
The bytes are formatted as 2-character hexadecimal values. */
sprintf(ip6_string,
"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
ip6_s_ptr->s6_addr[0], ip6_s_ptr->s6_addr[1],
ip6_s_ptr->s6_addr[2], ip6_s_ptr->s6_addr[3],
ip6_s_ptr->s6_addr[4], ip6_s_ptr->s6_addr[5],
ip6_s_ptr->s6_addr[6], ip6_s_ptr->s6_addr[7],
ip6_s_ptr->s6_addr[8], ip6_s_ptr->s6_addr[9],
ip6_s_ptr->s6_addr[10], ip6_s_ptr->s6_addr[11],
ip6_s_ptr->s6_addr[12], ip6_s_ptr->s6_addr[13],
ip6_s_ptr->s6_addr[14], ip6_s_ptr->s6_addr[15]);
return std::string(ip6_string);
}
std::string encode(std::string address, std::string port) {
/* Convert the address to decimal, and convert that to hex */
std::string addr_coded = dotted_dec_to_dec(address);
addr_coded = base_convert(addr_coded, 10, 32);
std::string addr_coded = "";
if (check_ip_ver(address.data()) == 4) {
/* First, convert the address into a decimal format. Then convert this decimal format
into base-32, and also convert the port number into base-32. Join these together with
a "_". */
/* I don't really have a reason to use my own function (dotted_dec_to_dec()
and dec_to_dotted_dec()), to convert the IP address from text to binary.
The inet_pton() and inet_ntop() functions can do this just fine, and also
take care of edge cases. Maybe someday, I might change this code. I could probably
repurpose the functions for something else, though. */
/* First, convert the address into a 32-bit integer (the integer is stored as a string).
Then, convert the address into base-32. */
addr_coded = dotted_dec_to_dec(address);
addr_coded = base_convert(addr_coded, 10, 32);
}
if (check_ip_ver(address.data()) == 6) {
/* First, expand the address into the full 39-character format (32 hex values + 7 colons).
Then, tokenize the string, using colons as the delimiters.
Finally, take each token in the string, and convert it from base-16 to base-32, appending a '-' as a delimiter. */
std::string addr_expanded = expand_ip6_addr(address);
std::vector<std::string> addr_tokenized = tokenize_str(addr_expanded, ":");
for (size_t i = 0; i < addr_tokenized.size()-1; i++ ) {
addr_coded += base_convert(addr_tokenized[i], 16, 32);
addr_coded += "-";
}
addr_coded += base_convert(addr_tokenized[addr_tokenized.size()-1], 16, 32); // I put this outside the loop, because I don't want a hyphen after it
/* TODO - Check if the IP address is actually converted properly, and test if the server socket is created correctly.
Also do the same for client side, and check client-server connection. */
}
/* Convert the port to hex */
std::string port_coded = base_convert(port, 10, 32);
std::string ret_val = addr_coded + "_" + port_coded;
@@ -61,22 +128,58 @@ namespace connect_code {
std::vector<std::string> decode(std::string connect_code) {
if (connect_code.find("_") == std::string::npos) {
throw std::invalid_argument("Invalid code entered.");
throw std::invalid_argument("Invalid code entered."); // There must be an underscore, to separate the address part from the port part
}
int ip_ver = 0;
if (connect_code.find("-") != std::string::npos) {
ip_ver = 6; // If the string contains hyphens, it must be an IPv6 address encoding.
} else {
ip_ver = 4;
}
std::vector<std::string> result = tokenize_str(connect_code, "_"); /* Split the string into address and port */
std::string address = result[0]; /* Address (in base 16) */
std::string port = result[1]; /* Port (in base 16) */
/* Base 16 to base 10 - These lines convert the string to a base 10 number, and convert the result back into a string */
address = std::to_string(std::stoul(address, 0, 32));
port = std::to_string(std::stoul(port, 0, 32));
/* Convert decimal address to dotted decimal */
address = dec_to_dotted_dec(address);
std::string address = result[0]; /* Address (in base 32) */
std::string port = result[1]; /* Port (in base 32) */
std::vector<std::string> ret_val;
ret_val.push_back(address);
ret_val.push_back(port);
/* The IPv6 and IPv4 encodings are slightly different - I use a hyphen as a delimiter
for IPv6, while there is no delimiter for IPv4. This is why I need to check if the address
is IPv4 or IPv6. */
if (ip_ver == 4) {
/* Base 32 to base 10 - These lines convert the string to a base 10 number, and convert the result back into a string */
address = std::to_string(std::stoul(address, 0, 32));
port = std::to_string(std::stoul(port, 0, 32));
/* Convert decimal address to dotted decimal */
address = dec_to_dotted_dec(address);
/* Create a vector containing the address and the port, which will be returned */
ret_val.push_back(address);
ret_val.push_back(port);
} else {
/* IPv6 */
/* There are three main steps to decoding for IPv6:
1. Tokenize the address using the delimiter set while encoding ('-', in my case).
2. Convert each token from base-32 to base-16.
3. Join the string vector back together into a string, this time using ':' as a delimiter. This will give us our IP address. */
std::string conv_addr = ""; // Stores the final address
std::vector<std::string> address_tokenized = tokenize_str(address, "-"); // Step 1
for (size_t i = 0; i < address_tokenized.size()-1; i++) {
address_tokenized[i] = base_convert(address_tokenized[i], 32, 16); // Step 2
conv_addr += address_tokenized[i] + ":"; // Step 3
}
conv_addr += base_convert(address_tokenized[address_tokenized.size()-1], 32, 16); // Add the last token
port = std::to_string(std::stoul(port, 0, 32));
ret_val.push_back(conv_addr);
ret_val.push_back(port);
}
return ret_val;
}
}

View File

@@ -2,17 +2,28 @@
# This script copies required DLLs, and the application itself into a folder called 'release'. It only runs on MinGW.
set -o errexit # Stop executing when a command fails
BASE_DIR=$(dirname $0)
REL_DIR="$BASE_DIR/release/dist"
RAYLIB_DLL="$BASE_DIR/build/subprojects/raylib/libraylib.dll"
mkdir -p "$REL_DIR"
# Parse the output of the 'ldd' command, and create a file with the required DLL paths.
ldd build/pong.exe | awk ' NF == 4 {print $3}' > "$BASE_DIR/tmp_file.txt"
# Set up the build directory
meson setup build/
# 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.
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
cp "$BASE_DIR/build/pong" "$REL_DIR"

26
create_static_linux.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
# This script creates and packages a statically-linked build of the game, for Linux.
# It must be placed in the root of the source code.
set -o errexit # Stop executing when a command fails
BASE_DIR=$(dirname 0)
REL_DIR="$BASE_DIR/release/static/pong"
mkdir -p "$REL_DIR"
# Set the default build target to static
meson configure -Ddefault_library=static "$BASE_DIR/build/"
# Build the application
meson compile -C "$BASE_DIR/build/"
# Package the application:
# 1. Copy the executable to REL_DIR
# 2. Create a tarball after cd'ing into the parent directory.
cp "$BASE_DIR/build/pong" "$REL_DIR"
tar -C "$BASE_DIR/release/static" -czf "$BASE_DIR/release/pong.tar.gz" "pong/"
# Reset default build target to shared
meson configure -Ddefault_library=shared "$BASE_DIR/build/"

13
display_text.cpp Normal file
View File

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

View File

@@ -17,12 +17,14 @@ typedef 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.
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. */
GameType check_server(char* ip_text, char* port_text);
GameType check_server(char* ip_text, char* port_text, const int if_type);
/* 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. */
GameType check_client(char* code);
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, const int if_type);
#endif

View File

@@ -15,7 +15,7 @@ public:
~Client();
/* Normal constructor that calls the parent constructor to set the given values */
Client(int ip_ver, char protocol, const char* address, int port) : Sock(ip_ver, protocol, address, port) {}
Client(char protocol, const char* address, int port) : Sock(protocol, address, port) {}
void create_socket() override;

10
includes/display_text.hpp Normal file
View File

@@ -0,0 +1,10 @@
#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,11 +5,14 @@ extern "C" {
#define EASYSOCK_H_
#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 <winsock.h>
#include <ws2tcpip.h>
#endif
#ifdef __unix__
#if defined(__unix) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
@@ -47,7 +50,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'
nor '6'. */
int create_addr(int network, const char* address, int port,struct sockaddr* dest);
int create_addr(int network, const char* address, int port,struct sockaddr_storage* dest);
@@ -57,7 +60,7 @@ same as above.
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* addr_struct);
SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr_storage* addr_struct);
/* This function utilizes the same functions as 'create_local' but _connects_ to the
@@ -66,7 +69,7 @@ as above. This function needs an empty 'sockaddr *' structure passed to it, whic
If something goes wrong, this function returns with ( -1 * errno ). */
SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr* remote_addr_struct);
SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr_storage* remote_addr_struct);
/* check_ip_ver - This function checks if the given string is an IPv4 address (returns 4),
IPv6 address (returns 6) or neither (returns -1). */

View File

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

View File

@@ -15,7 +15,7 @@ public:
/* Constructors */
Server() {}
Server(int ip_ver, char protocol, const char* address, int port) : Sock(ip_ver, protocol, address, port) {}
Server(char protocol, const char* address, int port) : Sock(protocol, address, port) {}
/* Destructor */
~Server();

View File

@@ -2,7 +2,7 @@
#define _SOCK_CLASS
#include <string>
#ifdef __unix__
#if defined(__unix__) || defined(__unix) ||(defined(__APPLE__) && defined(__MACH__))
#include <sys/socket.h>
#endif
#ifdef _WIN32
@@ -23,7 +23,7 @@ protected:
int port;
int sock_fd;
std::string address;
struct sockaddr* dest;
struct sockaddr_storage* dest;
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) */
@@ -38,7 +38,7 @@ public:
virtual ~Sock();
/* Regular constructor - defined in sock.cpp */
Sock(int ip_ver, char protocol, const char* address, int port);
Sock(char protocol, const char* address, int port);
/* Method to send data in 'to_send' through the 'other_socket' socket */
void sendAll(std::string to_send);
@@ -49,7 +49,8 @@ public:
/* Method to receive data sent to the 'other_socket' socket */
char* recvAll();
/* Non-blocking receive method - calls the method above after polling for data */
/* Non-blocking receive method - calls the method above after polling for data. Returns
an empty string if there is nothing to read. */
char* recvAllNB();
/* Returns socket identifier */

240
main.cpp
View File

@@ -14,9 +14,11 @@
#include <iostream>
#define _USE_MATH_DEFINES
#include <cmath>
#include <cstring>
#include <ctime>
#include <cerrno>
#include <sstream>
#include "includes/raylib-cpp/raylib-cpp.hpp"
@@ -30,7 +32,7 @@
#include "includes/server.hpp"
#include "includes/exception_consts.hpp"
#include "includes/check_input.hpp"
#include "includes/raygui_helpers.hpp"
#include "includes/display_text.hpp"
#include "includes/easysock.h"
#include "includes/serialization.h"
#include "includes/timer.h"
@@ -47,6 +49,24 @@ const float BASE_BOUNCE_RAD = (BASE_BOUNCE_DEG / 180.0) * M_PI;
const float BASE_SPEED_COMPONENTS = 15;
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 */
int signum(int num) {
int retval = 0;
@@ -74,138 +94,68 @@ raylib::Vector2 changeVelocityAfterCollision(Paddle paddle, Ball ball) {
return raylib::Vector2(new_x_vel, new_y_vel);
}
/* This function checks the command-line arguments passed to the program.
It then decides whether the game is in Server or Client mode (or neither), and
instantiates the appropriate object. The (uninitialized) objects are passed to the
function as pointers. It returns a GameType struct, that indicates whether the game
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;
/* Checks the number and type of the command-line arguments. Throws an exception
if the args are invalid. DOES NOT PROCESS VALID ARGUMENTS. */
void check_num_args(int argc, char** argv) {
if (argc > 4) {
throw std::invalid_argument("ARGUMENT ERROR: Too many arguments. To view syntax, use -h or --help.");
}
/* GAME STARTED IN CLIENT MODE */
if (strcmp(argv[1],"-C") == 0) {
if (argc < 3) { /* No address was provided */
throw EXCEPT_TOOFEWARGS;
}
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. */
try {
addr_port = connect_code::decode(connect_code);
Client* client = new Client(4, 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;
if (argc > 1) { // Either server or client mode
if (std::string(argv[1]) == "-S") {
if (argc < 4) { // Server mode but no address and/or port
throw std::invalid_argument("ARGUMENT ERROR: Server mode specified without any address or port.");
}
type.mode = M_CLIENT;
type.netsock = client;
return type;
} catch (int e) {
throw;
} catch (std::exception& e) {
throw;
}
}
/* 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;
else if (std::string(argv[1]) == "-C") {
if (argc < 3) { // Client mode but no code
throw std::invalid_argument("ARGUMENT ERRROR: Client mode specified without any code.");
}
}
/* 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 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.
} else {
addr = std::string(argv[2]);
port = std::stoi(std::string(argv[3]));
throw std::invalid_argument("Unrecognized argument.");
}
/* 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(4, 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;
}
else {
throw EXCEPT_INVALIDARGS;
}
return;
}
int main(int argc, char** argv) {
/* Check if game was started in server or client mode, and set appropriate variables */
/* GameType struct, to define whether the game is in single or muilti-player mode, and
/* Check the number and validity of command-line arguments. Invalid arguments
will throw an exception. */
try {
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 */
GameType type;
try {
type = check_server_client(argc, argv);
} catch(int e) {
if (e == EXCEPT_TOOFEWARGS) {
std::cout << "Started in client mode, but no address was specified." << std::endl;
return -1;
/* Check if game was started in server or client mode, and call the appropriate function to process the arguments.
If game was started in single-player mode (i.e. with no arguments), then the user is prompted in the GUI. */
try { // I put this try-catch block outside the if-statement because the exception handling is the same for both client and server.
if (argc > 1) { // Server or client mode
if (std::string(argv[1]) == "-S") { // Server mode
type = check_server(argv[2], argv[3], IF_CLI);
}
if (std::string(argv[1]) == "-C") { // Client mode
type = check_client(argv[2], IF_CLI);
}
}
if (e == EXCEPT_INVALIDARGS) {
std::cout << "Invalid argument." << std::endl;
return -2;
}
if (e == EXCEPT_INVALIDIP) {
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;
} catch (std::invalid_argument& inv) {
std::cout << inv.what() << std::endl;
return -1;
} catch (int err) {
std::cout << strerror(err) << std::endl;
return -1;
}
/* Initialize window and other variables */
@@ -239,6 +189,10 @@ int main(int argc, char** argv) {
bool button_pressed = false; // Indicates whether the submit button has been pressed
while (button_pressed == false) {
if (WindowShouldClose()) {
CloseWindow();
return 0;
}
BeginDrawing();
ClearBackground(BLACK);
GuiLabel(Rectangle{(WIDTH/2)-(label_size.x/2), (HEIGHT/8), label_size.x, label_size.y}, text_to_display); // Label to display text on top
@@ -281,6 +235,11 @@ int main(int argc, char** argv) {
bool editing_ip = false; // Indicates whether the IP address text box is being edited
bool editing_port = false; // Indicates whether the port text box is being edited
while (button_pressed == false || ((strlen(ip_text) == 0) || (strlen(port_text) == 0))) {
if (WindowShouldClose()) {
CloseWindow();
return 0;
}
BeginDrawing();
ClearBackground(BLACK);
/* Label and text box for IP address */
@@ -305,12 +264,16 @@ int main(int argc, char** argv) {
}
try {
type = check_server(ip_text, port_text);
type = check_server(ip_text, port_text, IF_GUI);
} 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
free(ip_text);
free(port_text);
return -1;
} catch (std::invalid_argument& inv) {
display_text_centered(std::string(inv.what()) + "\nClosing game...");
Timer timer = timer_init(2); // Wait for two seconds
while (!timer_done(timer));
CloseWindow(); // Close and exit
display_and_exit_raygui(std::string(inv.what()) + "\nClosing game...", 2);
free(ip_text);
free(port_text);
return -1;
}
free(ip_text);
@@ -323,6 +286,11 @@ int main(int argc, char** argv) {
const char* code_label = "Enter code:";
bool editing_code = false; // Indicates whether the port text box is being edited
while (button_pressed == false || ((strlen(code_text) == 0))) {
if (WindowShouldClose()) {
CloseWindow();
return 0;
}
BeginDrawing();
ClearBackground(BLACK);
/* Label and text box for IP address */
@@ -335,8 +303,15 @@ int main(int argc, char** argv) {
button_pressed = GuiButton(Rectangle{(WIDTH/2) - (box_size.x/2), (HEIGHT/2) + (HEIGHT/6), box_size.x, box_size.y}, "Connect");
EndDrawing();
}
type = check_client(code_text);
try {
type = check_client(code_text, IF_GUI);
} 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
return -1;
} catch (std::invalid_argument& inv) {
display_and_exit_raygui(std::string(inv.what()) + "\nClosing game...", 2);
return -1;
}
free(code_text);
}
}
@@ -376,11 +351,18 @@ int main(int argc, char** argv) {
}
}
/* For client (wait for start message from server) */
/* For client (wait for start or quit message from server): When the peer quits the
game, it sends a serialized struct, containing all zeros, with the last bit turned
on as a flag. We catch this zero bit, as it indicates that the peer quit the game. */
if (type.mode == M_CLIENT) {
do {
response = type.netsock->recvAll();
} while (response[0] != 'S');
} while (response[0] != 'S' && response[0] != 0);
if (response[0] == 0) {
CloseWindow();
std::cout << "Peer unexpectedly quit game." << std::endl;
return -1;
}
game_started = true;
std::cout << "Game has been started by server." << std::endl;
}

View File

@@ -1,5 +1,6 @@
project('Pong', ['cpp', 'c'], version: '0.1')
add_global_arguments('-g', '-Wall', '-pedantic', '-Wno-unused-function', '-Wno-narrowing', language : ['cpp', 'c'])
add_global_arguments('-std=c++11', language: ['cpp'])
compiler = meson.get_compiler('cpp')
cmake = import('cmake')
@@ -28,12 +29,12 @@ endif
ws2_dep = compiler.find_library('ws2_32', required: false)
winmm = compiler.find_library('winmm', required: false)
if build_machine.system() == 'windows'
add_global_arguments('-Wl,--subsystem,windows', '-mwindows', language: ['cpp', 'c']) # Prevent opening console when game is run
add_project_arguments('-Wl,--subsystem,windows', '-mwindows', language: ['cpp', 'c']) # Prevent opening console when game is run
endif
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',
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',
'serialization.c', 'timer.c', 'easysock.c',
dependencies: [raylib, ws2_dep, winmm]
)

View File

@@ -1,6 +1,7 @@
#include "includes/numeric_base.hpp"
#include <string>
#include <cmath>
#include <cctype>
#include <algorithm>
std::string possible_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -12,10 +13,10 @@ unsigned int to_decimal(std::string num, int from_base) {
/* Here, we convert 'num' to decimal (base 10) - Find the index of
every character in the string, in 'possible_chars' and
compute the value using */
compute the value using the position of the character in the number. */
for (int i=0; i < (int)num.length(); i++) {
current_char = num.at(i);
index = possible_chars.find(current_char);
index = possible_chars.find(toupper(current_char)); // Convert the character to upper-case, so that the earliest match is detected
value += pow(from_base, num.length() - i - 1) * index;
}
@@ -26,12 +27,17 @@ unsigned int to_decimal(std::string num, int from_base) {
std::string from_decimal(unsigned int num, int to_base) {
std::string return_val;
int val = 0;
while (num > 0) {
val = num % to_base;
return_val.push_back(possible_chars[val]);
num /= to_base;
/* Handle the special case of num being zero: In this case, the result is also zero */
if (num == 0) {
return_val = "0";
} else {
while (num > 0) {
val = num % to_base;
return_val.push_back(possible_chars[val]);
num /= to_base;
}
}
/* Reverse the string, since we started from the right */
std::reverse(return_val.begin(), return_val.end());

View File

@@ -1,7 +1,7 @@
#include "includes/raygui_helpers.hpp"
#include "includes/raygui/raygui.h"
void display_text_centered(std::string to_disp) {
#include "includes/timer.h"
void display_text_raygui(std::string to_disp) {
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
@@ -11,3 +11,10 @@ void display_text_centered(std::string to_disp) {
EndDrawing();
return;
}
void display_and_exit_raygui(std::string to_disp, int time) {
display_text_raygui(to_disp);
Timer timer = timer_init(time);
while (!timer_done(timer));
return;
}

View File

@@ -2,7 +2,7 @@
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#ifdef __unix__
#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
#include <arpa/inet.h>
#endif
#ifdef _WIN32

View File

@@ -1,4 +1,4 @@
#ifdef __unix__
#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
#include <sys/socket.h>
#endif
#ifdef _WIN32
@@ -7,7 +7,6 @@
#include <fcntl.h>
#include "includes/sock.hpp"
#include "includes/server.hpp"
#include "includes/exception_consts.hpp"
#include "includes/connect_code.hpp"
#include "includes/easysock.h"
@@ -44,11 +43,14 @@ char* Server::recvAll() {
if (this->ip_ver == 4) {
/* FOR IPv4 */
struct sockaddr_in* temp_struct = (struct sockaddr_in*)this->dest;
/* 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 casted 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)));
} else {
/* FOR IPv6 */
peer_addr = "IPV6 NOT SUPPORTED YET";
/* FOR IPv6 - Use the inet_ntop function, and convert the struct's address into a string */
struct sockaddr_in6* temp_struct = (struct sockaddr_in6*)this->dest;
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;
@@ -70,8 +72,11 @@ char* Server::recvAllNB() {
/* 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)));
} else {
/* FOR IPv6 */
peer_addr = "IPV6 NOT SUPPORTED YET";
/* FOR IPv6 - Use the inet_ntop function, and convert the struct's address into a string */
struct sockaddr_in6* temp_struct = (struct sockaddr_in6*)this->dest;
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;
@@ -84,7 +89,7 @@ is thrown as an exception. */
void Server::wait_for_peer() {
if (this->protocol == ES_TCP) {
this->other_socket = accept(this->sock_fd, dest, &addrlen);
this->other_socket = accept(this->sock_fd, (struct sockaddr *)dest, &addrlen);
if (this->other_socket < 0) {
throw errno;
}
@@ -96,8 +101,7 @@ 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
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
'catching' the thrown exception and using strerror().
This function also sets a timeout of 100ms for UDP sockets */
'catching' the thrown exception and using strerror().*/
void Server::create_socket() {
Sock::create_socket();

View File

@@ -1,7 +1,6 @@
#include <cerrno>
#include <stdexcept>
#include "includes/sock.hpp"
#include "includes/exception_consts.hpp"
#include "includes/easysock.h"
/* Function to create socket. This function doesn't actually create a socket
@@ -9,7 +8,7 @@
extend this function, and create the appropriate sockets. */
void Sock::create_socket() {
dest = (struct sockaddr *)malloc(sizeof(struct sockaddr));
dest = (struct sockaddr_storage *)malloc(sizeof(struct sockaddr_storage));
addrlen = sizeof(*dest);
}
@@ -18,11 +17,11 @@ Sock::~Sock() {}
/* Constructor - This function initializes the object attributes with the given
parameters. It throws an exception if an IPv4 address was given, but the type
given is IPv6 (or the other way around). */
parameters. The address version (IPv4 or IPv6) is determined based on the given address. */
Sock::Sock(int ip_ver, char protocol, const char* address, int port) {
/* Error checking */
Sock::Sock(char protocol, const char* address, int port) {
this->ip_ver = check_ip_ver(address);
if (ip_ver != 4 && ip_ver != 6) {
throw std::invalid_argument("Invalid IP address type");
}
@@ -33,15 +32,9 @@ Sock::Sock(int ip_ver, char protocol, const char* address, int port) {
throw std::invalid_argument("Invalid protocol");
}
this->ip_ver = ip_ver;
this->protocol = protocol;
this->port = port;
this->address = std::string(address);
/* Check to see if the given IP address matches the given ip_ver */
if ((check_ip_ver(address) != 6 && ip_ver == 6) || (check_ip_ver(address) != 4 && ip_ver == 4)) {
throw std::invalid_argument("Invalid IP address for given type.");
}
}
/* This method sends the given data, through the 'other_sockt' variable.. Client
@@ -55,7 +48,9 @@ void Sock::sendAll(std::string to_send) {
/* For UDP sockets */
if (this->protocol == ES_UDP) {
sendto(this->sock_fd, to_send.data(), str_length, 0, dest, addrlen);
if (sendto(this->sock_fd, to_send.data(), str_length, 0, (struct sockaddr *)dest, addrlen) == -1) {
throw errno;
}
}
/* For TCP sockets */
else {
@@ -63,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 */
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) {
throw errno * -1;
throw errno;
}
total_bytes_sent += num_bytes_sent;
}
@@ -86,14 +81,17 @@ This function also needs more testing for TCP. */
char* Sock::recvAll() {
int num_bytes_received = 0;
int total_bytes_received = 0;
char* buffer = (char *)malloc(100);
char* buffer = (char *)malloc(150 * sizeof(char));
bool has_been_read = false;
if (this->protocol == ES_UDP) {
num_bytes_received = recvfrom(this->sock_fd, buffer, 99, 0, dest, &addrlen);
num_bytes_received = recvfrom(this->sock_fd, buffer, 99, 0, (struct sockaddr *)dest, &addrlen);
if (num_bytes_received == 0) {
return NULL;
}
if (num_bytes_received < 0) {
throw errno;
}
/* Null-terminate the string */
*(buffer + num_bytes_received) = '\0';
return buffer;
@@ -111,7 +109,7 @@ char* Sock::recvAll() {
}
if (num_bytes_received < 0) {
throw errno * -1;
throw errno;
}
total_bytes_received += num_bytes_received;
has_been_read = true;
@@ -122,7 +120,8 @@ char* Sock::recvAll() {
return buffer;
}
/* Non-blocking recv call - Uses 'select' to poll for data from the FD. */
/* Non-blocking recv call - Uses 'select' to poll for data from the FD. Returns an empty string if there
is nothing to read. */
char* Sock::recvAllNB() {
struct timeval tv;
fd_set readfs;
@@ -136,7 +135,7 @@ char* Sock::recvAllNB() {
if (FD_ISSET(this->sock_fd, &readfs)) {
return Sock::recvAll();
} else {
return NULL;
return (char *)"";
}
}

View File

@@ -1,10 +1,10 @@
1. Try to make the ball go between screens.
2. ----SHOULD BE DONE---- Add code to zip the dist/ folder inside the release_build script.
3. Sign Windows executable, to remove 'Unknown Publisher' warnings.
5. Create and publish statically-linked Linux binary, and create a build script for packaging it.
6. Figure out how to build statically-linked Mac binary, and create a build script for packaging it.
7. ----IN PROGRESS---- Figure out how to input game mode and (if applicable) IP address and port through the GUI, instead of the command-line.
8. 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).
9. Allow the user to quit before the game actually starts i.e. while they are inputting the game mode.
11. Add better error checking in check_server and check_client functions in check_input.cpp.
12. Add 'install' target to Meson, to allow the user to install the game. This should also copy the .so files to the right locations.
1. Sign Windows executable, to remove 'Unknown Publisher' warnings.
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. Use free() to free allocated memory.
4. Use the struct to establish a connection, and to start each round (instead of sending strings).
5. Figure out how to build statically-linked Mac binary, and create a build script for packaging it.
6. Communicate the paddle reset position to the peer, after a round.
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. Allow the user to specify which paddle they want to control, in multi-player mode.
9. Try to make the ball go between screens.
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).