Compare commits

35 Commits

Author SHA1 Message Date
f4c3ef9b19 Renamed file 2024-03-06 15:59:46 -06:00
bb4601c5bd Chenged FPS to 60 for release build 2024-03-06 15:59:12 -06:00
45aa6ba4bc Added version number, added an additional compiler argument for Windows (doesn't work yet) 2024-03-06 15:58:47 -06:00
c3f6ae0ae9 Added .gitignore 2024-03-06 13:09:06 -06:00
21c864da60 Created script to copy DLLs into application fodler on mingw 2024-03-06 13:06:29 -06:00
a2fed8e4b0 Updated meson.build to include additional DLLs needed on Windows 2024-03-06 12:47:58 -06:00
1196ebd228 Updated README 2024-03-05 22:42:28 -05:00
3a9a32d7e3 Updated README 2024-03-05 22:29:09 -05:00
a5202ad85a Add support for building a statically linked version of the game, by specifying a command-line flag 2024-03-05 22:26:40 -05:00
61a856e88f Added raylib submodule, under subprojects directory 2024-03-05 21:03:08 -05:00
6170c95666 Delete raylib submodule and move it inside 'subprojects' 2024-03-05 20:58:41 -05:00
d6f597d8c0 Added raylib submodule 2024-03-05 20:47:18 -05:00
591c3b16a2 Fixed stupid error (using 'meson' instead of 'compiler') 2024-03-05 16:39:10 -05:00
2ea5bb4fe2 Find raylib even if pkg-config is not found 2024-03-05 16:36:43 -05:00
f961db5e58 Added code to send quit message only if game is not in single player mode 2024-03-05 07:57:20 -05:00
64aa4b1850 Replaced 'linux' with '__unix__' because the same header files are included on macOS as well. 2024-03-05 07:50:32 -05:00
0e504060cf Modified README 2024-03-04 23:51:48 -05:00
d69b627bb1 Added README 2024-03-04 23:50:16 -05:00
91bf5e2ce1 Fixed some commenting issues; Added code to detect if game was quit by peer and, if so, quit the game locally 2024-03-01 22:35:31 -05:00
50c090cd88 Added a boolean field to the struct, to indicate whether the game should be quit or not 2024-03-01 22:34:33 -05:00
2c735896df Used unsigned int instead of int when converting to base-10 2024-03-01 11:31:18 -05:00
24c1dd6391 Removed unnecessary variable, and added a (optional) dependency for the ws2_32 library, which is required on Windows 2024-03-01 11:31:18 -05:00
8e3488b904 Added a virtual destructor to the Sock class, which would allow Server and Client to override it 2024-03-01 11:31:15 -05:00
c2c095dfa7 Added code to get a non-const char* from a std::string 2024-03-01 11:31:08 -05:00
047ff602ed Changed 'char*' to 'const char*' because that's what the underlying functions use 2024-03-01 11:31:01 -05:00
ff8dc1f1f7 Removed files related to cross-compilation.
I had a ton of issues related to cross-compilation (libraries not found, clashing function
names with raylib, improper linking), and I realized that it's too much work to cross-compile,
_and_ I'm using a build system. Instead, I'm probably just going to use my Windows VM to compile for Windows.
I still haven't decided what to do for Mac, though.
2024-02-29 16:40:49 -05:00
4b3d5387a1 Changed code to support Windows libraries and functions as well 2024-02-29 16:38:36 -05:00
ddbbc322a6 Deleted Makefile, switched over fully to meson build system 2024-02-29 16:38:01 -05:00
b90d37c73f Added relevant files for cross-compilation to windows 2024-02-29 16:37:26 -05:00
69e70eb206 Changed the recvAll return type from std::string to char pointer, and created a non-blocking version of the function 2024-02-28 00:05:53 -05:00
349b0b78db Created method to set position of paddle 2024-02-28 00:05:05 -05:00
a37ec79f09 Changed 'recvAll' return type from std::string to char pointer, and created a non-blocking version of the function 2024-02-28 00:04:43 -05:00
ba667d020d Game is mostly finished, added a ton of code for reading and applying peer position.
The most important addition is that the program now parses data in the
Serial_Data struct, and updates the positions accordingly. I also removed
the old implementation with strings, and fixed a bunch of bugs along the way.
2024-02-28 00:00:53 -05:00
effeea73b9 Made the serialization code cleaner, and fixed a bug where data was overwritten by the null pointer 2024-02-28 00:00:28 -05:00
c6bbe82d25 Made the 'recvAll' function return a char pointer instead of a std::string, this is better for portability. Also created a non-blocking version of the function. 2024-02-27 23:59:53 -05:00
23 changed files with 418 additions and 163 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
main.o main.o
easysock.o easysock.o
pong pong
release/

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "subprojects/raylib"]
path = subprojects/raylib
url = https://github.com/raysan5/raylib.git

View File

@@ -1,11 +0,0 @@
LDLIBS = -lraylib
CXXFLAGS += -Wall -g -Wextra -pedantic -Werror
pong: main.o easysock.o paddle.hpp ball.hpp math-helpers.hpp
g++ $(CXXFLAGS) main.o easysock.o -o pong $(LDLIBS)
main.o : main.cpp
g++ $(CXXFLAGS) -c main.cpp -o main.o
easysock.o : easysock.c
g++ $(CXXFLAGS) -c easysock.c -o easysock.o

32
README.md Normal file
View File

@@ -0,0 +1,32 @@
## Netpong - A Pong game for the internet era
__Netpong__ is a network-enabled Pong game, written in C++. It enables two players to play against each other, provided an IP address and a port. It also supports a single-player mode.
## How it works
The game has only one runtime dependency: The [raylib](https://www.raylib.com/) graphics system. In order to write idiomatic C++, I chose to use the [raylib-cpp](https://robloach.github.io/raylib-cpp/) wrapper, which provides an object-oriented interface to the Raylib library. However, this wrapper is bundled with the project, and is thus not required to be installed.
## Building
This application uses [Meson](https://mesonbuild.com/) as a build system. To build the application:
1. Install __meson__ from the link above.
2. Install __raylib__ from the link above (THIS IS OPTIONAL, SEE STEP 5)
3. Set up the build directory with the `meson setup build` command.
4. Compile the application, with the existing raylib installation, using `meson compile -C build`.
5. If you don't have raylib installed, you can create a statically linked version of the library on Linux by running the following commands:
```
meson configure -Ddefault_library=static build/
meson compile -C build -Ddefault_library=static
```
## Running
- To run in single-player mode:
- Run the application with no arguments: `build/pong`
- Left paddle is controlled with `W` and `S` keys, right paddle is controlled with `Up` and `Down` arrow keys.
- To run in multi-player mode:
- One player runs the application in Server mode, specifying their IP address and a port: `build/pong -S <ip_address> <port>`
- The other player connects to the first player by running in Client mode, specifying the first player's IP address and port: `build/pong -C <ip_address> <port>`.
- The server controls the left paddle by default (WIP to allow the user to modify this), and the client controls the right paddle.

View File

@@ -16,7 +16,7 @@ address specified in the constructor, and will throw an exception if that fails.
The exception thrown is an integer, that corresponds to the errno returned by the failing The exception thrown is an integer, that corresponds to the errno returned by the failing
function. This enables a client to 'catch' the thrown exception, and print the corresponding function. This enables a client to 'catch' the thrown exception, and print the corresponding
error message using strerror(). error message using strerror().
*/ This function also sets a timeout of 100ms for UDP sockets. */
void Client::create_socket() { void Client::create_socket() {
Sock::create_socket(); Sock::create_socket();
@@ -24,6 +24,12 @@ void Client::create_socket() {
if (this->sock_fd < 0) { if (this->sock_fd < 0) {
throw (this->sock_fd * -1); throw (this->sock_fd * -1);
} }
// if (protocol == ES_UDP) {
// struct timeval tv;
// tv.tv_sec = 0;
// tv.tv_usec = 10000;
// setsockopt(this->sock_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// }
} }
/* Sends given data to the peer socket. This method is overriden, because a TCP /* Sends given data to the peer socket. This method is overriden, because a TCP
@@ -39,11 +45,17 @@ void Client::sendAll(std::string to_send) {
/* Receives data from peer socket, and returns it. See above for better /* Receives data from peer socket, and returns it. See above for better
explanation of why this method is overriden. */ explanation of why this method is overriden. */
std::string Client::recvAll() { char* Client::recvAll() {
this->other_socket = this->sock_fd; this->other_socket = this->sock_fd;
return Sock::recvAll(); return Sock::recvAll();
} }
/* Same as function above, but calls Sock::recvAllNB() */
char* Client::recvAllNB() {
this->other_socket = this->sock_fd;
return Sock::recvAllNB();
}
/* Returns the type of socket based on the global constants set in sock.hpp */ /* Returns the type of socket based on the global constants set in sock.hpp */
int Client::get_type() { int Client::get_type() {
return SOCK_CLIENT; return SOCK_CLIENT;

View File

@@ -11,8 +11,10 @@ namespace connect_code {
/* Tokenizes a string, based on the given delimiter */ /* Tokenizes a string, based on the given delimiter */
std::vector<std::string> tokenize_str(std::string str, std::string delim) { std::vector<std::string> tokenize_str(std::string str, std::string delim) {
std::vector<std::string> result; std::vector<std::string> result;
char* c_str = str.data(); /* &str[0] is used to convert an std::string to a char*. I tried using string.data(),
char* c_delim = delim.data(); but that appears to return a const char*. */
char* c_str = &str[0];
char* c_delim = &delim[0];
char* tok = strtok(c_str, c_delim); char* tok = strtok(c_str, c_delim);
while (tok != NULL) { while (tok != NULL) {

21
create_release_build.sh Normal file
View File

@@ -0,0 +1,21 @@
#!/bin/bash
# This script copies required DLLs, and the application itself into a folder called 'release'. It only runs on MinGW.
BASE_DIR=$(dirname $0)
REL_DIR="$BASE_DIR/release/dist"
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"
# Copy the required DLLs.
cp $(cat "$BASE_DIR/tmp_file.txt") "$REL_DIR"
# Copy the executable itself
cp "$BASE_DIR/build/pong" "$REL_DIR"
# Remove the temporary file.
rm "$BASE_DIR/tmp_file.txt"

View File

@@ -5,7 +5,7 @@
#include <cerrno> #include <cerrno>
#include <fcntl.h> #include <fcntl.h>
#ifndef _WIN_32 #ifndef _WIN32
const int INVALID_SOCKET = -1; const int INVALID_SOCKET = -1;
#endif #endif
@@ -27,10 +27,8 @@ int sock_quit(void) {
} }
/* Function to create a socket - Accepts IP version(4 or 6), protocol /* Function to create a socket - Accepts IP version(4 or 6), protocol
type (PROTO_TCP or PROTO_UDP) and a flag to indicate whether the socket type (PROTO_TCP or PROTO_UDP). */
should be set in blocking mode or not. This flag is ONLY FOR TCP. It does SOCKET create_socket(int network, char transport) {
nothing if the protocol is UDP.*/
int create_socket(int network, char transport, bool is_blocking) {
sock_init(); sock_init();
int domain; int domain;
int type; int type;
@@ -57,16 +55,11 @@ int create_socket(int network, char transport, bool is_blocking) {
int set_opt = 1; int set_opt = 1;
setsockopt(newSock, SOL_SOCKET, SO_REUSEADDR, (char *)&set_opt, sizeof(set_opt)); setsockopt(newSock, SOL_SOCKET, SO_REUSEADDR, (char *)&set_opt, sizeof(set_opt));
if (is_blocking && transport == ES_TCP) {
int flags = fcntl(newSock, F_GETFL);
flags |= O_NONBLOCK;
fcntl(newSock,F_SETFL,flags);
}
return newSock; return newSock;
} }
int create_addr(int network, char* address, int port,struct sockaddr* dest) { int create_addr(int network, const char* address, int port,struct sockaddr* dest) {
if (network == 4) { if (network == 4) {
struct sockaddr_in listen_address; struct sockaddr_in listen_address;
@@ -90,8 +83,8 @@ int create_addr(int network, char* address, int port,struct sockaddr* dest) {
} }
int create_local (int network, char transport, char* address, int port,struct sockaddr* addr_struct, bool is_blocking) { SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr* addr_struct) {
int socket = create_socket(network,transport, is_blocking); int socket = create_socket(network,transport);
if (socket < 0) { if (socket < 0) {
return (-1 * errno); return (-1 * errno);
} }
@@ -116,7 +109,7 @@ int create_local (int network, char transport, char* address, int port,struct so
return socket; return socket;
} }
int create_remote (int network,char transport,char* address,int port,struct sockaddr* remote_addr_struct, bool is_blocking) { SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr* remote_addr_struct) {
struct addrinfo hints; /* Used to tell getaddrinfo what kind of address we want */ struct addrinfo hints; /* Used to tell getaddrinfo what kind of address we want */
struct addrinfo* results; /* Used by getaddrinfo to store the addresses */ struct addrinfo* results; /* Used by getaddrinfo to store the addresses */
@@ -142,7 +135,7 @@ int create_remote (int network,char transport,char* address,int port,struct sock
create_addr(network,address,port,remote_addr_struct); create_addr(network,address,port,remote_addr_struct);
} }
int socket = create_socket(network,transport, is_blocking); int socket = create_socket(network,transport);
if (socket < 0) { if (socket < 0) {
return (-1 * errno); return (-1 * errno);
} }

View File

@@ -21,7 +21,10 @@ public:
void sendAll(std::string to_send); void sendAll(std::string to_send);
std::string recvAll(); char* recvAll();
/* Non-blocking receive */
char* recvAllNB();
/* Return the type of socket */ /* Return the type of socket */
int get_type() override; int get_type() override;

View File

@@ -1,10 +1,12 @@
#ifndef EASYSOCK_HPP_ #ifndef EASYSOCK_HPP_
#define EASYSOCK_HPP_ #define EASYSOCK_HPP_
#ifdef _WIN_32 #ifdef _WIN32
#include <winsock2.h> #include <winsock2.h>
#include <Ws2tcpip.h> #include <winsock.h>
#else #include <ws2tcpip.h>
#endif
#ifdef __unix__
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <netdb.h> #include <netdb.h>
@@ -12,7 +14,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#endif #endif
#ifndef _WIN_32 #ifndef _WIN32
typedef int SOCKET; typedef int SOCKET;
#endif #endif
@@ -25,13 +27,12 @@ const char ES_UDP = 'U';
a layer 3 - network layer - integer, which must be '4' for IPv4 a layer 3 - network layer - integer, which must be '4' for IPv4
and 6 for IPv6; and 6 for IPv6;
a layer 4 - transport layer - character, which must be 'T' for a layer 4 - transport layer - character, which must be 'T' for
TCP or 'U' for UDP; and TCP or 'U' for UDP.
a bool that indicates whether the socket should be blocking or non-blocking.
It creates a _blocking_ socket, and returns the created socket, or -1
if the socket creation failed.*/
It returns the created socket, or -1 if the socket creation failed.*/ SOCKET create_socket(int network, char transport);
SOCKET create_socket(int network, char transport, bool is_blocking = false);
/* This function fills in the sockaddr struct 'dest' based on the given information. /* This function fills in the sockaddr struct 'dest' based on the given information.
@@ -43,7 +44,7 @@ and dest is a pointer to the sockaddr struct that will be filled in.
The function returns with -202 if the network parameter contained neither '4' The function returns with -202 if the network parameter contained neither '4'
nor '6'. */ nor '6'. */
SOCKET create_addr(int network, char* address, int port,struct sockaddr* dest); int create_addr(int network, const char* address, int port,struct sockaddr* dest);
@@ -53,7 +54,7 @@ same as above.
It prints the error returned by 'bind' if something went wrong, and returns ( -1 * errno ).*/ It prints the error returned by 'bind' if something went wrong, and returns ( -1 * errno ).*/
SOCKET create_local (int network, char transport, char* address, int port,struct sockaddr* addr_struct, bool is_blocking = false); SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr* addr_struct);
/* This function utilizes the same functions as 'create_local' but _connects_ to the /* This function utilizes the same functions as 'create_local' but _connects_ to the
@@ -62,7 +63,7 @@ as above. This function needs an empty 'sockaddr *' structure passed to it, whic
If something goes wrong, this function returns with ( -1 * errno ). */ If something goes wrong, this function returns with ( -1 * errno ). */
SOCKET create_remote (int network,char transport,char* address,int port,struct sockaddr* remote_addr_struct, bool is_blocking = false); SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr* remote_addr_struct);
/* check_ip_ver - This function checks if the given string is an IPv4 address (returns 4), /* check_ip_ver - This function checks if the given string is an IPv4 address (returns 4),
IPv6 address (returns 6) or neither (returns -1). */ IPv6 address (returns 6) or neither (returns -1). */

View File

@@ -3,10 +3,10 @@
#include <string> #include <string>
/* Convert the given value from the given base, to base 10 */ /* Convert the given value from the given base, to base 10 */
int to_decimal(std::string num, int from_base); unsigned int to_decimal(std::string num, int from_base);
/* Convert the given value from base 10 to the given base */ /* Convert the given value from base 10 to the given base */
std::string from_decimal(int num, int to_base); std::string from_decimal(unsigned int num, int to_base);
/* Convert the given value from 'from_base', to 'to_base' */ /* Convert the given value from 'from_base', to 'to_base' */
std::string base_convert(std::string num, int from_base, int to_base); std::string base_convert(std::string num, int from_base, int to_base);

View File

@@ -25,6 +25,9 @@ public:
/* Increments the number of points that this paddle has earned */ /* Increments the number of points that this paddle has earned */
void incrementPoints(); void incrementPoints();
/* Sets the paddle position */
void setPosition(int x, int y);
/* Resets the paddle position */ /* Resets the paddle position */
void reset(); void reset();

View File

@@ -6,17 +6,19 @@ extern "C" {
#endif #endif
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
/* Struct used to hold the data that will be sent between sockets */ /* Struct used to hold the data that will be sent between sockets */
typedef struct { typedef struct {
uint16_t pad_x; uint16_t pad_x; // X-coordinate of sending paddle
uint16_t pad_y; uint16_t pad_y; // Y-coordinate of sending paddle
uint16_t ball_x; uint16_t ball_x; // X-coordinate of ball (only the server fills this in)
uint16_t ball_y; 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; } Serial_Data;
/* Create a Serial_Data struct from float values */ /* 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); 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 */ /* Serialize a struct into a byte array, that can be sent through a socket */
uint8_t* Serial_serialize(Serial_Data data); uint8_t* Serial_serialize(Serial_Data data);

View File

@@ -30,10 +30,13 @@ public:
FOR UDP - Send data to the client, from which data was received. FOR UDP - Send data to the client, from which data was received.
FOR UDP, this function MUST be called after recvAll() */ FOR UDP, this function MUST be called after recvAll() */
void sendAll(std::string to_send); void sendAll(std::string to_send);
/* Receive data from peer socket */ /* Receive data from peer socket */
std::string recvAll(); char* recvAll();
/* Non-blocking receive */
char* recvAllNB();
/* Return the address of the peer */ /* Return the address of the peer */
std::string get_peer_addr(); std::string get_peer_addr();

View File

@@ -2,7 +2,13 @@
#define _SOCK_CLASS #define _SOCK_CLASS
#include <string> #include <string>
#include <sys/socket.h> #ifdef __unix__
#include <sys/socket.h>
#endif
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
/* Global constants - can be used by children classes as return values, and by any clients to check what type the socket is of */ /* Global constants - can be used by children classes as return values, and by any clients to check what type the socket is of */
const int SOCK_CLIENT = 'C'; const int SOCK_CLIENT = 'C';
@@ -28,14 +34,23 @@ public:
/* Default constructor */ /* Default constructor */
Sock() {} Sock() {}
/* Virtual destructor */
virtual ~Sock();
/* Regular constructor - defined in sock.cpp */ /* Regular constructor - defined in sock.cpp */
Sock(int ip_ver, char protocol, const char* address, int port); Sock(int ip_ver, char protocol, const char* address, int port);
/* Method to send data in 'to_send' through the 'other_socket' socket */ /* Method to send data in 'to_send' through the 'other_socket' socket */
void sendAll(std::string to_send); void sendAll(std::string to_send);
/* Same as method above, with buffer and buffer size */
void sendAll(char* buffer, int size);
/* Method to receive data sent to the 'other_socket' socket */ /* Method to receive data sent to the 'other_socket' socket */
std::string recvAll(); char* recvAll();
/* Non-blocking receive method - calls the method above after polling for data */
char* recvAllNB();
/* Returns socket identifier */ /* Returns socket identifier */
int getSockFD(); int getSockFD();

180
main.cpp
View File

@@ -1,3 +1,18 @@
#if defined(_WIN32)
#define NOGDI // All GDI defines and routines
#define NOUSER // All USER defines and routines
#define WIN32_LEAN_AND_MEAN
#include <windows.h> // or any library that uses Windows.h
#endif
#if defined(_WIN32) // raylib uses these names as function parameters
#undef near
#undef far
#endif
#include <iostream> #include <iostream>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
@@ -25,6 +40,7 @@ const float BASE_BOUNCE_DEG = 45;
const float BASE_BOUNCE_RAD = (BASE_BOUNCE_DEG / 180.0) * M_PI; const float BASE_BOUNCE_RAD = (BASE_BOUNCE_DEG / 180.0) * M_PI;
const float BASE_SPEED_COMPONENTS = 18; const float BASE_SPEED_COMPONENTS = 18;
const float BASE_SPEED = sqrt(powf(BASE_SPEED_COMPONENTS, 2) * 2); const float BASE_SPEED = sqrt(powf(BASE_SPEED_COMPONENTS, 2) * 2);
typedef enum {M_SINGLE, M_CLIENT, M_SERVER} Mode; typedef enum {M_SINGLE, M_CLIENT, M_SERVER} Mode;
/* This struct contains a Mode enum, which indicates the type of game we are /* This struct contains a Mode enum, which indicates the type of game we are
@@ -135,11 +151,13 @@ GameType check_server_client(int argc, char** argv) {
Server* server = new Server(4, ES_UDP, addr.data(), port); Server* server = new Server(4, ES_UDP, addr.data(), port);
server->create_socket(); server->create_socket();
std::cout << "Waiting for connection..." << std::endl; std::cout << "Waiting for connection..." << std::endl;
std::string response; std::string response = "";
/* Wait for the right client to connect */ 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. */
do { do {
response = server->recvAll(); temp_response = server->recvAll();
} while (response != "GG"); } while (temp_response == NULL);
response = std::string(temp_response);
std::cout << "Connection received from " << server->get_peer_addr() << std::endl; std::cout << "Connection received from " << server->get_peer_addr() << std::endl;
server->sendAll("U2"); server->sendAll("U2");
@@ -193,15 +211,15 @@ int main(int argc, char** argv) {
SetTraceLogLevel(LOG_NONE); SetTraceLogLevel(LOG_NONE);
raylib::Window window = raylib::Window(WIDTH, HEIGHT, "Pong"); raylib::Window window = raylib::Window(WIDTH, HEIGHT, "Pong");
window.ClearBackground(BLACK); window.ClearBackground(BLACK);
SetTargetFPS(5); SetTargetFPS(60);
SetExitKey(KEY_Q); SetExitKey(KEY_Q);
std::string points_str = std::string("0\t\t0"); std::string points_str = std::string("0\t\t0");
bool game_started = false; bool game_started = false;
srand(std::time(NULL)); srand(std::time(NULL));
/* Variables to store the response given by the other player */ /* Variable to store the response given by the other player */
std::string response; std::string response;
std::istringstream response_stream; Serial_Data response_data;
/* Vector to store peer paddle position */ /* Vector to store peer paddle position */
raylib::Vector2 peer_pos; raylib::Vector2 peer_pos;
@@ -228,7 +246,9 @@ int main(int argc, char** argv) {
if ((type.mode == M_SERVER || type.mode == M_SINGLE) && IsKeyDown(KEY_SPACE)) { if ((type.mode == M_SERVER || type.mode == M_SINGLE) && IsKeyDown(KEY_SPACE)) {
game_started = true; game_started = true;
/* Send a start message to the client */ /* Send a start message to the client */
type.netsock->sendAll("S"); if (type.mode == M_SERVER) {
type.netsock->sendAll("S");
}
} }
/* For client (wait for start message from server) */ /* For client (wait for start message from server) */
@@ -244,87 +264,87 @@ int main(int argc, char** argv) {
if (game_started) { if (game_started) {
/* Serialize the data that we need to send, and then send it to the peer paddle */ /* Serialize the data that we need to send, and then send it to the peer paddle */
if (type.mode == M_SERVER) { if (type.mode == M_SERVER) {
/* Serial_create_data creates a Serial_Data struct from our values, and Serial_serialize serializes it. /* Serial_create_data creates a Serial_Data struct from our values.
The 'sendAll' function accepts an std::string, so the byte array (uint8_t *) has to be cast to (char *), then converted to an std::string */ Paddle 2 is controlled by the server, Paddle 1, by the client.*/
to_send_data = Serial_create_data(pad1.getRect().x, pad1.getRect().y, ball.pos.x, ball.pos.y); to_send_data = Serial_create_data(pad2.getRect().x, pad2.getRect().y, ball.pos.x, ball.pos.y, false);
} }
if (type.mode == M_CLIENT) { else if (type.mode == M_CLIENT) {
/* The _server_ is the authoritative peer for the ball position, so the client sends (0, 0) as the ball position instead of actually sending a position */ /* The _server_ is the authoritative peer for the ball position, so the client sends (0, 0) as the ball position instead of actually sending a position */
to_send_data = Serial_create_data(pad2.getRect().x, pad2.getRect().y, 0, 0); to_send_data = Serial_create_data(pad1.getRect().x, pad1.getRect().y, 0, 0, false);
} }
to_send_string = std::string((char *)Serial_serialize(to_send_data)); /* Only send and receive data if the game is not in single player mode */
type.netsock->sendAll(to_send_string); if (type.mode != M_SINGLE) {
/* Serial_serialize serializes the struct into a byte_array. Since sendAll accepts a string, we have to convert this byte array into a string. */
/* Create a stream from the response of the server, and use that to create the vector of peer position */ type.netsock->sendAll((char *)Serial_serialize(to_send_data), sizeof(Serial_Data) + 1);
response = type.netsock->recvAll();
if (response[0] == 'P') { /* Create Serial_data struct from the response of the server. Since recvAll returns a char*, we need to convert it to a byte array */
/* Create a stream, excluding the first character */ uint8_t* response_array = (uint8_t *)(type.netsock->recvAll());
response_stream = std::istringstream(response.substr(1)); if (response_array != NULL) {
response_stream >> peer_pos.x >> peer_pos.y; response_data = Serial_deserialize(response_array);
std::cout << "X position of peer is: " << peer_pos.x << " Y position is: " << peer_pos.y << std::endl; std::cout << response_data.pad_x << "\t" << response_data.pad_y << "\t" << response_data.ball_x << "\t" << response_data.ball_y << std::endl;
} else {
/* 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;
}
} }
/* Since I have already received the data, I need to write code to handle it, in case it isn't 'P' */
/* When updating the paddle positions, update the peer paddle's positions based on the vector set earlier */ /* Check to see if peer has quit the game */
if (response_data.should_quit == true) {
std::string to_send = ""; std::cout << "Peer unexpectedly quit game." << std::endl;
/* Update paddle velocity */ break; // Break out of main game loop
}
/* Left paddle (controlled by client) */
/* Up motion */ /* Left paddle (controlled by client) - I use type.mode != M_SERVER, because I also want the single player
if (IsKeyPressed(KEY_S) || response == "D1") { mode to be able to control the paddle. Therefore, the only mode that _can't_ control the paddle is the server
mode. */
/* Down motion */
if (IsKeyPressed(KEY_S) && type.mode != M_SERVER) {
pad1.velocity.y = PADDLE_SPEED; /* Set positive (downward) velocity, since (0,0) is top-left */ pad1.velocity.y = PADDLE_SPEED; /* Set positive (downward) velocity, since (0,0) is top-left */
if (type.mode == M_CLIENT) { /* If this machine controls this paddle, Send the key press to the other machine */
to_send = "D1";
}
} }
/* Down motion */ /* Up motion */
if (IsKeyPressed(KEY_W) || response == "U1") { if (IsKeyPressed(KEY_W) && type.mode != M_SERVER) {
pad1.velocity.y = (-1) * PADDLE_SPEED; /* Set negative (upward) velocity */ pad1.velocity.y = (-1) * PADDLE_SPEED; /* Set negative (upward) velocity */
if (type.mode == M_CLIENT) {
to_send = "U1";
}
} }
/* Stop */ /* Stop */
if ((IsKeyReleased(KEY_S) || IsKeyReleased(KEY_W)) || (response == "S1")) { if (((IsKeyReleased(KEY_S) || IsKeyReleased(KEY_W))) && (type.mode != M_SERVER)) {
pad1.velocity.y = 0; pad1.velocity.y = 0;
if (type.mode == M_CLIENT) {
to_send = "S1";
}
} }
/* Right paddle - controlled by server*/
if (IsKeyPressed(KEY_DOWN)) { /* Right paddle - controlled by server - See above for why I used '!= M_CLIENT' instead of '== M_SERVER' */
if (type.mode == M_SERVER) { /* Down */
type.netsock->sendAll(std::string("D")); if (IsKeyPressed(KEY_DOWN) && type.mode != M_CLIENT) {
}
pad2.velocity.y = PADDLE_SPEED; pad2.velocity.y = PADDLE_SPEED;
} }
if (IsKeyPressed(KEY_UP)) {
if (type.mode == M_SERVER) { /* Up */
type.netsock->sendAll(std::string("U")); if (IsKeyPressed(KEY_UP) && type.mode != M_CLIENT) {
}
pad2.velocity.y = (-1) * PADDLE_SPEED; pad2.velocity.y = (-1) * PADDLE_SPEED;
} }
if (IsKeyReleased(KEY_UP) || IsKeyReleased(KEY_DOWN)) {
if (type.mode == M_SERVER) { /* Stop */
type.netsock->sendAll(std::string("S")); if ((IsKeyReleased(KEY_UP) || IsKeyReleased(KEY_DOWN)) && type.mode != M_CLIENT) {
}
pad2.velocity.y = 0; pad2.velocity.y = 0;
} }
/* Update ball velocity based on collision detection */ /* Why did I use 'type.mode != M_CLIENT'? - The client should set the ball position solely based
if (pad1.getRect().CheckCollision(ball.pos, ball.radius)) { /* Collision with paddle 1 */ on the data sent by the server. It doesn't have to do any calculations of its own. */
ball.pos.x = pad1.getRect().x + pad1.getRect().GetWidth() + ball.radius + 1; /* Ensuring that the ball doesn't get stuck inside the paddle */ if (type.mode != M_CLIENT) {
ball.vel = changeVelocityAfterCollision(pad1, ball); /* Update ball velocity based on collision detection */
if (pad1.getRect().CheckCollision(ball.pos, ball.radius)) { /* Collision with paddle 1 */
ball.pos.x = pad1.getRect().x + pad1.getRect().GetWidth() + ball.radius + 1; /* Ensuring that the ball doesn't get stuck inside the paddle */
ball.vel = changeVelocityAfterCollision(pad1, ball);
}
if (pad2.getRect().CheckCollision(ball.pos, ball.radius)) { /* Collision with paddle 2 */
ball.pos.x = pad2.getRect().x - ball.radius - 1;
ball.vel = changeVelocityAfterCollision(pad2, ball);
}
} else {
ball.setPosition(raylib::Vector2(response_data.ball_x, response_data.ball_y));
} }
if (pad2.getRect().CheckCollision(ball.pos, ball.radius)) { /* Collision with paddle 2 */
ball.pos.x = pad2.getRect().x - ball.radius - 1;
ball.vel = changeVelocityAfterCollision(pad2, ball);
}
if (ball.pos.x + ball.radius >= window.GetWidth()) { /* Collision with right wall */ if (ball.pos.x + ball.radius >= window.GetWidth()) { /* Collision with right wall */
pad1.incrementPoints(); pad1.incrementPoints();
game_started = false; game_started = false;
@@ -349,11 +369,19 @@ int main(int argc, char** argv) {
ball.vel.y = ball.vel.y * -1; ball.vel.y = ball.vel.y * -1;
} }
/* Update positions based on velocities */ /* Update positions based on velocities - Client only updates pad1 (and receives data for pad2),
pad1.updatePosition(); server updates pad2 and ball (and receives data for pad1) */
pad2.updatePosition(); if (type.mode != M_CLIENT) {
ball.updatePosition(); ball.updatePosition();
pad2.updatePosition();
} else {
pad2.setPosition(response_data.pad_x, response_data.pad_y);
}
if (type.mode != M_SERVER) {
pad1.updatePosition();
} else {
pad1.setPosition(response_data.pad_x, response_data.pad_y);
}
} }
/* Draw objects */ /* Draw objects */
@@ -366,9 +394,15 @@ int main(int argc, char** argv) {
ball.draw(); ball.draw();
window.EndDrawing(); window.EndDrawing();
} }
/* If the game has been quit, ask the peer to quit as well */
if (type.mode != M_SINGLE) {
to_send_data = Serial_create_data(0, 0, 0, 0, true);
type.netsock->sendAll((char *)Serial_serialize(to_send_data), sizeof(Serial_Data) + 1);
sock_quit();
}
window.Close(); window.Close();
sock_quit();
return 0; return 0;
} }

View File

@@ -1,8 +1,36 @@
project('Pong', ['cpp', 'c']) project('Pong', ['cpp', 'c'], version: '0.1')
add_global_arguments('-g', '-Wall', '-pedantic', '-Wno-unused-function', '-Werror', language : ['cpp', 'c']) add_global_arguments('-g', '-Wall', '-pedantic', '-Wno-unused-function', language : ['cpp', 'c'])
raylib = dependency('raylib') compiler = meson.get_compiler('cpp')
cmake = import('cmake')
if get_option('default_library') == 'shared'
raylib = dependency('raylib', required: false) # Try to find dependency with pkg-config
if not raylib.found()
raylib = compiler.find_library('raylib', has_headers: ['raylib.h', 'raymath.h'], required: true) # Try to manually search for the dependency
endif
# if not raylib.found()
# opt_var = cmake.subproject_options()
# opt_var.add_cmake_defines({'BUILD_SHARED_LIBS' : true})
# opt_var.add_cmake_defines({'CMAKE_SKIP_RPATH' : true})
# raylib_proj = cmake.subproject('raylib', options: opt_var)
# raylib = raylib_proj.dependency('raylib')
# endif
endif
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
endif
executable('pong', executable('pong',
'main.cpp', 'easysock.cpp', 'sock.cpp','paddle.cpp', 'ball.cpp', 'numeric_base.cpp', 'connect_code.cpp', 'server.cpp', 'client.cpp', 'main.cpp', 'easysock.cpp', 'sock.cpp','paddle.cpp', 'ball.cpp', 'numeric_base.cpp', 'connect_code.cpp', 'server.cpp', 'client.cpp',
'serialization.c', 'serialization.c',
dependencies: raylib, dependencies: [raylib, ws2_dep, winmm]
) )

View File

@@ -5,10 +5,10 @@
std::string possible_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; std::string possible_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int to_decimal(std::string num, int from_base) { unsigned int to_decimal(std::string num, int from_base) {
char current_char = 0; char current_char = 0;
int index = 0; int index = 0;
int value = 0; unsigned int value = 0;
/* Here, we convert 'num' to decimal (base 10) - Find the index of /* Here, we convert 'num' to decimal (base 10) - Find the index of
every character in the string, in 'possible_chars' and every character in the string, in 'possible_chars' and
@@ -23,7 +23,7 @@ int to_decimal(std::string num, int from_base) {
} }
/* Convert the given value from base 10 to the given base */ /* Convert the given value from base 10 to the given base */
std::string from_decimal(int num, int to_base) { std::string from_decimal(unsigned int num, int to_base) {
std::string return_val; std::string return_val;
int val = 0; int val = 0;
while (num > 0) { while (num > 0) {
@@ -40,7 +40,7 @@ std::string from_decimal(int num, int to_base) {
/* Convert the given value from 'from_base', to 'to_base' */ /* Convert the given value from 'from_base', to 'to_base' */
std::string base_convert(std::string num, int from_base, int to_base) { std::string base_convert(std::string num, int from_base, int to_base) {
int temp = to_decimal(num, from_base); unsigned int temp = to_decimal(num, from_base);
std::string result = from_decimal(temp, to_base); std::string result = from_decimal(temp, to_base);
return result; return result;

View File

@@ -22,6 +22,12 @@ void Paddle::incrementPoints() {
return; return;
} }
void Paddle::setPosition(int x, int y) {
this->rectangle.x = x;
this->rectangle.y = y;
return;
}
void Paddle::reset() { void Paddle::reset() {
this->rectangle.x = this->initial_pos.x; this->rectangle.x = this->initial_pos.x;
this->rectangle.y = this->initial_pos.y; this->rectangle.y = this->initial_pos.y;

View File

@@ -1,32 +1,63 @@
#include <stdint.h> #include <stdint.h>
#include <stdlib.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" #include "includes/serialization.h"
/* Takes in float values, casts them to uint16_t and creates a Serial_Data struct */ /* 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) { Serial_Data Serial_create_data(float pad_x, float pad_y, float ball_x, float ball_y, bool should_quit) {
Serial_Data data; Serial_Data data;
data.pad_x = (uint16_t)pad_x; data.pad_x = (uint16_t)pad_x;
data.pad_y = (uint16_t)pad_y; data.pad_y = (uint16_t)pad_y;
data.ball_x = (uint16_t)ball_x; data.ball_x = (uint16_t)ball_x;
data.ball_y = (uint16_t)ball_y; data.ball_y = (uint16_t)ball_y;
data.should_quit = should_quit;
return data; return data;
} }
/* Serializes a 'Data' struct into a byte array */ /* Serializes a 'Data' struct into a byte array, converted to network-byte order */
uint8_t* Serial_serialize(Serial_Data data) { uint8_t* Serial_serialize(Serial_Data data) {
/* Create a pointer that can fit the entire struct */ /* Create a pointer that can fit the entire struct */
uint8_t* serialized = malloc(sizeof(Serial_Data)); uint8_t* serialized = malloc(sizeof(Serial_Data) + 1);
/* Store the data into the pointer, by using an incremented memory address for each successive store */ uint8_t* pad_x_ptr;
*serialized = data.pad_x; uint8_t* pad_y_ptr;
*(serialized + sizeof(uint16_t)) = data.pad_y; uint8_t* ball_x_ptr;
*(serialized + 2 * sizeof(uint16_t)) = data.ball_x; uint8_t* ball_y_ptr;
*(serialized + 3 * sizeof(uint16_t)) = data.ball_y; 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; return serialized;
} }
/* Deserialize a byte array into a 'Data' struct */ /* Deserialize a byte array into a 'Data' struct, converted to host byte order */
Serial_Data Serial_deserialize(uint8_t* serialized) { Serial_Data Serial_deserialize(uint8_t* serialized) {
Serial_Data deserialized; Serial_Data deserialized;
/* Use successive chunks of memory address to create pointers to the data */ /* Use successive chunks of memory address to create pointers to the data */
@@ -34,12 +65,22 @@ Serial_Data Serial_deserialize(uint8_t* serialized) {
uint8_t* pad_y_ptr = serialized + sizeof(uint16_t); uint8_t* pad_y_ptr = serialized + sizeof(uint16_t);
uint8_t* ball_x_ptr = pad_y_ptr + 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* 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 */ /* Dereference (and cast) the pointers, and store them into the struct */
deserialized.pad_x = *((uint16_t *)pad_x_ptr); 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 = *((uint16_t *)pad_y_ptr);
deserialized.pad_y = ntohs(deserialized.pad_y);
deserialized.ball_x = *((uint16_t *)ball_x_ptr); 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 = *((uint16_t *)ball_y_ptr);
deserialized.ball_y = ntohs(deserialized.ball_y);
deserialized.should_quit = *((bool *)should_quit_ptr);
return deserialized; return deserialized;
} }

View File

@@ -1,4 +1,9 @@
#include <sys/socket.h> #ifdef __unix__
#include <sys/socket.h>
#endif
#ifdef _WIN32
#include <winsock2.h>
#endif
#include <fcntl.h> #include <fcntl.h>
#include "includes/sock.hpp" #include "includes/sock.hpp"
#include "includes/server.hpp" #include "includes/server.hpp"
@@ -15,7 +20,6 @@ Server::~Server() {
/* Sends given data through the peer socket - This method is overriden from the /* Sends given data through the peer socket - This method is overriden from the
base method, because a different socket must be used. In the server's case, the base method, because a different socket must be used. In the server's case, the
'peer' socket i.e. the socket returned after calling 'accept', must be used. */ 'peer' socket i.e. the socket returned after calling 'accept', must be used. */
void Server::sendAll(std::string to_send) { void Server::sendAll(std::string to_send) {
Sock::sendAll(to_send); Sock::sendAll(to_send);
} }
@@ -28,13 +32,36 @@ of a peer socket, and so the regular server socket (the one created in create_so
is used instead. This function also sets the 'peer_addr' string to the address of the is used instead. This function also sets the 'peer_addr' string to the address of the
peer socket, handling both TCP and UDP. */ peer socket, handling both TCP and UDP. */
std::string Server::recvAll() { char* Server::recvAll() {
if (this->protocol == ES_UDP) { if (this->protocol == ES_UDP) {
this->other_socket = this->sock_fd; this->other_socket = this->sock_fd;
} }
/* Call receive method of parent */ /* Call receive method of parent */
std::string to_return = Sock::recvAll(); char* to_return = Sock::recvAll();
/* Set the peer address of the socket */
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 */
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";
}
return to_return;
}
/* Same as function above, but calls Sock::recvAllNB() instead */
char* Server::recvAllNB() {
if (this->protocol == ES_UDP) {
this->other_socket = this->sock_fd;
}
/* Call receive method of parent */
char* to_return = Sock::recvAllNB();
/* Set the peer address of the socket */ /* Set the peer address of the socket */
if (this->ip_ver == 4) { if (this->ip_ver == 4) {
@@ -69,7 +96,8 @@ called immediately after the constructor. If the socket is TCP, it also sets the
socket to listen for incoming connections. This function throws an exception if socket to listen for incoming connections. This function throws an exception if
the socket could not be created. The excpetion is an integer corresponding to the errno the socket could not be created. The excpetion is an integer corresponding to the errno
of the failing function, and enables the caller to print a corresponding error message by of the failing function, and enables the caller to print a corresponding error message by
'catching' the thrown exception and using strerror(). */ 'catching' the thrown exception and using strerror().
This function also sets a timeout of 100ms for UDP sockets */
void Server::create_socket() { void Server::create_socket() {
Sock::create_socket(); Sock::create_socket();
@@ -80,6 +108,12 @@ void Server::create_socket() {
if (protocol == ES_TCP) { if (protocol == ES_TCP) {
listen(sock_fd, 10); listen(sock_fd, 10);
} }
// if (protocol == ES_UDP) {
// struct timeval tv;
// tv.tv_sec = 0;
// tv.tv_usec = 10000;
// setsockopt(this->sock_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// }
} }
/* Returns the address of the peer socket as a string, can be used for debugging */ /* Returns the address of the peer socket as a string, can be used for debugging */

View File

@@ -13,6 +13,10 @@ void Sock::create_socket() {
addrlen = sizeof(*dest); addrlen = sizeof(*dest);
} }
/* Virtual destructor, allows 'Server' and 'Client' to override this destructor */
Sock::~Sock() {}
/* Constructor - This function initializes the object attributes with the given /* Constructor - This function initializes the object attributes with the given
parameters. It throws an exception if an IPv4 address was given, but the type parameters. It throws an exception if an IPv4 address was given, but the type
given is IPv6 (or the other way around). */ given is IPv6 (or the other way around). */
@@ -64,30 +68,39 @@ void Sock::sendAll(std::string to_send) {
total_bytes_sent += num_bytes_sent; total_bytes_sent += num_bytes_sent;
} }
} }
return; return;
} }
/* Receives data from 'other_socket' into a string, and returns that string. For TCP, the /* This method receives a (char *) and a size, and creates a std::string with it.
'recv' method is called until all the data has been read. For UDP, the 'recvfrom' It then calls the method above, passing that string as a parameter. */
method is only called once. void Sock::sendAll(char* buffer, int size) {
This function also needs more testing for TCP. */ std::string to_send = std::string(buffer, size);
sendAll(to_send);
}
std::string Sock::recvAll() { /* Receives data from 'other_socket' into a char *, and returns that char *. For TCP, the
int num_bytes_received; 'recv' method is called until all the data has been read. For UDP, the 'recvfrom'
std::string string = std::string(); method is only called once. The 'select' function is used to poll data for UDP
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(100);
bool has_been_read = false; bool has_been_read = false;
if (this->protocol == ES_UDP) { if (this->protocol == ES_UDP) {
num_bytes_received = recvfrom(this->sock_fd, buffer, 100, 0, dest, &addrlen); num_bytes_received = recvfrom(this->sock_fd, buffer, 99, 0, dest, &addrlen);
if (num_bytes_received == 0) {
return NULL;
}
/* Null-terminate the string */ /* Null-terminate the string */
*(buffer + num_bytes_received) = '\0'; *(buffer + num_bytes_received) = '\0';
string.append(std::string(buffer)); return buffer;
} }
/* For TCP sockets */ /* For TCP sockets */
else { else {
while ((num_bytes_received = recv(this->other_socket, buffer, 100, 0)) != 0) { while ((num_bytes_received = recv(this->other_socket, buffer + total_bytes_received, 100 - total_bytes_received, 0)) != 0) {
if ((errno == EAGAIN || errno == EWOULDBLOCK)) { if ((errno == EAGAIN || errno == EWOULDBLOCK)) {
if (has_been_read) { if (has_been_read) {
@@ -100,13 +113,31 @@ std::string Sock::recvAll() {
if (num_bytes_received < 0) { if (num_bytes_received < 0) {
throw errno * -1; throw errno * -1;
} }
string.append(std::string(buffer)); total_bytes_received += num_bytes_received;
has_been_read = true; has_been_read = true;
} }
} }
return string; return buffer;
}
/* Non-blocking recv call - Uses 'select' to poll for data from the FD. */
char* Sock::recvAllNB() {
struct timeval tv;
fd_set readfs;
tv.tv_sec = 0; // Set to 0 to poll instead of wait
tv.tv_usec = 0; // Set to 0 to poll instead of wait
FD_ZERO(&readfs);
FD_SET(this->sock_fd, &readfs);
select(this->sock_fd + 1, &readfs, NULL, NULL, &tv);
if (FD_ISSET(this->sock_fd, &readfs)) {
return Sock::recvAll();
} else {
return NULL;
}
} }
int Sock::getSockFD() { int Sock::getSockFD() {

1
subprojects/raylib Submodule

Submodule subprojects/raylib added at c7b362d19d