Compare commits
75 Commits
2c7d1d0b43
...
master
Author | SHA1 | Date | |
---|---|---|---|
a0a658ca8a | |||
100dc94bd3 | |||
2b911105a7 | |||
af242cb812 | |||
a7e252acd2 | |||
5fe24bffd9 | |||
6e0b7f8394 | |||
4b7880349d | |||
14131d8942 | |||
6331d81ba3 | |||
fa0cadfabf | |||
b113098c7b | |||
ab7b40f778 | |||
50ed0b89e9 | |||
1ab22651ae | |||
b6439bf7d5 | |||
1641cef13b | |||
f42ac94a45 | |||
4ff840e91e | |||
fd4ad04aeb | |||
d842485103 | |||
3bf65ab8f9 | |||
f3dcbc3b3e | |||
463dfbd3e5 | |||
d3716536f9 | |||
8805402241 | |||
3d0aeac943 | |||
c490eaa301 | |||
0e7ebb4d78 | |||
c94138ad8b | |||
cfbc726dca | |||
ec2f3320e3 | |||
43ba4aba0c | |||
c2bedb0601 | |||
77a147e08f | |||
26999a1145 | |||
f41c3d22e2 | |||
9f1f313091 | |||
8401f74922 | |||
d2dd95b7cc | |||
5cf11ac014 | |||
0dbf8936fd | |||
3ab97b3853 | |||
aea8f3dfd2 | |||
94e08f3863 | |||
a847da5339 | |||
550643281e | |||
ef869710e5 | |||
00d20ebc88 | |||
839efc3c44 | |||
0a1934fdf9 | |||
24b2a83044 | |||
7d4fd929c7 | |||
54f7dbe7ee | |||
06f44d385d | |||
6f292699f8 | |||
6a40a596c1 | |||
fc59a7221b | |||
7f0898c81e | |||
4001135451 | |||
66d7585297 | |||
727aeafdb9 | |||
3bdfdb114c | |||
f840ff9c00 | |||
986e386098 | |||
53282727ec | |||
d43dc41f25 | |||
0058e7e411 | |||
764f343f5d | |||
cdd1db6808 | |||
ae044c1905 | |||
8011c5e8b9 | |||
24eda2d16a | |||
418579a627 | |||
6acbf90d80 |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +1,6 @@
|
||||
[submodule "subprojects/raylib"]
|
||||
path = subprojects/raylib
|
||||
url = https://github.com/raysan5/raylib.git
|
||||
[submodule "subprojects/netpong-serialization"]
|
||||
path = netpong-serialization
|
||||
url = https://gitea.twomorecents.org/Rockingcool/netpong-serialization.git
|
||||
|
14
README.md
14
README.md
@@ -10,15 +10,23 @@ The game has only one runtime dependency: The [raylib](https://www.raylib.com/)
|
||||
|
||||
This application uses [Meson](https://mesonbuild.com/) as a build system. To build the application:
|
||||
1. Install meson from the link above.
|
||||
2. Set up the build directory.
|
||||
|
||||
2. Clone the repository.
|
||||
|
||||
3. Update all submodules:
|
||||
```
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
3. Set up the build directory.
|
||||
```
|
||||
meson setup build
|
||||
```
|
||||
3. Compile the application. Meson should use a system installation of raylib, if it exists. If not, it falls back to a bundled version.
|
||||
4. Compile the application. Meson should use a system installation of raylib, if it exists. If not, it falls back to a bundled version.
|
||||
```
|
||||
meson compile -C build
|
||||
```
|
||||
4. You can also create a statically-linked version of the game (with no runtime dependencies) on Linux by running the following commands:
|
||||
5. You can also create a statically-linked version of the game (with no runtime dependencies) on Linux by running the following commands:
|
||||
|
||||
```
|
||||
meson configure -Ddefault_library=static build/
|
||||
|
@@ -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(check_ip_ver(addr.data()), 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;
|
||||
|
@@ -1,11 +1,11 @@
|
||||
#include <fcntl.h>
|
||||
#include "includes/client.hpp"
|
||||
#include "includes/exception_consts.hpp"
|
||||
#include "includes/sock.hpp"
|
||||
#include "includes/easysock.h"
|
||||
|
||||
/* Destructor - closes any open sockets */
|
||||
Client::~Client() {
|
||||
free(dest);
|
||||
close(this->other_socket);
|
||||
close(this->sock_fd);
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
@@ -82,9 +83,12 @@ namespace connect_code {
|
||||
|
||||
|
||||
std::string encode(std::string address, std::string port) {
|
||||
std::string addr_coded;
|
||||
/* Convert the address to decimal, and convert that to hex */
|
||||
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
|
||||
@@ -101,10 +105,9 @@ namespace connect_code {
|
||||
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::string addr_coded = "";
|
||||
std::vector<std::string> addr_tokenized = tokenize_str(addr_expanded, ":");
|
||||
|
||||
for (int i = 0; i < addr_tokenized.size()-1; i++ ) {
|
||||
for (size_t i = 0; i < addr_tokenized.size()-1; i++ ) {
|
||||
addr_coded += base_convert(addr_tokenized[i], 16, 32);
|
||||
addr_coded += "-";
|
||||
}
|
||||
@@ -113,9 +116,6 @@ namespace connect_code {
|
||||
/* 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. */
|
||||
|
||||
std::cout << addr_coded << std::endl;
|
||||
abort();
|
||||
|
||||
}
|
||||
|
||||
/* Convert the port to hex */
|
||||
@@ -127,32 +127,59 @@ namespace connect_code {
|
||||
}
|
||||
|
||||
std::vector<std::string> decode(std::string connect_code) {
|
||||
//<AIRPLANE_CODE>
|
||||
if (connect_code.find("_") == std::string::npos) {
|
||||
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("-") == connect_code.npos) {
|
||||
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;
|
||||
}
|
||||
|
||||
//</AIRPLANE_CODE>
|
||||
if (connect_code.find("_") == std::string::npos) {
|
||||
throw std::invalid_argument("Invalid code entered.");
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -4,22 +4,35 @@
|
||||
|
||||
set -o errexit # Stop executing when a command fails
|
||||
BASE_DIR=$(dirname $0)
|
||||
REL_DIR="$BASE_DIR/release/dist"
|
||||
REL_DIR="$BASE_DIR/release/dist/pong"
|
||||
RAYLIB_DLL="$BASE_DIR/build/subprojects/raylib/libraylib.dll"
|
||||
|
||||
mkdir -p "$REL_DIR"
|
||||
rm -r "$REL_DIR"; 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"
|
||||
|
||||
# Remove the temporary file.
|
||||
rm "$BASE_DIR/tmp_file.txt"
|
||||
|
||||
#Zip the $REL_DIR folder
|
||||
zip -r "$BASE_DIR/release/netpong-win.zip" "$REL_DIR"
|
||||
# Go to the parent directory of $REL_DIR, and zip the $REL_DIR directory. This ensures
|
||||
# that the parent directories aren't included in the zip file.
|
||||
# The command is enclosed in parantheses, to ensure that the main shell's directory
|
||||
# isn't changed.
|
||||
(cd "$REL_DIR/.." && zip -r "./netpong-win.zip" "./pong")
|
||||
|
||||
|
@@ -17,10 +17,10 @@ meson compile -C "$BASE_DIR/build/"
|
||||
|
||||
# Package the application:
|
||||
# 1. Copy the executable to REL_DIR
|
||||
# 2. Create a tarball
|
||||
# 2. Create a tarball after cd'ing into the parent directory.
|
||||
|
||||
cp "$BASE_DIR/build/pong" "$REL_DIR"
|
||||
tar -czf "$BASE_DIR/release/pong.tar.gz" "$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
13
display_text.cpp
Normal 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;
|
||||
}
|
24
easysock.c
24
easysock.c
@@ -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,8 +131,9 @@ 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);
|
||||
free(port_str);
|
||||
} else {
|
||||
create_addr(network,address,port,remote_addr_struct);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
10
includes/display_text.hpp
Normal 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);
|
@@ -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). */
|
||||
|
@@ -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
|
||||
|
@@ -1,33 +0,0 @@
|
||||
#ifndef _SERIALIZATION_H
|
||||
#define _SERIALIZATION_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/* Struct used to hold the data that will be sent between sockets */
|
||||
typedef struct {
|
||||
uint16_t pad_x; // X-coordinate of sending paddle
|
||||
uint16_t pad_y; // Y-coordinate of sending paddle
|
||||
uint16_t ball_x; // X-coordinate of ball (only the server fills this in)
|
||||
uint16_t ball_y; // Y-coordinate of ball (only the server fills this in)
|
||||
bool should_quit; // Flag to indicate whether game should be quit or not
|
||||
} Serial_Data;
|
||||
|
||||
/* Create a Serial_Data struct from float values */
|
||||
Serial_Data Serial_create_data(float pad_x, float pad_y, float ball_x, float ball_y, bool should_quit);
|
||||
|
||||
/* Serialize a struct into a byte array, that can be sent through a socket */
|
||||
uint8_t* Serial_serialize(Serial_Data data);
|
||||
|
||||
/* Deserialize a byte array into a struct, and return the struct */
|
||||
Serial_Data Serial_deserialize(uint8_t* serialized);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
@@ -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();
|
||||
|
@@ -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,12 +49,16 @@ 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 */
|
||||
int getSockFD();
|
||||
|
||||
/* Returns whether or not the given socket is connected to a remote address */
|
||||
bool has_remote_address();
|
||||
|
||||
/* This is a pure virtual function (AKA an abstract function). It's purpose
|
||||
is to be redefined by the children classes (client and server). */
|
||||
virtual int get_type() = 0;
|
||||
|
249
main.cpp
249
main.cpp
@@ -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,9 +32,9 @@
|
||||
#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 "netpong-serialization/includes/serialization.h"
|
||||
#include "includes/timer.h"
|
||||
|
||||
/* Global variables used to instantiate structs */
|
||||
@@ -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
|
||||
@@ -261,6 +215,9 @@ int main(int argc, char** argv) {
|
||||
|
||||
/* Single player mode */
|
||||
if (selected_item == M_SINGLE) {
|
||||
type.mode = M_SINGLE;
|
||||
type.netsock = NULL;
|
||||
|
||||
GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // Enable text wrapping so that the long text, displayed below, will be wrapped
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
@@ -281,6 +238,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 +267,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 +289,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,9 +306,16 @@ 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);
|
||||
free(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 +354,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;
|
||||
}
|
||||
@@ -410,6 +395,7 @@ int main(int argc, char** argv) {
|
||||
/* If the response is NULL, that means it timed-out. In this case, there's no value to print */
|
||||
std::cout << "NOTHING RECEIVED" << std::endl;
|
||||
}
|
||||
free(response_array);
|
||||
}
|
||||
|
||||
/* Check to see if peer has quit the game */
|
||||
@@ -446,6 +432,7 @@ int main(int argc, char** argv) {
|
||||
/* Up */
|
||||
if (IsKeyPressed(KEY_UP) && type.mode != M_CLIENT) {
|
||||
pad2.velocity.y = (-1) * PADDLE_SPEED;
|
||||
|
||||
}
|
||||
|
||||
/* Stop */
|
||||
|
66
meson.build
66
meson.build
@@ -1,8 +1,48 @@
|
||||
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')
|
||||
|
||||
# For macOS only - define extra dependencies early
|
||||
if build_machine.system() == 'darwin'
|
||||
objc_dep = dependency('objc', required: false)
|
||||
if not objc_dep.found()
|
||||
objc_dep = compiler.find_library('objc', required: true)
|
||||
endif
|
||||
# Add other macOS frameworks that might be needed
|
||||
foundation_dep = dependency('Foundation', required: false)
|
||||
if not foundation_dep.found()
|
||||
foundation_dep = compiler.find_library('Foundation', required: false)
|
||||
endif
|
||||
cocoa_dep = dependency('Cocoa', required: false)
|
||||
if not cocoa_dep.found()
|
||||
cocoa_dep = compiler.find_library('Cocoa', required: false)
|
||||
endif
|
||||
iokit_dep = dependency('IOKit', required: false)
|
||||
if not iokit_dep.found()
|
||||
iokit_dep = compiler.find_library('IOKit', required: false)
|
||||
endif
|
||||
|
||||
extra_deps = [objc_dep]
|
||||
if foundation_dep.found()
|
||||
extra_deps += [foundation_dep]
|
||||
endif
|
||||
if cocoa_dep.found()
|
||||
extra_deps += [cocoa_dep]
|
||||
endif
|
||||
if iokit_dep.found()
|
||||
extra_deps += [iokit_dep]
|
||||
endif
|
||||
else
|
||||
extra_deps = []
|
||||
endif
|
||||
|
||||
# For Windows only
|
||||
ws2_dep = compiler.find_library('ws2_32', required: false)
|
||||
winmm = compiler.find_library('winmm', required: false)
|
||||
|
||||
# Handle raylib dependency based on library type
|
||||
# if we are building a shared library
|
||||
if get_option('default_library') == 'shared'
|
||||
raylib = dependency('raylib', required: false) # Try to find dependency with pkg-config
|
||||
@@ -16,24 +56,20 @@ if get_option('default_library') == 'shared'
|
||||
raylib_proj = cmake.subproject('raylib', options: opt_var)
|
||||
raylib = raylib_proj.dependency('raylib')
|
||||
endif
|
||||
else
|
||||
# For static library (default case)
|
||||
opt_var = cmake.subproject_options()
|
||||
opt_var.add_cmake_defines({'BUILD_SHARED_LIBS' : false})
|
||||
raylib_proj = cmake.subproject('raylib', options: opt_var)
|
||||
raylib = raylib_proj.dependency('raylib')
|
||||
endif
|
||||
|
||||
# I we are building a static library
|
||||
if get_option('default_library') == 'static'
|
||||
raylib_proj = cmake.subproject('raylib')
|
||||
raylib = raylib_proj.dependency('raylib')
|
||||
endif
|
||||
|
||||
#For Windows only
|
||||
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',
|
||||
'serialization.c', 'timer.c', 'easysock.c',
|
||||
dependencies: [raylib, ws2_dep, winmm]
|
||||
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',
|
||||
'netpong-serialization/serialization.c', 'timer.c', 'easysock.c',
|
||||
dependencies: [raylib, ws2_dep, winmm] + extra_deps
|
||||
)
|
||||
|
1
netpong-serialization
Submodule
1
netpong-serialization
Submodule
Submodule netpong-serialization added at c0c7e14aa6
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -1,87 +0,0 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#ifdef __unix__
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#endif
|
||||
#include "includes/serialization.h"
|
||||
|
||||
|
||||
/* Takes in float values, casts them to uint16_t and creates a Serial_Data struct */
|
||||
Serial_Data Serial_create_data(float pad_x, float pad_y, float ball_x, float ball_y, bool should_quit) {
|
||||
Serial_Data data;
|
||||
data.pad_x = (uint16_t)pad_x;
|
||||
data.pad_y = (uint16_t)pad_y;
|
||||
data.ball_x = (uint16_t)ball_x;
|
||||
data.ball_y = (uint16_t)ball_y;
|
||||
data.should_quit = should_quit;
|
||||
return data;
|
||||
}
|
||||
|
||||
/* Serializes a 'Data' struct into a byte array, converted to network-byte order */
|
||||
uint8_t* Serial_serialize(Serial_Data data) {
|
||||
/* Create a pointer that can fit the entire struct */
|
||||
uint8_t* serialized = malloc(sizeof(Serial_Data) + 1);
|
||||
uint8_t* pad_x_ptr;
|
||||
uint8_t* pad_y_ptr;
|
||||
uint8_t* ball_x_ptr;
|
||||
uint8_t* ball_y_ptr;
|
||||
uint8_t* should_quit_ptr;
|
||||
|
||||
memset(serialized, 0, sizeof(Serial_Data) + 1); // Zero out the memory
|
||||
pad_x_ptr = serialized;
|
||||
pad_y_ptr = pad_x_ptr + sizeof(uint16_t);
|
||||
ball_x_ptr = pad_y_ptr + sizeof(uint16_t);
|
||||
ball_y_ptr = ball_x_ptr + sizeof(uint16_t);
|
||||
should_quit_ptr = ball_y_ptr + sizeof(uint16_t);
|
||||
|
||||
*((uint16_t *)pad_x_ptr) = data.pad_x;
|
||||
*((uint16_t *)pad_x_ptr) = htons(*((uint16_t *)pad_x_ptr));
|
||||
|
||||
*((uint16_t *)pad_y_ptr) = data.pad_y;
|
||||
*((uint16_t *)pad_y_ptr) = htons(*((uint16_t *)pad_y_ptr));
|
||||
|
||||
*((uint16_t *)ball_x_ptr) = data.ball_x;
|
||||
*((uint16_t *)ball_x_ptr) = htons(*((uint16_t *)ball_x_ptr));
|
||||
|
||||
*((uint16_t *)ball_y_ptr) = data.ball_y;
|
||||
*((uint16_t *)ball_y_ptr) = htons(*((uint16_t *)ball_y_ptr));
|
||||
|
||||
*((bool *)should_quit_ptr) = data.should_quit;
|
||||
*(should_quit_ptr + sizeof(bool)) = '\0';
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/* Deserialize a byte array into a 'Data' struct, converted to host byte order */
|
||||
Serial_Data Serial_deserialize(uint8_t* serialized) {
|
||||
Serial_Data deserialized;
|
||||
/* Use successive chunks of memory address to create pointers to the data */
|
||||
uint8_t* pad_x_ptr = serialized;
|
||||
uint8_t* pad_y_ptr = serialized + sizeof(uint16_t);
|
||||
uint8_t* ball_x_ptr = pad_y_ptr + sizeof(uint16_t);
|
||||
uint8_t* ball_y_ptr = ball_x_ptr + sizeof(uint16_t);
|
||||
uint8_t* should_quit_ptr = ball_y_ptr + sizeof(uint16_t);
|
||||
|
||||
/* Dereference (and cast) the pointers, and store them into the struct */
|
||||
deserialized.pad_x = *((uint16_t *)pad_x_ptr);
|
||||
deserialized.pad_x = ntohs(deserialized.pad_x);
|
||||
|
||||
deserialized.pad_y = *((uint16_t *)pad_y_ptr);
|
||||
deserialized.pad_y = ntohs(deserialized.pad_y);
|
||||
|
||||
deserialized.ball_x = *((uint16_t *)ball_x_ptr);
|
||||
deserialized.ball_x = ntohs(deserialized.ball_x);
|
||||
|
||||
deserialized.ball_y = *((uint16_t *)ball_y_ptr);
|
||||
deserialized.ball_y = ntohs(deserialized.ball_y);
|
||||
|
||||
deserialized.should_quit = *((bool *)should_quit_ptr);
|
||||
|
||||
return deserialized;
|
||||
}
|
||||
|
25
server.cpp
25
server.cpp
@@ -1,4 +1,4 @@
|
||||
#ifdef __unix__
|
||||
#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
@@ -7,12 +7,12 @@
|
||||
#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"
|
||||
|
||||
/* Destructor - closes any open sockets */
|
||||
Server::~Server() {
|
||||
free(dest);
|
||||
close(this->other_socket);
|
||||
close(this->sock_fd);
|
||||
}
|
||||
@@ -44,11 +44,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 +73,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 +90,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 +102,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();
|
||||
|
48
sock.cpp
48
sock.cpp
@@ -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,16 @@ 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 returns whether or not the socket is connected to a remote address */
|
||||
bool Sock::has_remote_address() {
|
||||
struct sockaddr_storage addr;
|
||||
socklen_t len = sizeof(addr);
|
||||
return getpeername(this->sock_fd, (struct sockaddr*)&addr, &len) == 0;
|
||||
}
|
||||
|
||||
/* This method sends the given data, through the 'other_sockt' variable.. Client
|
||||
@@ -55,7 +55,15 @@ 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);
|
||||
int retval;
|
||||
if (this->has_remote_address()) {
|
||||
retval = send(this->sock_fd, to_send.data(), str_length, 0);
|
||||
} else {
|
||||
retval = sendto(this->sock_fd, to_send.data(), str_length, 0, (struct sockaddr *)dest, addrlen);
|
||||
}
|
||||
if (retval == -1) {
|
||||
throw errno;
|
||||
}
|
||||
}
|
||||
/* For TCP sockets */
|
||||
else {
|
||||
@@ -63,7 +71,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 +94,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 +122,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 +133,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 +148,7 @@ char* Sock::recvAllNB() {
|
||||
if (FD_ISSET(this->sock_fd, &readfs)) {
|
||||
return Sock::recvAll();
|
||||
} else {
|
||||
return NULL;
|
||||
return (char *)"";
|
||||
}
|
||||
}
|
||||
|
||||
|
23
todo.txt
23
todo.txt
@@ -1,13 +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.
|
||||
4. Create and publish statically-linked Linux binary, and create a build script for packaging it.
|
||||
5. Figure out how to build statically-linked Mac binary, and create a build script for packaging it.
|
||||
6. ----IN PROGRESS---- Figure out how to input game mode and (if applicable) IP address and port through the GUI, instead of the command-line.
|
||||
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 quit before the game actually starts i.e. while they are inputting the game mode.
|
||||
9. Add better error checking in check_server and check_client functions in check_input.cpp.
|
||||
10. Add 'install' target to Meson, to allow the user to install the game. This should also copy the .so files to the right locations.
|
||||
11. Allow the user to specify which paddle they want to control, in multi-player mode.
|
||||
12. Add IPv6 support for the server and client sockets (and everything that goes along with it, such as error handling for IP addresses).
|
||||
13. Figure out how to make 'tar' not include the entire directory structure, when creating the archive in create_static_linux.sh.
|
||||
1. Add 'install' target to Meson, to allow the user to install the game. This should also copy the .so files to the right locations.
|
||||
2. Use the struct to establish a connection, and to start each round (instead of sending strings).
|
||||
3. Figure out how to build statically-linked Mac binary, and create a build script for packaging it.
|
||||
4. Communicate the paddle reset position to the peer, after a round.
|
||||
5. 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. Allow the user to specify which paddle they want to control, in multi-player mode.
|
||||
7. Try to make the ball go between screens.
|
||||
8. 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).
|
||||
9. Create (or find) an icon for the application.
|
||||
10. [This can't really be fixed, since I'd need to purchase a developer certificate] Sign Windows executable, to remove 'Unknown Publisher' warnings.
|
||||
|
Reference in New Issue
Block a user