Compare commits
35 Commits
6d1565a020
...
v0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| f4c3ef9b19 | |||
| bb4601c5bd | |||
| 45aa6ba4bc | |||
| c3f6ae0ae9 | |||
| 21c864da60 | |||
| a2fed8e4b0 | |||
| 1196ebd228 | |||
| 3a9a32d7e3 | |||
| a5202ad85a | |||
| 61a856e88f | |||
| 6170c95666 | |||
| d6f597d8c0 | |||
| 591c3b16a2 | |||
| 2ea5bb4fe2 | |||
| f961db5e58 | |||
| 64aa4b1850 | |||
| 0e504060cf | |||
| d69b627bb1 | |||
| 91bf5e2ce1 | |||
| 50c090cd88 | |||
| 2c735896df | |||
| 24c1dd6391 | |||
| 8e3488b904 | |||
| c2c095dfa7 | |||
| 047ff602ed | |||
| ff8dc1f1f7 | |||
| 4b3d5387a1 | |||
| ddbbc322a6 | |||
| b90d37c73f | |||
| 69e70eb206 | |||
| 349b0b78db | |||
| a37ec79f09 | |||
| ba667d020d | |||
| effeea73b9 | |||
| c6bbe82d25 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
main.o
|
main.o
|
||||||
easysock.o
|
easysock.o
|
||||||
pong
|
pong
|
||||||
|
release/
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "subprojects/raylib"]
|
||||||
|
path = subprojects/raylib
|
||||||
|
url = https://github.com/raysan5/raylib.git
|
||||||
11
Makefile
11
Makefile
@@ -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
32
README.md
Normal 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.
|
||||||
16
client.cpp
16
client.cpp
@@ -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;
|
||||||
|
|||||||
@@ -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
21
create_release_build.sh
Normal 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"
|
||||||
|
|
||||||
23
easysock.cpp
23
easysock.cpp
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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). */
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ public:
|
|||||||
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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
156
main.cpp
156
main.cpp
@@ -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,8 +246,10 @@ 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 */
|
||||||
|
if (type.mode == M_SERVER) {
|
||||||
type.netsock->sendAll("S");
|
type.netsock->sendAll("S");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* For client (wait for start message from server) */
|
/* For client (wait for start message from server) */
|
||||||
if (type.mode == M_CLIENT) {
|
if (type.mode == M_CLIENT) {
|
||||||
@@ -244,77 +264,74 @@ 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. */
|
||||||
|
type.netsock->sendAll((char *)Serial_serialize(to_send_data), sizeof(Serial_Data) + 1);
|
||||||
|
|
||||||
/* Create a stream from the response of the server, and use that to create the vector of peer position */
|
/* Create Serial_data struct from the response of the server. Since recvAll returns a char*, we need to convert it to a byte array */
|
||||||
response = type.netsock->recvAll();
|
uint8_t* response_array = (uint8_t *)(type.netsock->recvAll());
|
||||||
if (response[0] == 'P') {
|
if (response_array != NULL) {
|
||||||
/* Create a stream, excluding the first character */
|
response_data = Serial_deserialize(response_array);
|
||||||
response_stream = std::istringstream(response.substr(1));
|
std::cout << response_data.pad_x << "\t" << response_data.pad_y << "\t" << response_data.ball_x << "\t" << response_data.ball_y << std::endl;
|
||||||
response_stream >> peer_pos.x >> peer_pos.y;
|
} else {
|
||||||
std::cout << "X position of peer is: " << peer_pos.x << " Y position is: " << peer_pos.y << std::endl;
|
/* 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 */
|
|
||||||
|
|
||||||
std::string to_send = "";
|
|
||||||
/* Update paddle velocity */
|
|
||||||
|
|
||||||
/* Left paddle (controlled by client) */
|
|
||||||
/* Up motion */
|
|
||||||
if (IsKeyPressed(KEY_S) || response == "D1") {
|
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Check to see if peer has quit the game */
|
||||||
|
if (response_data.should_quit == true) {
|
||||||
|
std::cout << "Peer unexpectedly quit game." << std::endl;
|
||||||
|
break; // Break out of main game loop
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Left paddle (controlled by client) - I use type.mode != M_SERVER, because I also want the single player
|
||||||
|
mode to be able to control the paddle. Therefore, the only mode that _can't_ control the paddle is the server
|
||||||
|
mode. */
|
||||||
/* Down motion */
|
/* Down motion */
|
||||||
if (IsKeyPressed(KEY_W) || response == "U1") {
|
if (IsKeyPressed(KEY_S) && type.mode != M_SERVER) {
|
||||||
pad1.velocity.y = (-1) * PADDLE_SPEED; /* Set negative (upward) velocity */
|
pad1.velocity.y = PADDLE_SPEED; /* Set positive (downward) velocity, since (0,0) is top-left */
|
||||||
if (type.mode == M_CLIENT) {
|
|
||||||
to_send = "U1";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Up motion */
|
||||||
|
if (IsKeyPressed(KEY_W) && type.mode != M_SERVER) {
|
||||||
|
pad1.velocity.y = (-1) * PADDLE_SPEED; /* Set negative (upward) velocity */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Why did I use 'type.mode != M_CLIENT'? - The client should set the ball position solely based
|
||||||
|
on the data sent by the server. It doesn't have to do any calculations of its own. */
|
||||||
|
if (type.mode != M_CLIENT) {
|
||||||
/* Update ball velocity based on collision detection */
|
/* Update ball velocity based on collision detection */
|
||||||
if (pad1.getRect().CheckCollision(ball.pos, ball.radius)) { /* Collision with paddle 1 */
|
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.pos.x = pad1.getRect().x + pad1.getRect().GetWidth() + ball.radius + 1; /* Ensuring that the ball doesn't get stuck inside the paddle */
|
||||||
@@ -324,6 +341,9 @@ int main(int argc, char** argv) {
|
|||||||
ball.pos.x = pad2.getRect().x - ball.radius - 1;
|
ball.pos.x = pad2.getRect().x - ball.radius - 1;
|
||||||
ball.vel = changeVelocityAfterCollision(pad2, ball);
|
ball.vel = changeVelocityAfterCollision(pad2, ball);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
ball.setPosition(raylib::Vector2(response_data.ball_x, response_data.ball_y));
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
@@ -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 */
|
||||||
@@ -367,8 +395,14 @@ int main(int argc, char** argv) {
|
|||||||
window.EndDrawing();
|
window.EndDrawing();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Close();
|
/* 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();
|
sock_quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Close();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
36
meson.build
36
meson.build
@@ -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]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
44
server.cpp
44
server.cpp
@@ -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 */
|
||||||
|
|||||||
55
sock.cpp
55
sock.cpp
@@ -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). */
|
||||||
@@ -68,26 +72,35 @@ void Sock::sendAll(std::string to_send) {
|
|||||||
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
1
subprojects/raylib
Submodule
Submodule subprojects/raylib added at c7b362d19d
Reference in New Issue
Block a user