98 Commits
v0.1 ... v0.2

Author SHA1 Message Date
f42ac94a45 Added check to release script, to check if DLL exists 2024-03-19 09:02:48 -05:00
4ff840e91e Fixed boneheaded mistake, where I set REUSEADDR for UDP instead of TCP 2024-03-18 16:03:19 -04:00
fd4ad04aeb Removed unnecessary comment 2024-03-18 16:02:57 -04:00
d842485103 Throw errno instead of errno * -1, if an error is encountered with sending or receiving 2024-03-18 13:43:09 -04:00
3bf65ab8f9 Updated TODO 2024-03-18 13:27:14 -04:00
f3dcbc3b3e Removed check_server_client() function.
It has been replaced by check_server() and check_client().
2024-03-18 13:25:51 -04:00
463dfbd3e5 IPv6 support for RecvAllNB() 2024-03-18 13:22:26 -04:00
d3716536f9 Minor changes 2024-03-18 13:22:12 -04:00
8805402241 Return after catching exception 2024-03-18 13:22:01 -04:00
3d0aeac943 Ensure that REUSEADDR flag is only applied for TCP 2024-03-18 13:21:42 -04:00
c490eaa301 Updated TODO 2024-03-17 23:35:05 -04:00
0e7ebb4d78 Finished integrating check_server() and check_client(), check_server_client() has been commented out. 2024-03-17 23:34:24 -04:00
c94138ad8b Included new file in compilation process 2024-03-17 23:32:56 -04:00
cfbc726dca Renamed functions to make their purpose more explicit 2024-03-17 23:32:37 -04:00
ec2f3320e3 Replaced display_text_centered() with the environment-agnostic display_text() function; Changed function to include parameter to indicate environment type 2024-03-17 23:31:56 -04:00
43ba4aba0c Created a file that contains functions for agnostic text output 2024-03-17 18:09:07 -04:00
c2bedb0601 Wrote the check_num_args() function; still need to integrate it into the program. Also wrote a help text that is printed with -h flag. 2024-03-17 00:30:25 -04:00
77a147e08f Updated TODO 2024-03-17 00:27:35 -04:00
26999a1145 Started working on function to check the number of command-line arguments 2024-03-16 10:49:21 -05:00
f41c3d22e2 Updated TODO 2024-03-16 10:24:59 -05:00
9f1f313091 Cast sockaddr to sockaddr_storage 2024-03-13 15:14:38 -05:00
8401f74922 Define WIN32_LEAN_AND_MEAN to avoid including windows.h, when including winsock2.h 2024-03-13 15:10:16 -05:00
d2dd95b7cc Replaced global_args with project_args to prevent build error on MinGW 2024-03-13 15:09:48 -05:00
5cf11ac014 Added stdint header file 2024-03-13 14:36:24 -05:00
0dbf8936fd Added a #define for math constants on MinGW 2024-03-13 14:36:10 -05:00
3ab97b3853 Compile the application if it isn't already compiled 2024-03-13 13:00:28 -05:00
aea8f3dfd2 Updated TODO 2024-03-12 10:05:39 -05:00
94e08f3863 Used blocking recv here, to ensure that we wait for the server to respond 2024-03-12 10:02:53 -05:00
a847da5339 Added support for printing out the peer's IPv6 address; replaced struct sockaddr with struct sockaddr_storage 2024-03-12 10:02:23 -05:00
550643281e Replace all instances of sockaddr with sockaddr_storage 2024-03-12 09:44:41 -05:00
ef869710e5 Replaced recvAll call with recvAllNB 2024-03-12 09:44:15 -05:00
00d20ebc88 Used the non-blocking recv function, to ensure that the game doesn't hang if the server doesn't respond. 2024-03-12 09:43:48 -05:00
839efc3c44 Replaced all instances of struct sockaddr with struct sockaddr_storage, since it can fit v6 addresses as well. Cast values accordingly. 2024-03-12 09:43:16 -05:00
0a1934fdf9 Updated TODO 2024-03-12 09:42:32 -05:00
24b2a83044 Used a sockaddr_storage struct instead of a sockaddr struct, since the latter is not big enough for IPv6 2024-03-12 00:25:41 -05:00
7d4fd929c7 Updated TODO 2024-03-12 00:25:11 -05:00
54f7dbe7ee Modified recvAllNB() to return an empty string (instead of NULL) if there is nothing to read 2024-03-12 00:13:56 -05:00
06f44d385d Allow user to quit in menu screen; better error handling.
I set up a try-catch to catch the exception thrown by the Server/Client when
it can't create a socket. I also used display_and_exit() to
automatically close the window after the text has been displayed.
2024-03-11 13:45:05 -05:00
6f292699f8 Updated header file to reflect new function 2024-03-11 13:19:07 -05:00
6a40a596c1 Added new function to display text then exit 2024-03-11 13:18:48 -05:00
fc59a7221b Removed unnecessary #include 2024-03-11 13:18:31 -05:00
7f0898c81e Updated TODO 2024-03-11 13:17:18 -05:00
4001135451 Integrated upstream changes, since I forgot to pull before making local changes. 2024-03-11 01:32:56 -05:00
66d7585297 Removed IP version checking code (since this is handled in the Sock constructor), and allowed server (but not client, yet) to quit game before round start 2024-03-11 01:29:56 -05:00
727aeafdb9 Updated Server and Client constructor calls 2024-03-11 01:28:35 -05:00
3bdfdb114c Updated TODO 2024-03-11 01:28:04 -05:00
f840ff9c00 Updated comment explaining function 2024-03-10 21:57:58 -05:00
986e386098 Updated Server and Client constructor calls, so that they don't pass in the IP version 2024-03-10 21:56:03 -05:00
53282727ec Removed testing code 2024-03-10 21:55:09 -05:00
d43dc41f25 Updated TODO 2024-03-10 21:54:47 -05:00
0058e7e411 Removed ip_ver parameter
I removed this because I realized I could just check the IP version inside
the constructor. The Sock constructor now checks the address passed to it.
Like before, if the address is neither v4 nor v6, an exception is thrown.
Since the Server and Client constructors call the Sock constructor, no change
was required in these files, except passing the right number of parameters.
2024-03-10 21:53:06 -05:00
764f343f5d Updated TODO 2024-03-10 19:27:46 -05:00
cdd1db6808 Implemented IPv6 address decoding algorithm 2024-03-10 19:27:17 -05:00
ae044c1905 Convert character to upper-case before converting to decimal 2024-03-10 19:26:39 -05:00
8011c5e8b9 Finished script to create and package statically linked binary on Linux 2024-03-10 16:13:45 -05:00
24eda2d16a Updated TODO 2024-03-10 16:13:26 -05:00
418579a627 Added C++ standard as a compiler flag, which is apparently needed by compilers on macOS 2024-03-10 01:09:31 -06:00
6acbf90d80 Updated UNIX macro checks, to account for MacOS 2024-03-10 00:51:20 -06:00
2c7d1d0b43 Started working on client-side decoding of IPv6 code 2024-03-09 21:34:47 -05:00
00b83e6de2 Added bash flag to fail if any command in the script fails 2024-03-09 21:34:09 -05:00
9881567009 Created a script to build statically-linked binaries on Linux 2024-03-09 21:33:49 -05:00
f23f307e17 Updated TODO 2024-03-09 21:33:22 -05:00
dd658c9c1d WROTE IN AIRPLANE: Checked edge case where the number is zero 2024-03-09 21:05:38 -05:00
52f8034f4e Rudimentary support for IPv6 in server socket creation
I haven't completely implemented it yet, but I did come up with a basic algorithm
to convert the IPv6 address into a 'code' form. I still have to write the code to
actually create the socket, though.
2024-03-09 19:32:45 -05:00
8758060bfb Updated TODO 2024-03-09 19:32:03 -05:00
10f91fafd4 Updated README 2024-03-09 19:31:51 -05:00
e9405b69e7 Updated README 2024-03-09 12:46:52 -05:00
9a12edcdb1 Started working on better error handling, by throwing exceptions and displaying error messages in the GUI 2024-03-09 11:05:04 -05:00
83a0d5beb4 Updated files to reflect change in easysock (from CPP to C) 2024-03-09 11:04:17 -05:00
eeae444b1d Moved display_text_centered() into a separate file, since I could possibly extend this file with other raygui helper functions 2024-03-09 11:03:27 -05:00
f9d5e8cdeb Converted easysock from CPP to C, because it was mostly just C code anyways 2024-03-09 11:02:50 -05:00
9972e146d5 Updated TODO 2024-03-08 23:45:48 -05:00
8848f0ff8c Removed debug print statements, cleared background before drawing to screen, and added client GUI implementation
I removed a print statement that printed out every position of the ball, because it was no longer necessary.
I also added code to clear the background before drawing to the screen at the start of the game, to remove
any lingering un-erased objects. Finally, and most substantially, I finished the initial implementation of
the client-side GUI. The client should now be able to specify a connect code through the GUI, and connect
to the appropriate server.
2024-03-08 23:35:18 -05:00
07ac3f9166 Filled out implementation of check_client function 2024-03-08 23:34:49 -05:00
9e0990156e Removed unnecessary print message, and added timer.c to source files 2024-03-08 23:33:54 -05:00
1423cc19a0 Split timer into header and implementation file 2024-03-08 23:33:24 -05:00
352d3f26f1 Moved struct definition to separate file, and added check for displaying GUI
I moved the GameType struct (and the Mode enum) to a separate file, as I will need
to use it in the check_server and check_client functions as well. I also added the
signum function (which was previously in sign.hpp) to this file, since it was the only
function in sign.hpp. Finally, I added a check, that will only display the GUI, if the
user didn't provide any command-line arguments.
2024-03-08 14:46:30 -05:00
788b334e7c Removed sign file, since it only contained one function. This function has been moved to main.cpp 2024-03-08 14:46:26 -05:00
9de9353936 Added include guards to timer header file 2024-03-08 14:45:18 -05:00
a3392308c4 Updated TODO 2024-03-08 14:44:53 -05:00
0e9088beb6 Added comments, and added a way to use the bundled raylib, even if we are building a dynamically linked version 2024-03-08 14:44:17 -05:00
7812611fe6 Created an implementation and header file to check the user input, if it is entered through the GUI 2024-03-08 14:43:45 -05:00
bc0d644399 Replaced compound literal initialization of 'Rectangle' and 'Vector2' types (which is only valid in C), with braced-initialization (valid in C++) 2024-03-08 08:20:52 -05:00
d05ba0daa1 Updated TODO 2024-03-07 23:26:01 -05:00
c8f29d1336 Updated TODO 2024-03-07 23:24:47 -05:00
0d1dc049b5 Worked on further implementation of game mode selection.
I added code to display a help text after the user selects a mode. Currently,
this text is only displayed in single player mode. Additionally, I added a rudimentary
'form' to input IP address and port, if the user selects server mode.
2024-03-07 23:19:03 -05:00
f4bbb6ef6a Added a rudimentary timer implementation 2024-03-07 23:18:59 -05:00
1f470e23ee Updated TODO 2024-03-07 18:15:34 -05:00
97e6da3b2b Updated TODO 2024-03-07 18:11:15 -05:00
0286878c70 Rudimentary support for inputting game mode through GUI instead of command-line
The only thing that 'works' right now is the skeleton GUI structure. The buttons don't
actually do anything.
2024-03-07 18:10:14 -05:00
e9da48d9a0 Added raygui header file, and dark mode header file 2024-03-07 18:09:27 -05:00
832dae977a Updated TODO 2024-03-07 18:08:42 -05:00
613b81c542 Added compiler flag to ignore warnings related to narrowing conversions
I know it's bad practice, but I didn't see any other alternative, as this seemed to be an
issue with raygui.
2024-03-07 18:07:18 -05:00
28c4b421d2 Made changes to script
I renamed the file to reflect that it is only for MinGW (I plan to create
similar scripts for other operating systems), and added code to zip the release folder,
inside the shell script.
2024-03-07 07:49:39 -05:00
13ce75067b Updated TODO 2024-03-07 07:48:28 -05:00
635d71f1c5 Slightly lowered speed, to make the game a little easier to play 2024-03-07 07:46:38 -05:00
8c4a515046 Updated TODO 2024-03-06 23:30:28 -05:00
f25e7fae38 Updated TODO 2024-03-06 21:42:04 -05:00
29 changed files with 6922 additions and 271 deletions

View File

@@ -4,16 +4,21 @@ __Netpong__ is a network-enabled Pong game, written in C++. It enables two playe
## 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.
The game has only one runtime dependency: The [raylib](https://www.raylib.com/) graphics library. 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.
## 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:
1. Install meson from the link above.
2. Set up the build directory.
```
meson setup build
```
3. Compile the application. Meson should use a system installation of raylib, if it exists. If not, it falls back to a bundled version.
```
meson compile -C build
```
4. You can also create a statically-linked version of the game (with no runtime dependencies) on Linux by running the following commands:
```
meson configure -Ddefault_library=static build/
@@ -30,3 +35,7 @@ This application uses [Meson](https://mesonbuild.com/) as a build system. To bui
- 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.
## TODO
See todo.txt.

83
check_input.cpp Normal file
View File

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

View File

@@ -1,8 +1,7 @@
#include <fcntl.h>
#include "includes/client.hpp"
#include "includes/exception_consts.hpp"
#include "includes/sock.hpp"
#include "includes/easysock.hpp"
#include "includes/easysock.h"
/* Destructor - closes any open sockets */
Client::~Client() {

View File

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

View File

@@ -1,21 +0,0 @@
#!/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"

35
create_release_mingw.sh Normal file
View File

@@ -0,0 +1,35 @@
#!/bin/bash
# This script copies required DLLs, and the application itself into a folder called 'release'. It only runs on MinGW.
set -o errexit # Stop executing when a command fails
BASE_DIR=$(dirname $0)
REL_DIR="$BASE_DIR/release/dist"
RAYLIB_DLL="$BASE_DIR/build/subprojects/raylib/libraylib.dll"
mkdir -p "$REL_DIR"
# Set up the build directory
meson setup build/
# Build the application
meson compile -C build/
# Parse the output of the 'ldd' command (using only DLLs that are found) and create a file with the required DLL paths.
ldd build/pong.exe | awk ' NF == 4 {print $3}' | grep -i "dll" > "$BASE_DIR/tmp_file.txt"
# Copy the required DLLs.
cp $(cat "$BASE_DIR/tmp_file.txt") "$REL_DIR"
# Copy the raylib DLL, if it does not exist in the directory
cp -n "$RAYLIB_DLL" "$REL_DIR"
# Copy the executable itself
cp "$BASE_DIR/build/pong" "$REL_DIR"
# Remove the temporary file.
rm "$BASE_DIR/tmp_file.txt"
#Zip the $REL_DIR folder
zip -r "$BASE_DIR/release/netpong-win.zip" "$REL_DIR"

26
create_static_linux.sh Executable file
View File

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

13
display_text.cpp Normal file
View File

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

View File

@@ -1,8 +1,9 @@
#include "includes/easysock.hpp"
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cerrno>
#include "includes/easysock.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#ifndef _WIN32
@@ -51,22 +52,23 @@ SOCKET create_socket(int network, char transport) {
int newSock = socket(domain,type,0);
/* Set REUSEADDR flag, allowing program to be run twice */
int set_opt = 1;
setsockopt(newSock, SOL_SOCKET, SO_REUSEADDR, (char *)&set_opt, sizeof(set_opt));
/* Set REUSEADDR flag for TCP, allowing program to be run twice */
if (transport == ES_TCP) {
int set_opt = 1;
setsockopt(newSock, SOL_SOCKET, SO_REUSEADDR, (char *)&set_opt, sizeof(set_opt));
}
return newSock;
}
int create_addr(int network, const char* address, int port,struct sockaddr* dest) {
int create_addr(int network, const char* address, int port,struct sockaddr_storage* dest) {
if (network == 4) {
struct sockaddr_in listen_address;
listen_address.sin_family = AF_INET;
listen_address.sin_port = htons(port);
inet_pton(AF_INET,address,&listen_address.sin_addr);
memcpy(dest,&listen_address,sizeof(listen_address));
memcpy((struct sockaddr *)dest,&listen_address,sizeof(listen_address));
return 0;
} else if (network == 6) {
@@ -74,7 +76,7 @@ int create_addr(int network, const char* address, int port,struct sockaddr* dest
listen_ipv6.sin6_family = AF_INET6;
listen_ipv6.sin6_port = htons(port);
inet_pton(AF_INET6,address,&listen_ipv6.sin6_addr);
memcpy(dest,&listen_ipv6,sizeof(listen_ipv6));
memcpy((struct sockaddr_in6 *)dest,&listen_ipv6,sizeof(listen_ipv6));
return 0;
} else {
@@ -83,7 +85,7 @@ int create_addr(int network, const char* address, int port,struct sockaddr* dest
}
SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr* addr_struct) {
SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr_storage* addr_struct) {
int socket = create_socket(network,transport);
if (socket < 0) {
return (-1 * errno);
@@ -102,14 +104,14 @@ SOCKET create_local (int network, char transport, const char* address, int port,
This should be set to the size of 'sockaddr_in' for IPv4, and 'sockaddr_in6' for IPv6.
See https://stackoverflow.com/questions/73707162/socket-bind-failed-with-invalid-argument-error-for-program-running-on-macos */
int i = bind (socket,addr_struct,(socklen_t)addrlen);
int i = bind (socket,(struct sockaddr *)addr_struct,(socklen_t)addrlen);
if (i < 0) {
return (-1 * errno);
}
return socket;
}
SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr* remote_addr_struct) {
SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr_storage* remote_addr_struct) {
struct addrinfo hints; /* Used to tell getaddrinfo what kind of address we want */
struct addrinfo* results; /* Used by getaddrinfo to store the addresses */
@@ -129,7 +131,7 @@ SOCKET create_remote (int network,char transport, const char* address,int port,s
if (err_code != 0) {
return (-1 * err_code);
}
remote_addr_struct = results->ai_addr;
remote_addr_struct = (struct sockaddr_storage *)results->ai_addr;
network = inet_to_int(results->ai_family);
} else {
create_addr(network,address,port,remote_addr_struct);
@@ -187,6 +189,29 @@ int check_ip_ver(const char* address) {
}
}
int port_to_num(const char* port_str) {
/* The largest possible port value is 65535: a 5 character string */
if (strlen(port_str) > 5) {
return -1;
}
for (int i = 0; i < strlen(port_str); i++) {
if (isdigit(port_str[i]) == 0) { // Ensure that every character in port_str is a digit (isidigit() returns 0 if the parameter is not a digit)
return -1;
}
}
/* Convert the string to a base-10 integer */
int port_num = (int)strtol(port_str, NULL, 10);
if (port_num > 65535) {
return -1;
}
if (port_num < 1024) {
return -2;
}
return port_num;
}
int int_to_inet(int network) {
if (network == 4) {
return AF_INET;

30
includes/check_input.hpp Normal file
View File

@@ -0,0 +1,30 @@
#ifndef _CHECK_INPUT_H
#define _CHECK_INPUT_H
#include "includes/sock.hpp"
typedef enum {M_SINGLE, M_CLIENT, M_SERVER} Mode;
/* This struct contains a Mode enum, which indicates the type of game we are
playing (Single player, client mode or server mode). The netsock parameter is
a 'Sock' object - Client and Server classes inherit from this object, so this
parameter can be instantiated to either a client or server, depending on the
game type. */
typedef struct {
Mode mode;
Sock* netsock;
} GameType;
/* This function checks the IP address and port passed to it, and returns a struct,
that contains information about the game mode, and contains the server socket.
It assumes that both ip_text and port_text are non-null.
Any errors are printed using the display_text function, with the given if_type.
TODO - Add better error checking. */
GameType check_server(char* ip_text, char* port_text, const int if_type);
/* NOT IMPLEMENTED YET - This function checks the code given to it, and returns a struct
that contains information about the game mode, and contains the client socket.
Any errors are printed using the display_text function, with the given if_type. */
GameType check_client(char* code, const int if_type);
#endif

View File

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

10
includes/display_text.hpp Normal file
View File

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

View File

@@ -1,12 +1,18 @@
#ifndef EASYSOCK_HPP_
#define EASYSOCK_HPP_
#ifdef __cplusplus
extern "C" {
#endif
#ifndef EASYSOCK_H_
#define EASYSOCK_H_
#ifdef _WIN32
#define NOGDI // All GDI defines and routines
#define NOUSER // All USER defines and routines
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <winsock.h>
#include <ws2tcpip.h>
#endif
#ifdef __unix__
#if defined(__unix) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
@@ -44,7 +50,7 @@ and dest is a pointer to the sockaddr struct that will be filled in.
The function returns with -202 if the network parameter contained neither '4'
nor '6'. */
int create_addr(int network, const char* address, int port,struct sockaddr* dest);
int create_addr(int network, const char* address, int port,struct sockaddr_storage* dest);
@@ -54,7 +60,7 @@ same as above.
It prints the error returned by 'bind' if something went wrong, and returns ( -1 * errno ).*/
SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr* addr_struct);
SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr_storage* addr_struct);
/* This function utilizes the same functions as 'create_local' but _connects_ to the
@@ -63,13 +69,19 @@ as above. This function needs an empty 'sockaddr *' structure passed to it, whic
If something goes wrong, this function returns with ( -1 * errno ). */
SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr* remote_addr_struct);
SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr_storage* remote_addr_struct);
/* check_ip_ver - This function checks if the given string is an IPv4 address (returns 4),
IPv6 address (returns 6) or neither (returns -1). */
int check_ip_ver(const char* address);
/* port_to_num - Converts a string representing a port, into a numeric value.
Returns -1 if the string is not numeric, or exceeds the maximum port length.
Returns -2 if the string is lower than 1024, This serves as a warning, as ports less
than 1023 are reserved. */
int port_to_num(const char* port_str);
/* int_to_inet - Takes an int value (4 for IPv4, 6 for IPv6) and returns AF_INET or
AF_INET6 respectively. */
@@ -92,3 +104,6 @@ int sock_quit(void);
int sock_close(SOCKET);
#endif
#ifdef __cplusplus
}
#endif

5535
includes/raygui/raygui.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,589 @@
//////////////////////////////////////////////////////////////////////////////////
// //
// StyleAsCode exporter v2.0 - Style data exported as a values array //
// //
// USAGE: On init call: GuiLoadStyleDark(); //
// //
// more info and bugs-report: github.com/raysan5/raygui //
// feedback and support: ray[at]raylibtech.com //
// //
// Copyright (c) 2020-2024 raylib technologies (@raylibtech) //
// //
//////////////////////////////////////////////////////////////////////////////////
#define DARK_STYLE_PROPS_COUNT 23
// Custom style name: Dark
static const GuiStyleProp darkStyleProps[DARK_STYLE_PROPS_COUNT] = {
{ 0, 0, 0x878787ff }, // DEFAULT_BORDER_COLOR_NORMAL
{ 0, 1, 0x2c2c2cff }, // DEFAULT_BASE_COLOR_NORMAL
{ 0, 2, 0xc3c3c3ff }, // DEFAULT_TEXT_COLOR_NORMAL
{ 0, 3, 0xe1e1e1ff }, // DEFAULT_BORDER_COLOR_FOCUSED
{ 0, 4, 0x848484ff }, // DEFAULT_BASE_COLOR_FOCUSED
{ 0, 5, 0x181818ff }, // DEFAULT_TEXT_COLOR_FOCUSED
{ 0, 6, 0x000000ff }, // DEFAULT_BORDER_COLOR_PRESSED
{ 0, 7, 0xefefefff }, // DEFAULT_BASE_COLOR_PRESSED
{ 0, 8, 0x202020ff }, // DEFAULT_TEXT_COLOR_PRESSED
{ 0, 9, 0x6a6a6aff }, // DEFAULT_BORDER_COLOR_DISABLED
{ 0, 10, 0x818181ff }, // DEFAULT_BASE_COLOR_DISABLED
{ 0, 11, 0x606060ff }, // DEFAULT_TEXT_COLOR_DISABLED
{ 0, 16, 0x00000010 }, // DEFAULT_TEXT_SIZE
{ 0, 17, 0x00000000 }, // DEFAULT_TEXT_SPACING
{ 0, 18, 0x9d9d9dff }, // DEFAULT_LINE_COLOR
{ 0, 19, 0x3c3c3cff }, // DEFAULT_BACKGROUND_COLOR
{ 0, 20, 0x00000018 }, // DEFAULT_TEXT_LINE_SPACING
{ 1, 5, 0xf7f7f7ff }, // LABEL_TEXT_COLOR_FOCUSED
{ 1, 8, 0x898989ff }, // LABEL_TEXT_COLOR_PRESSED
{ 4, 5, 0xb0b0b0ff }, // SLIDER_TEXT_COLOR_FOCUSED
{ 5, 5, 0x848484ff }, // PROGRESSBAR_TEXT_COLOR_FOCUSED
{ 9, 5, 0xf5f5f5ff }, // TEXTBOX_TEXT_COLOR_FOCUSED
{ 10, 5, 0xf6f6f6ff }, // VALUEBOX_TEXT_COLOR_FOCUSED
};
// WARNING: This style uses a custom font: "PixelOperator.ttf" (size: 16, spacing: 0)
#define DARK_STYLE_FONT_ATLAS_COMP_SIZE 2126
// Font atlas image pixels data: DEFLATE compressed
static unsigned char darkFontData[DARK_STYLE_FONT_ATLAS_COMP_SIZE] = { 0xed,
0xdd, 0xdb, 0x72, 0xa4, 0x3a, 0x12, 0x05, 0x50, 0xfd, 0xff, 0x4f, 0xe7, 0x3c, 0x4c, 0x4c, 0x4c, 0x74, 0x9c, 0xd3, 0x20,
0xa5, 0x52, 0x17, 0xec, 0xd5, 0xeb, 0xcd, 0xe5, 0x76, 0x51, 0xa0, 0x94, 0x84, 0x28, 0x36, 0xd1, 0x00, 0x00, 0x00, 0x80,
0x5f, 0x2f, 0xfe, 0xf5, 0x27, 0xf1, 0xd7, 0xdf, 0x8c, 0xee, 0xbf, 0xf3, 0xfc, 0xf3, 0xff, 0xbd, 0x1a, 0x0f, 0xef, 0xd5,
0xb7, 0xad, 0xa3, 0xef, 0x1b, 0x03, 0x7b, 0xe2, 0xdf, 0xb7, 0x2f, 0xba, 0xff, 0xee, 0xdf, 0x3e, 0xdf, 0xf8, 0xef, 0x3f,
0xfd, 0xa5, 0xe7, 0xad, 0x8f, 0xa1, 0xfd, 0x3e, 0xfe, 0x7f, 0xc6, 0x8e, 0x62, 0x2c, 0xd9, 0xf7, 0xef, 0x5b, 0x37, 0xbe,
0xed, 0x95, 0xff, 0x27, 0x1e, 0x3e, 0x4f, 0xe6, 0x28, 0xf5, 0xd4, 0xd6, 0x0d, 0xf5, 0x1f, 0x5d, 0x95, 0x18, 0xa5, 0x2d,
0xe7, 0xff, 0xef, 0x1a, 0x85, 0x3d, 0x55, 0x76, 0x4f, 0x3e, 0x55, 0xfa, 0xf8, 0xfe, 0x78, 0x6a, 0xfd, 0xe3, 0x3d, 0x60,
0x94, 0x7c, 0x96, 0xf9, 0xd6, 0x18, 0xd3, 0xbf, 0xdd, 0xd7, 0x92, 0x6a, 0x3e, 0xd5, 0xcc, 0x2b, 0xad, 0xe4, 0x28, 0x9d,
0xa8, 0xff, 0x48, 0xf7, 0x54, 0x31, 0x58, 0xd1, 0x15, 0x7b, 0x30, 0xd3, 0x0f, 0xb7, 0x8d, 0xf5, 0x1f, 0xa5, 0x73, 0x97,
0x28, 0x6b, 0x73, 0x27, 0xea, 0xff, 0x79, 0xd4, 0x6e, 0x25, 0xed, 0x26, 0x5e, 0xf6, 0x51, 0x6d, 0x2d, 0xaf, 0x9d, 0xa7,
0xae, 0xae, 0xff, 0xe7, 0xdf, 0x1c, 0x1d, 0x6f, 0x63, 0x70, 0xec, 0xae, 0xd8, 0x4f, 0xb9, 0xf1, 0xbf, 0x7e, 0x3f, 0xc6,
0x5f, 0xc7, 0xe6, 0xec, 0x3c, 0x64, 0x64, 0x0f, 0xc7, 0xe0, 0xd9, 0xd5, 0xec, 0x08, 0x7b, 0x72, 0xfc, 0xef, 0x9b, 0x0b,
0xaa, 0xff, 0xb5, 0xf5, 0x9f, 0xf9, 0x24, 0xd1, 0xb9, 0x05, 0x99, 0x31, 0xb1, 0x5d, 0x50, 0xff, 0xa3, 0xfd, 0xd0, 0xf3,
0xbe, 0xaa, 0x9a, 0xcb, 0x8f, 0xae, 0x4b, 0xf4, 0x9d, 0x61, 0xef, 0x6b, 0x85, 0xe3, 0x73, 0xa3, 0xf1, 0xfa, 0x1f, 0xef,
0x3d, 0xdf, 0xfe, 0xda, 0xe8, 0x5e, 0xfd, 0xfb, 0x99, 0xdd, 0xfc, 0x1e, 0xdf, 0x57, 0xff, 0x91, 0x18, 0x53, 0xce, 0xd7,
0x73, 0x4d, 0xfd, 0x47, 0x62, 0xe5, 0xa2, 0x95, 0xad, 0xf2, 0xe5, 0xd6, 0x25, 0xea, 0xd6, 0x0c, 0xce, 0x1f, 0x81, 0xf7,
0xfa, 0x7f, 0x3b, 0x1f, 0x3c, 0x3f, 0xfe, 0xc7, 0x15, 0xe3, 0x7f, 0xa4, 0xd7, 0x86, 0x5b, 0xa2, 0xf7, 0xfd, 0x5a, 0xfd,
0x47, 0xa2, 0x66, 0xa2, 0xe4, 0xbc, 0xbc, 0x2d, 0x59, 0xcb, 0xab, 0x5d, 0x33, 0xdc, 0x73, 0xb4, 0xde, 0x46, 0xd1, 0xaa,
0xb3, 0x9a, 0x9f, 0x55, 0xff, 0xad, 0x60, 0xfc, 0xef, 0x9b, 0x03, 0xcc, 0x7e, 0x8e, 0xf8, 0xd8, 0xe8, 0x5f, 0x59, 0xff,
0xbb, 0x56, 0xec, 0xd7, 0xb7, 0xc6, 0xb8, 0xe2, 0xc8, 0x58, 0xff, 0xaf, 0x9c, 0xff, 0xe7, 0xd7, 0x00, 0xc6, 0xce, 0x63,
0xe2, 0xd2, 0xea, 0xcf, 0x9c, 0x33, 0x57, 0x5d, 0xb1, 0xaf, 0xfd, 0x36, 0x41, 0x24, 0xbe, 0xab, 0x51, 0xb3, 0x8a, 0xb3,
0xfa, 0x18, 0x8c, 0x8f, 0xff, 0xbb, 0xae, 0xff, 0xbf, 0xcf, 0xb9, 0xef, 0x18, 0xff, 0xdb, 0xeb, 0xe8, 0x7e, 0xcb, 0xf6,
0x00, 0x7b, 0x7b, 0x80, 0x50, 0xfd, 0xe0, 0x7b, 0xc4, 0x80, 0xfa, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0a, 0xbf, 0x9f, 0x5f, 0x9f, 0x63, 0x30, 0x9e, 0x84, 0xbd, 0x3e, 0x81, 0x7d, 0x26, 0x13, 0x38, 0xbb,
0x87, 0xaa, 0xfe, 0xe2, 0x73, 0x76, 0xfc, 0x48, 0xd2, 0x42, 0x7f, 0xcb, 0x88, 0xe1, 0xa7, 0x1f, 0x54, 0x26, 0x0a, 0x64,
0x32, 0x0d, 0xda, 0x60, 0x9b, 0xa8, 0xcd, 0x52, 0x1a, 0xdb, 0xfa, 0x3d, 0x2d, 0xfe, 0x0b, 0xf5, 0x7f, 0xfe, 0x95, 0x96,
0xc8, 0xef, 0xd9, 0x59, 0xff, 0xa3, 0x6d, 0x2f, 0x97, 0x8f, 0x3b, 0x37, 0x96, 0xec, 0x4e, 0x14, 0x8b, 0xc2, 0x63, 0xb8,
0x6f, 0x5c, 0xfd, 0x33, 0x43, 0x67, 0x47, 0xbb, 0x9e, 0xa9, 0xff, 0xd1, 0xec, 0xd3, 0x78, 0xe8, 0x85, 0xef, 0xa9, 0xff,
0xb6, 0xa5, 0xfe, 0xe3, 0x23, 0xf5, 0x5f, 0x9d, 0x24, 0x53, 0x95, 0x42, 0x9e, 0x49, 0xbe, 0x19, 0x3f, 0x86, 0xf9, 0x84,
0xdf, 0x6c, 0x8b, 0xfb, 0x4e, 0xfd, 0x67, 0xda, 0xd6, 0x73, 0xce, 0xaa, 0xfa, 0x7f, 0xdf, 0xb7, 0x99, 0xd1, 0x64, 0xfe,
0xe7, 0x35, 0x95, 0xbf, 0x27, 0x47, 0xf7, 0xed, 0x39, 0x5a, 0x6b, 0xdb, 0xca, 0xec, 0x58, 0xf9, 0x95, 0xfa, 0x8f, 0xe4,
0x5f, 0x89, 0x97, 0x1e, 0xa5, 0x36, 0x81, 0xbd, 0x26, 0xb5, 0x6d, 0x6f, 0xfd, 0x57, 0x3f, 0xf7, 0xe7, 0x8e, 0xfa, 0x3f,
0x9f, 0xa3, 0xbb, 0xea, 0x1c, 0xae, 0x76, 0xac, 0xfc, 0xd9, 0xe3, 0xff, 0x7b, 0x2f, 0xb9, 0xa3, 0x7d, 0xcc, 0x8c, 0xf4,
0x27, 0xc7, 0xff, 0x73, 0x3f, 0x3f, 0x5b, 0xff, 0x55, 0xad, 0x3d, 0x36, 0xce, 0x4c, 0xb2, 0x63, 0xe5, 0xce, 0x55, 0xb2,
0xdd, 0xe7, 0xff, 0xab, 0x3e, 0x59, 0x1b, 0x9e, 0x4d, 0xc4, 0xb5, 0xf5, 0x3f, 0xb3, 0x0a, 0xb9, 0x6e, 0xbe, 0x70, 0x66,
0xfe, 0x9f, 0x7b, 0xbe, 0x6d, 0xe6, 0xa9, 0x80, 0xb7, 0x8d, 0xff, 0xbb, 0xae, 0x92, 0xb5, 0xcd, 0x35, 0xb9, 0xf7, 0xbd,
0x2a, 0x9f, 0x52, 0x37, 0x9e, 0xdf, 0x1f, 0xc5, 0x33, 0xbc, 0xaf, 0xd7, 0x7f, 0x7e, 0x95, 0xbf, 0x15, 0xad, 0x4a, 0x9e,
0x9b, 0xff, 0xef, 0x7e, 0xd2, 0x49, 0xe6, 0x5d, 0x6a, 0xfa, 0xdf, 0xbe, 0xab, 0xc2, 0xb5, 0xa3, 0xc0, 0xdf, 0xaf, 0x36,
0x44, 0xd1, 0xc8, 0x51, 0x95, 0xdf, 0xff, 0xe7, 0xb6, 0x8d, 0x3f, 0xf1, 0x6c, 0xfc, 0x4a, 0x7c, 0x0c, 0x3e, 0x4f, 0xff,
0x44, 0xfd, 0x67, 0xde, 0x39, 0xf3, 0xbf, 0x46, 0x8f, 0x61, 0x65, 0xfd, 0x9f, 0xeb, 0x01, 0xe4, 0x6e, 0xc2, 0x8d, 0xb3,
0x18, 0xe0, 0xe7, 0xf4, 0x00, 0x9e, 0x70, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xec, 0xbe, 0x13, 0xfb, 0xef, 0xd9, 0x5c, 0x99, 0xd4, 0xf8, 0xf1, 0xfb, 0xa4, 0x77, 0x26, 0xe6, 0xb7, 0x97, 0x14, 0xb2,
0xe7, 0xdf, 0xfa, 0xf3, 0x95, 0x8a, 0x3c, 0xde, 0x28, 0xbc, 0x6b, 0xfd, 0x29, 0x93, 0xe4, 0x39, 0x4b, 0x71, 0x34, 0x77,
0xe0, 0x7d, 0x0f, 0x8f, 0x1f, 0x97, 0x99, 0xd7, 0x5a, 0x61, 0x9a, 0x42, 0x24, 0xd2, 0x5e, 0xde, 0xf6, 0x7a, 0x7f, 0x36,
0x70, 0x94, 0x6d, 0xf1, 0x58, 0x1d, 0x55, 0x65, 0x27, 0xdf, 0x9e, 0x98, 0x3f, 0x9f, 0xbc, 0x13, 0x2f, 0xad, 0xae, 0xbf,
0xfe, 0x7b, 0x92, 0x23, 0xd6, 0xe7, 0x18, 0xf5, 0xfc, 0x34, 0x93, 0x7a, 0x31, 0x96, 0xc1, 0x36, 0xd7, 0x06, 0x56, 0x8f,
0x50, 0x6d, 0xb2, 0x42, 0x62, 0xaa, 0xa5, 0xcc, 0x67, 0xff, 0xbd, 0x6f, 0x69, 0x1b, 0x1c, 0xb9, 0x6e, 0x48, 0xcc, 0x8c,
0xce, 0xfa, 0xaa, 0xfe, 0x69, 0x1b, 0xec, 0x6f, 0x32, 0x79, 0x75, 0x51, 0x9a, 0x44, 0x91, 0xff, 0x3f, 0x91, 0x48, 0x6d,
0x6e, 0xc9, 0x2c, 0xbe, 0x7c, 0x46, 0x7c, 0x26, 0xa7, 0x2a, 0x16, 0xef, 0xd9, 0x4c, 0xfd, 0xb7, 0x43, 0xf5, 0x9f, 0x49,
0x3d, 0x38, 0x9f, 0x98, 0x1d, 0xa9, 0xda, 0xdd, 0x5b, 0xff, 0xd1, 0xd1, 0x7f, 0x65, 0x66, 0xd7, 0x51, 0x78, 0xce, 0xf0,
0x3e, 0xe3, 0xad, 0x1e, 0xff, 0xdb, 0xe3, 0x7e, 0x1a, 0x4f, 0x75, 0x5b, 0x3f, 0x42, 0x65, 0x7a, 0x93, 0xb1, 0xfa, 0x5f,
0xdd, 0x63, 0xcd, 0x3c, 0x07, 0x29, 0x3e, 0x37, 0xfe, 0x9f, 0x49, 0xcc, 0x8e, 0xc4, 0xd9, 0x75, 0xe6, 0x7d, 0xeb, 0xd7,
0x5e, 0xa2, 0xb0, 0xfe, 0x33, 0x3d, 0x40, 0xbe, 0xfe, 0x77, 0xce, 0xa6, 0xb3, 0x79, 0x93, 0x31, 0xdd, 0x86, 0xce, 0xd4,
0x7f, 0xe5, 0xd3, 0x96, 0x56, 0x24, 0xe6, 0x8e, 0x3f, 0xd9, 0xf1, 0xd4, 0xf8, 0xdf, 0x52, 0xe3, 0x7f, 0x6e, 0x9c, 0xa8,
0x3b, 0xf6, 0xb5, 0x73, 0xe5, 0xf7, 0xfa, 0x8f, 0x0d, 0xef, 0xb6, 0xaf, 0xfe, 0x33, 0x3d, 0x61, 0x2c, 0x9b, 0xff, 0x67,
0xab, 0x25, 0x86, 0xe7, 0xd7, 0x3b, 0x13, 0x73, 0x33, 0xeb, 0xcc, 0xf7, 0xd7, 0x7f, 0xe5, 0x33, 0x4c, 0xda, 0x54, 0xfa,
0x72, 0x94, 0x8e, 0xc8, 0xfb, 0xc6, 0xff, 0x15, 0xb3, 0xe9, 0x4c, 0xfa, 0xfe, 0x3d, 0xeb, 0x7f, 0xad, 0xf8, 0x88, 0xb5,
0xe3, 0x79, 0x88, 0x2b, 0x66, 0x85, 0x27, 0xce, 0xff, 0xab, 0x12, 0xd8, 0x73, 0xe7, 0xff, 0xd5, 0x2b, 0xf2, 0x73, 0xc7,
0xf2, 0xde, 0xf1, 0x3f, 0xd7, 0x42, 0x62, 0x68, 0x7c, 0x6e, 0x9f, 0xaa, 0xff, 0x5b, 0xbe, 0xd1, 0x50, 0xd1, 0x6f, 0x45,
0x49, 0xef, 0x96, 0x79, 0x6e, 0x4b, 0xe5, 0x3c, 0xaa, 0x72, 0xfd, 0x6a, 0xe6, 0xfa, 0xff, 0xcc, 0xb7, 0x53, 0x56, 0xbf,
0x5b, 0xfe, 0xfa, 0xff, 0xda, 0xfa, 0x8f, 0xe2, 0x2d, 0xe6, 0x9e, 0x5e, 0xea, 0xe6, 0xf7, 0xd3, 0x7e, 0xee, 0x38, 0x82,
0x6a, 0x59, 0xeb, 0x39, 0x71, 0x3e, 0xc4, 0xef, 0x9a, 0x33, 0xf3, 0xd3, 0xbf, 0xe1, 0x6d, 0x3f, 0x18, 0x9b, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xef, 0xde, 0xc3, 0x14, 0xaf, 0x77, 0x38, 0xbc, 0x65, 0x4b,
0x64, 0x72, 0x29, 0x5b, 0x69, 0xce, 0x5d, 0x4b, 0x24, 0xf3, 0x67, 0xb7, 0xae, 0x77, 0xaf, 0xac, 0xff, 0x64, 0xb3, 0x69,
0xe6, 0x99, 0xc4, 0xf9, 0xcc, 0x3e, 0x3e, 0x7b, 0x34, 0xdf, 0x32, 0x3c, 0x22, 0x91, 0xc6, 0x5a, 0x79, 0xbc, 0x5a, 0x6a,
0xdf, 0x65, 0x5b, 0x59, 0x6f, 0x0f, 0xd0, 0x9f, 0x0f, 0x53, 0x7f, 0x8f, 0x69, 0xa4, 0xf6, 0x79, 0x4d, 0x32, 0xff, 0x4c,
0x5b, 0x6a, 0xe9, 0x04, 0xe2, 0x6c, 0xab, 0xe9, 0xfd, 0x5b, 0x31, 0xfd, 0x97, 0xdf, 0xb3, 0x5a, 0xe3, 0x73, 0x47, 0x33,
0x9b, 0x17, 0x5f, 0x7d, 0xbc, 0x6a, 0xd3, 0xdb, 0x6a, 0x7a, 0x80, 0x8a, 0xfa, 0xaf, 0xdb, 0xca, 0xfd, 0xf7, 0xc6, 0x46,
0xf2, 0x7e, 0xfa, 0xf8, 0xfc, 0xfd, 0xa7, 0xf1, 0xd1, 0x24, 0x81, 0x5c, 0xca, 0xe4, 0xae, 0x4f, 0x95, 0xcb, 0xe5, 0xca,
0xa7, 0xb7, 0xed, 0x1c, 0xff, 0x33, 0x49, 0x52, 0x99, 0xb4, 0xd0, 0xea, 0x57, 0xb2, 0xbd, 0x65, 0xae, 0xfe, 0x33, 0x99,
0x4d, 0x51, 0x94, 0x67, 0x5c, 0xf7, 0xdc, 0x93, 0x48, 0xce, 0xaf, 0x4f, 0x1e, 0xcd, 0x78, 0x4d, 0x25, 0xaf, 0xca, 0xd8,
0xdd, 0xfb, 0x4a, 0x4c, 0x26, 0x3e, 0x44, 0xd7, 0xf1, 0x8e, 0xe9, 0xb1, 0x70, 0xd7, 0xd3, 0x02, 0x33, 0xcf, 0xa4, 0x68,
0xc9, 0xd4, 0xcc, 0x28, 0x9b, 0xe1, 0x57, 0xbf, 0xb2, 0xa2, 0xfe, 0xe3, 0xe1, 0x3c, 0xb9, 0xfa, 0x99, 0x65, 0xd5, 0x47,
0xb3, 0xa7, 0x67, 0xf8, 0x62, 0xfd, 0xaf, 0x9d, 0xa7, 0xd5, 0x9c, 0xff, 0x47, 0xfa, 0x59, 0x3c, 0x77, 0xb6, 0x98, 0xe8,
0x4c, 0x77, 0xfe, 0x69, 0xf5, 0xbf, 0x77, 0x1f, 0x57, 0xf7, 0xe6, 0xcf, 0x33, 0xec, 0xf6, 0xd1, 0xfa, 0x9f, 0x49, 0x8b,
0xaf, 0x39, 0x5f, 0x79, 0x5f, 0xff, 0xaf, 0x5c, 0xd9, 0xb8, 0xa3, 0xfe, 0x6f, 0xa9, 0xf2, 0xb1, 0x7d, 0xbe, 0xb6, 0xfe,
0xcf, 0xd7, 0xd0, 0x6c, 0xf2, 0xfb, 0xf9, 0x6d, 0x8f, 0xb2, 0xf3, 0xff, 0x9a, 0x75, 0xa1, 0xea, 0x79, 0xc4, 0x6d, 0x3d,
0xe8, 0xaa, 0xa7, 0xab, 0xbd, 0xcd, 0x1d, 0xaa, 0xcf, 0x77, 0xef, 0x18, 0xff, 0x43, 0xfd, 0x2f, 0x58, 0x0b, 0xaf, 0x1d,
0x25, 0xf3, 0x2b, 0xcc, 0x33, 0xfd, 0xc9, 0xf8, 0xb3, 0xec, 0x6b, 0x57, 0xc9, 0x32, 0x7d, 0x68, 0xcf, 0x2b, 0xf9, 0x27,
0x6e, 0x7c, 0x75, 0xfe, 0xdf, 0xf3, 0x54, 0xa0, 0x6f, 0xd5, 0x7f, 0x24, 0xd7, 0xb7, 0xee, 0xa8, 0xff, 0xb5, 0x57, 0x06,
0xd6, 0x5f, 0xcb, 0xbf, 0x79, 0xfe, 0xbf, 0xa2, 0xcf, 0xfb, 0x76, 0xfd, 0x7f, 0x61, 0x0e, 0xbd, 0xe6, 0x5b, 0x28, 0xe7,
0xea, 0x7f, 0x6e, 0xdc, 0x56, 0xff, 0x2b, 0x56, 0x8c, 0xf3, 0x9f, 0x7a, 0xc5, 0xf9, 0x7f, 0xef, 0x4c, 0xe3, 0xe7, 0xd7,
0x7f, 0x7d, 0xd2, 0xf9, 0x9a, 0xf3, 0xcd, 0x76, 0xe9, 0x77, 0x61, 0xee, 0xfa, 0x4e, 0xf2, 0x9e, 0x16, 0xb3, 0x66, 0xed,
0x23, 0xf3, 0x94, 0xde, 0xda, 0xa7, 0x39, 0xc5, 0xa2, 0x4a, 0xcf, 0x5d, 0xe5, 0xdd, 0x73, 0xfd, 0x7f, 0xef, 0x37, 0x8a,
0xaa, 0xb7, 0x50, 0xfd, 0x7f, 0xa3, 0x0f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xdb,
0xee, 0xd0, 0xdb, 0x9b, 0xa8, 0x5f, 0x9f, 0x82, 0x71, 0x7b, 0xa2, 0xfe, 0xdb, 0x5d, 0x33, 0xed, 0xda, 0x44, 0xfd, 0xd6,
0x95, 0x4c, 0xf4, 0xbd, 0x44, 0xfd, 0xdf, 0x78, 0xc7, 0xc7, 0x6d, 0x89, 0xfa, 0x75, 0x77, 0x4e, 0xdf, 0x9e, 0xa8, 0xdf,
0x5e, 0xf2, 0x87, 0x6e, 0x4e, 0xd4, 0xcf, 0xdf, 0x3b, 0x75, 0x6b, 0xa2, 0x3e, 0x6b, 0xef, 0x98, 0xec, 0xed, 0xfb, 0xe3,
0xe0, 0x31, 0x5b, 0x75, 0x77, 0x69, 0x7c, 0xf4, 0xce, 0xcf, 0xf1, 0x8c, 0xa6, 0x15, 0x3d, 0x4a, 0xe5, 0x5d, 0x9a, 0xeb,
0x13, 0xf5, 0x7f, 0x4a, 0xbd, 0xee, 0x4a, 0xd4, 0x7f, 0xcf, 0x19, 0x8b, 0x4d, 0xdb, 0x1f, 0x43, 0xf3, 0xc6, 0xb5, 0x89,
0x5a, 0xcf, 0xfd, 0xe8, 0xbd, 0xcf, 0x47, 0xf8, 0x76, 0xa2, 0xf6, 0x6f, 0xab, 0xff, 0x55, 0xfb, 0x29, 0x8a, 0xaa, 0xae,
0xf7, 0x95, 0x7d, 0x59, 0x41, 0x75, 0xf5, 0x1f, 0xe5, 0xaf, 0xed, 0x4d, 0xd4, 0xca, 0xae, 0x27, 0xa8, 0xff, 0xef, 0xac,
0x01, 0xe4, 0xd6, 0xad, 0xe6, 0x12, 0x75, 0x4f, 0xb6, 0x80, 0x9d, 0x89, 0x5a, 0x5f, 0xaf, 0xff, 0xcc, 0xac, 0x5c, 0xfd,
0xdf, 0x39, 0xff, 0x8f, 0xb2, 0x57, 0x32, 0xbd, 0x46, 0x7e, 0xfd, 0xff, 0xcb, 0x4f, 0xd4, 0xfa, 0x99, 0xf5, 0x7f, 0x47,
0xa2, 0xae, 0xfa, 0x3f, 0x33, 0xff, 0x5f, 0xf7, 0x54, 0x82, 0x9d, 0x89, 0xba, 0xc6, 0xff, 0x55, 0x15, 0x71, 0x77, 0x95,
0xdf, 0x95, 0x9b, 0xfb, 0xcd, 0xf3, 0xff, 0xdc, 0x93, 0x40, 0xb3, 0x3d, 0xc0, 0xbe, 0x84, 0xd6, 0x13, 0x89, 0xfa, 0x12,
0x75, 0x67, 0x8e, 0xd7, 0xf3, 0xec, 0xef, 0x9b, 0xd7, 0x63, 0x6e, 0x9f, 0xff, 0xd7, 0x5e, 0xab, 0xf9, 0xe7, 0x9a, 0xd2,
0xea, 0x15, 0xef, 0x55, 0xf3, 0xff, 0xda, 0x2b, 0x5b, 0x12, 0x75, 0xab, 0x8f, 0xda, 0x6f, 0x5f, 0xff, 0x3b, 0xdb, 0xd3,
0xc0, 0xee, 0x79, 0xed, 0xf9, 0x9e, 0x10, 0xf8, 0xda, 0x37, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0xf9, 0xef, 0x3f, 0xfb, 0x01, 0xd4, 0x3f,
0xf0, 0xeb, 0xea, 0xff, 0x3f };
// Font glyphs rectangles data (on atlas)
static const Rectangle darkFontRecs[189] = {
{ 4, 4, 4 , 16 },
{ 16, 4, 1 , 9 },
{ 25, 4, 3 , 3 },
{ 36, 4, 6 , 9 },
{ 50, 4, 5 , 13 },
{ 63, 4, 7 , 9 },
{ 78, 4, 5 , 9 },
{ 91, 4, 1 , 3 },
{ 100, 4, 3 , 9 },
{ 111, 4, 3 , 9 },
{ 122, 4, 5 , 5 },
{ 135, 4, 5 , 5 },
{ 148, 4, 2 , 3 },
{ 158, 4, 4 , 1 },
{ 170, 4, 1 , 1 },
{ 179, 4, 3 , 9 },
{ 190, 4, 5 , 9 },
{ 203, 4, 3 , 9 },
{ 214, 4, 5 , 9 },
{ 227, 4, 5 , 9 },
{ 240, 4, 5 , 9 },
{ 253, 4, 5 , 9 },
{ 266, 4, 5 , 9 },
{ 279, 4, 5 , 9 },
{ 292, 4, 5 , 9 },
{ 305, 4, 5 , 9 },
{ 318, 4, 1 , 7 },
{ 327, 4, 2 , 9 },
{ 337, 4, 3 , 5 },
{ 348, 4, 4 , 3 },
{ 360, 4, 3 , 5 },
{ 371, 4, 5 , 9 },
{ 384, 4, 7 , 9 },
{ 399, 4, 5 , 9 },
{ 412, 4, 5 , 9 },
{ 425, 4, 5 , 9 },
{ 438, 4, 5 , 9 },
{ 451, 4, 5 , 9 },
{ 464, 4, 5 , 9 },
{ 477, 4, 5 , 9 },
{ 490, 4, 5 , 9 },
{ 4, 28, 1 , 9 },
{ 13, 28, 5 , 9 },
{ 26, 28, 5 , 9 },
{ 39, 28, 5 , 9 },
{ 52, 28, 7 , 9 },
{ 67, 28, 5 , 9 },
{ 80, 28, 5 , 9 },
{ 93, 28, 5 , 9 },
{ 106, 28, 5 , 9 },
{ 119, 28, 5 , 9 },
{ 132, 28, 5 , 9 },
{ 145, 28, 5 , 9 },
{ 158, 28, 5 , 9 },
{ 171, 28, 5 , 9 },
{ 184, 28, 7 , 9 },
{ 199, 28, 5 , 9 },
{ 212, 28, 5 , 9 },
{ 225, 28, 5 , 9 },
{ 238, 28, 3 , 9 },
{ 249, 28, 3 , 9 },
{ 260, 28, 3 , 9 },
{ 271, 28, 5 , 3 },
{ 284, 28, 5 , 1 },
{ 297, 28, 2 , 2 },
{ 307, 28, 5 , 7 },
{ 320, 28, 5 , 9 },
{ 333, 28, 5 , 7 },
{ 346, 28, 5 , 9 },
{ 359, 28, 5 , 7 },
{ 372, 28, 4 , 9 },
{ 384, 28, 5 , 9 },
{ 397, 28, 5 , 9 },
{ 410, 28, 1 , 9 },
{ 419, 28, 5 , 11 },
{ 432, 28, 5 , 9 },
{ 445, 28, 2 , 9 },
{ 455, 28, 7 , 7 },
{ 470, 28, 5 , 7 },
{ 483, 28, 5 , 7 },
{ 496, 28, 5 , 9 },
{ 4, 52, 5 , 9 },
{ 17, 52, 5 , 7 },
{ 30, 52, 5 , 7 },
{ 43, 52, 4 , 8 },
{ 55, 52, 5 , 7 },
{ 68, 52, 5 , 7 },
{ 81, 52, 7 , 7 },
{ 96, 52, 5 , 7 },
{ 109, 52, 5 , 9 },
{ 122, 52, 5 , 7 },
{ 135, 52, 4 , 9 },
{ 147, 52, 1 , 9 },
{ 156, 52, 4 , 9 },
{ 168, 52, 6 , 2 },
{ 182, 52, 1 , 9 },
{ 191, 52, 5 , 11 },
{ 204, 52, 6 , 9 },
{ 218, 52, 6 , 9 },
{ 232, 52, 5 , 9 },
{ 245, 52, 5 , 12 },
{ 258, 52, 5 , 9 },
{ 271, 52, 5 , 10 },
{ 284, 52, 7 , 9 },
{ 299, 52, 5 , 9 },
{ 312, 52, 6 , 5 },
{ 326, 52, 5 , 3 },
{ 339, 52, 7 , 9 },
{ 354, 52, 5 , 9 },
{ 367, 52, 4 , 4 },
{ 379, 52, 5 , 7 },
{ 392, 52, 5 , 9 },
{ 405, 52, 5 , 9 },
{ 418, 52, 5 , 12 },
{ 431, 52, 5 , 9 },
{ 444, 52, 7 , 9 },
{ 459, 52, 1 , 1 },
{ 468, 52, 5 , 10 },
{ 481, 52, 5 , 9 },
{ 494, 52, 5 , 9 },
{ 4, 76, 6 , 5 },
{ 18, 76, 9 , 9 },
{ 35, 76, 9 , 7 },
{ 52, 76, 5 , 11 },
{ 65, 76, 5 , 9 },
{ 78, 76, 5 , 12 },
{ 91, 76, 5 , 12 },
{ 104, 76, 5 , 12 },
{ 117, 76, 6 , 12 },
{ 131, 76, 5 , 11 },
{ 144, 76, 5 , 13 },
{ 157, 76, 9 , 9 },
{ 174, 76, 5 , 12 },
{ 187, 76, 5 , 12 },
{ 200, 76, 5 , 12 },
{ 213, 76, 5 , 12 },
{ 226, 76, 5 , 11 },
{ 239, 76, 2 , 12 },
{ 249, 76, 2 , 12 },
{ 259, 76, 3 , 12 },
{ 270, 76, 3 , 11 },
{ 281, 76, 6 , 9 },
{ 295, 76, 6 , 12 },
{ 309, 76, 5 , 12 },
{ 322, 76, 5 , 12 },
{ 335, 76, 5 , 12 },
{ 348, 76, 6 , 12 },
{ 362, 76, 5 , 11 },
{ 375, 76, 5 , 5 },
{ 388, 76, 7 , 9 },
{ 403, 76, 5 , 12 },
{ 416, 76, 5 , 12 },
{ 429, 76, 5 , 12 },
{ 442, 76, 5 , 11 },
{ 455, 76, 5 , 12 },
{ 468, 76, 5 , 9 },
{ 481, 76, 5 , 9 },
{ 494, 76, 5 , 10 },
{ 4, 100, 5 , 10 },
{ 17, 100, 5 , 10 },
{ 30, 100, 6 , 10 },
{ 44, 100, 5 , 9 },
{ 57, 100, 5 , 11 },
{ 70, 100, 9 , 7 },
{ 87, 100, 5 , 10 },
{ 100, 100, 5 , 10 },
{ 113, 100, 5 , 10 },
{ 126, 100, 5 , 10 },
{ 139, 100, 5 , 9 },
{ 152, 100, 2 , 10 },
{ 162, 100, 2 , 10 },
{ 172, 100, 3 , 10 },
{ 183, 100, 3 , 9 },
{ 194, 100, 6 , 9 },
{ 208, 100, 6 , 10 },
{ 222, 100, 5 , 10 },
{ 235, 100, 5 , 10 },
{ 248, 100, 5 , 10 },
{ 261, 100, 6 , 10 },
{ 275, 100, 5 , 9 },
{ 288, 100, 5 , 5 },
{ 301, 100, 7 , 7 },
{ 316, 100, 5 , 10 },
{ 329, 100, 5 , 10 },
{ 342, 100, 5 , 10 },
{ 355, 100, 5 , 9 },
{ 368, 100, 5 , 12 },
{ 381, 100, 5 , 11 },
{ 394, 100, 5 , 11 },
};
// Font glyphs info data
// NOTE: No glyphs.image data provided
static const GlyphInfo darkFontGlyphs[189] = {
{ 32, 0, 13, 4, { 0 }},
{ 33, 2, 4, 5, { 0 }},
{ 34, 2, 4, 7, { 0 }},
{ 35, 1, 4, 8, { 0 }},
{ 36, 1, 2, 7, { 0 }},
{ 37, 1, 4, 9, { 0 }},
{ 38, 1, 4, 7, { 0 }},
{ 39, 2, 4, 5, { 0 }},
{ 40, 3, 4, 7, { 0 }},
{ 41, 1, 4, 7, { 0 }},
{ 42, 1, 4, 7, { 0 }},
{ 43, 1, 6, 7, { 0 }},
{ 44, 1, 12, 5, { 0 }},
{ 45, 1, 8, 6, { 0 }},
{ 46, 2, 12, 5, { 0 }},
{ 47, 1, 4, 5, { 0 }},
{ 48, 1, 4, 7, { 0 }},
{ 49, 2, 4, 7, { 0 }},
{ 50, 1, 4, 7, { 0 }},
{ 51, 1, 4, 7, { 0 }},
{ 52, 1, 4, 7, { 0 }},
{ 53, 1, 4, 7, { 0 }},
{ 54, 1, 4, 7, { 0 }},
{ 55, 1, 4, 7, { 0 }},
{ 56, 1, 4, 7, { 0 }},
{ 57, 1, 4, 7, { 0 }},
{ 58, 2, 6, 5, { 0 }},
{ 59, 1, 6, 5, { 0 }},
{ 60, 1, 6, 5, { 0 }},
{ 61, 1, 7, 6, { 0 }},
{ 62, 1, 6, 5, { 0 }},
{ 63, 1, 4, 7, { 0 }},
{ 64, 1, 4, 9, { 0 }},
{ 65, 1, 4, 7, { 0 }},
{ 66, 1, 4, 7, { 0 }},
{ 67, 1, 4, 7, { 0 }},
{ 68, 1, 4, 7, { 0 }},
{ 69, 1, 4, 7, { 0 }},
{ 70, 1, 4, 7, { 0 }},
{ 71, 1, 4, 7, { 0 }},
{ 72, 1, 4, 7, { 0 }},
{ 73, 2, 4, 5, { 0 }},
{ 74, 1, 4, 7, { 0 }},
{ 75, 1, 4, 7, { 0 }},
{ 76, 1, 4, 7, { 0 }},
{ 77, 1, 4, 9, { 0 }},
{ 78, 1, 4, 7, { 0 }},
{ 79, 1, 4, 7, { 0 }},
{ 80, 1, 4, 7, { 0 }},
{ 81, 1, 4, 7, { 0 }},
{ 82, 1, 4, 7, { 0 }},
{ 83, 1, 4, 7, { 0 }},
{ 84, 1, 4, 7, { 0 }},
{ 85, 1, 4, 7, { 0 }},
{ 86, 1, 4, 7, { 0 }},
{ 87, 1, 4, 9, { 0 }},
{ 88, 1, 4, 7, { 0 }},
{ 89, 1, 4, 7, { 0 }},
{ 90, 1, 4, 7, { 0 }},
{ 91, 3, 4, 7, { 0 }},
{ 92, 1, 4, 5, { 0 }},
{ 93, 1, 4, 7, { 0 }},
{ 94, 1, 4, 7, { 0 }},
{ 95, 0, 14, 5, { 0 }},
{ 96, 1, 4, 5, { 0 }},
{ 97, 1, 6, 7, { 0 }},
{ 98, 1, 4, 7, { 0 }},
{ 99, 1, 6, 7, { 0 }},
{ 100, 1, 4, 7, { 0 }},
{ 101, 1, 6, 7, { 0 }},
{ 102, 1, 4, 6, { 0 }},
{ 103, 1, 6, 7, { 0 }},
{ 104, 1, 4, 7, { 0 }},
{ 105, 2, 4, 5, { 0 }},
{ 106, 1, 4, 7, { 0 }},
{ 107, 1, 4, 7, { 0 }},
{ 108, 2, 4, 5, { 0 }},
{ 109, 1, 6, 9, { 0 }},
{ 110, 1, 6, 7, { 0 }},
{ 111, 1, 6, 7, { 0 }},
{ 112, 1, 6, 7, { 0 }},
{ 113, 1, 6, 7, { 0 }},
{ 114, 1, 6, 7, { 0 }},
{ 115, 1, 6, 7, { 0 }},
{ 116, 1, 5, 6, { 0 }},
{ 117, 1, 6, 7, { 0 }},
{ 118, 1, 6, 7, { 0 }},
{ 119, 1, 6, 9, { 0 }},
{ 120, 1, 6, 7, { 0 }},
{ 121, 1, 6, 7, { 0 }},
{ 122, 1, 6, 7, { 0 }},
{ 123, 2, 4, 7, { 0 }},
{ 124, 2, 4, 5, { 0 }},
{ 125, 1, 4, 7, { 0 }},
{ 126, 1, 4, 8, { 0 }},
{ 161, 2, 6, 5, { 0 }},
{ 162, 1, 4, 7, { 0 }},
{ 163, 1, 4, 8, { 0 }},
{ 8364, 1, 4, 8, { 0 }},
{ 165, 1, 4, 7, { 0 }},
{ 352, 1, 1, 7, { 0 }},
{ 167, 2, 4, 9, { 0 }},
{ 353, 1, 3, 7, { 0 }},
{ 169, 1, 4, 9, { 0 }},
{ 170, 2, 4, 9, { 0 }},
{ 171, 1, 6, 8, { 0 }},
{ 172, 1, 8, 7, { 0 }},
{ 174, 1, 4, 9, { 0 }},
{ 175, 2, 4, 9, { 0 }},
{ 176, 1, 4, 6, { 0 }},
{ 177, 1, 6, 7, { 0 }},
{ 178, 2, 4, 9, { 0 }},
{ 179, 2, 4, 9, { 0 }},
{ 381, 1, 1, 7, { 0 }},
{ 181, 1, 6, 7, { 0 }},
{ 182, 1, 4, 9, { 0 }},
{ 183, 2, 8, 5, { 0 }},
{ 382, 1, 3, 7, { 0 }},
{ 185, 2, 4, 9, { 0 }},
{ 186, 2, 4, 9, { 0 }},
{ 187, 1, 6, 8, { 0 }},
{ 338, 1, 4, 11, { 0 }},
{ 339, 1, 6, 11, { 0 }},
{ 376, 1, 2, 7, { 0 }},
{ 191, 1, 6, 7, { 0 }},
{ 192, 1, 1, 7, { 0 }},
{ 193, 1, 1, 7, { 0 }},
{ 194, 1, 1, 7, { 0 }},
{ 195, 1, 1, 7, { 0 }},
{ 196, 1, 2, 7, { 0 }},
{ 197, 1, 0, 7, { 0 }},
{ 198, 1, 4, 11, { 0 }},
{ 199, 1, 4, 7, { 0 }},
{ 200, 1, 1, 7, { 0 }},
{ 201, 1, 1, 7, { 0 }},
{ 202, 1, 1, 7, { 0 }},
{ 203, 1, 2, 7, { 0 }},
{ 204, 1, 1, 5, { 0 }},
{ 205, 2, 1, 5, { 0 }},
{ 206, 1, 1, 5, { 0 }},
{ 207, 1, 2, 5, { 0 }},
{ 208, 0, 4, 7, { 0 }},
{ 209, 1, 1, 7, { 0 }},
{ 210, 1, 1, 7, { 0 }},
{ 211, 1, 1, 7, { 0 }},
{ 212, 1, 1, 7, { 0 }},
{ 213, 1, 1, 7, { 0 }},
{ 214, 1, 2, 7, { 0 }},
{ 215, 1, 6, 7, { 0 }},
{ 216, 0, 4, 7, { 0 }},
{ 217, 1, 1, 7, { 0 }},
{ 218, 1, 1, 7, { 0 }},
{ 219, 1, 1, 7, { 0 }},
{ 220, 1, 2, 7, { 0 }},
{ 221, 1, 1, 7, { 0 }},
{ 222, 1, 4, 7, { 0 }},
{ 223, 1, 4, 7, { 0 }},
{ 224, 1, 3, 7, { 0 }},
{ 225, 1, 3, 7, { 0 }},
{ 226, 1, 3, 7, { 0 }},
{ 227, 1, 3, 7, { 0 }},
{ 228, 1, 4, 7, { 0 }},
{ 229, 1, 2, 7, { 0 }},
{ 230, 1, 6, 11, { 0 }},
{ 231, 1, 6, 7, { 0 }},
{ 232, 1, 3, 7, { 0 }},
{ 233, 1, 3, 7, { 0 }},
{ 234, 1, 3, 7, { 0 }},
{ 235, 1, 4, 7, { 0 }},
{ 236, 1, 3, 5, { 0 }},
{ 237, 2, 3, 5, { 0 }},
{ 238, 1, 3, 5, { 0 }},
{ 239, 1, 4, 5, { 0 }},
{ 240, 1, 4, 7, { 0 }},
{ 241, 1, 3, 7, { 0 }},
{ 242, 1, 3, 7, { 0 }},
{ 243, 1, 3, 7, { 0 }},
{ 244, 1, 3, 7, { 0 }},
{ 245, 1, 3, 7, { 0 }},
{ 246, 1, 4, 7, { 0 }},
{ 247, 1, 6, 7, { 0 }},
{ 248, 0, 6, 7, { 0 }},
{ 249, 1, 3, 7, { 0 }},
{ 250, 1, 3, 7, { 0 }},
{ 251, 1, 3, 7, { 0 }},
{ 252, 1, 4, 7, { 0 }},
{ 253, 1, 3, 7, { 0 }},
{ 254, 1, 4, 7, { 0 }},
{ 255, 1, 4, 7, { 0 }},
};
// Style loading function: Dark
static void GuiLoadStyleDark(void)
{
// Load style properties provided
// NOTE: Default properties are propagated
for (int i = 0; i < DARK_STYLE_PROPS_COUNT; i++)
{
GuiSetStyle(darkStyleProps[i].controlId, darkStyleProps[i].propertyId, darkStyleProps[i].propertyValue);
}
// Custom font loading
// NOTE: Compressed font image data (DEFLATE), it requires DecompressData() function
int darkFontDataSize = 0;
unsigned char *data = DecompressData(darkFontData, DARK_STYLE_FONT_ATLAS_COMP_SIZE, &darkFontDataSize);
Image imFont = { data, 512, 256, 1, 2 };
Font font = { 0 };
font.baseSize = 16;
font.glyphCount = 189;
// Load texture from image
font.texture = LoadTextureFromImage(imFont);
UnloadImage(imFont); // Uncompressed image data can be unloaded from memory
// Copy char recs data from global fontRecs
// NOTE: Required to avoid issues if trying to free font
font.recs = (Rectangle *)RAYGUI_MALLOC(font.glyphCount*sizeof(Rectangle));
memcpy(font.recs, darkFontRecs, font.glyphCount*sizeof(Rectangle));
// Copy font char info data from global fontChars
// NOTE: Required to avoid issues if trying to free font
font.glyphs = (GlyphInfo *)RAYGUI_MALLOC(font.glyphCount*sizeof(GlyphInfo));
memcpy(font.glyphs, darkFontGlyphs, font.glyphCount*sizeof(GlyphInfo));
GuiSetFont(font);
// Setup a white rectangle on the font to be used on shapes drawing,
// it makes possible to draw shapes and text (full UI) in a single draw call
Rectangle fontWhiteRec = { 510, 254, 1, 1 };
SetShapesTexture(font.texture, fontWhiteRec);
//-----------------------------------------------------------------
// TODO: Custom user style setup: Set specific properties here (if required)
// i.e. Controls specific BORDER_WIDTH, TEXT_PADDING, TEXT_ALIGNMENT
}

View File

@@ -0,0 +1,11 @@
#ifndef _RAYGUI_HELPERS_HPP
#define _RAYGUI_HELPERS_HPP
#include <string>
/* Display the given text, centered on the screen, as a label.
NEEDS RAYGUI LIBRARY. */
void display_text_raygui(std::string to_disp);
/* Display the given string, and exit the game after 'time' seconds. */
void display_and_exit_raygui(std::string to_disp, int time);
#endif

View File

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

View File

@@ -1,8 +0,0 @@
#ifndef _MATH_HELP
#define _MATH_HELP
int signum(int num) {
int retval = 0;
(num > 0) ? retval = 1 : retval = -1;
return retval;
}
#endif

View File

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

30
includes/timer.h Normal file
View File

@@ -0,0 +1,30 @@
#ifdef __cplusplus
extern "C" {
#endif
#ifndef _TIMER_H
#define _TIMER_H
#include <stdbool.h>
/* This file defines a simple timer Types, and declares functions to initialize it,
and keep track of time elapsed. The actual definition of the Timer struct is in timer.c
It was copied from https://github.com/raysan5/raylib/wiki/Frequently-Asked-Questions#how-do-i-make-a-timer */
typedef struct Timer_s {
double start_time; // Start time (seconds)
double lifetime; // Lifetime (seconds)
} Timer;
/* Starts a timer for given number of seconds */
Timer timer_init(double lifetime_secs);
/* Returns true when timer finishes, false if not */
bool timer_done(Timer timer);
/* Returns amount of time elapsed since start of timer */
double timer_get_elapsed(Timer timer);
#endif
#ifdef __cplusplus
}
#endif

376
main.cpp
View File

@@ -14,20 +14,28 @@
#include <iostream>
#define _USE_MATH_DEFINES
#include <cmath>
#include <cstring>
#include <ctime>
#include <cerrno>
#include <sstream>
#include "includes/raylib-cpp/raylib-cpp.hpp"
#define RAYGUI_IMPLEMENTATION
#include "includes/raygui/raygui.h"
#include "includes/raygui/style_dark.h"
#include "includes/paddle.hpp"
#include "includes/ball.hpp"
#include "includes/easysock.hpp"
#include "includes/sign.hpp"
#include "includes/connect_code.hpp"
#include "includes/client.hpp"
#include "includes/server.hpp"
#include "includes/exception_consts.hpp"
#include "includes/check_input.hpp"
#include "includes/display_text.hpp"
#include "includes/easysock.h"
#include "includes/serialization.h"
#include "includes/timer.h"
/* Global variables used to instantiate structs */
const int WIDTH = 1500;
@@ -38,21 +46,33 @@ const int PADDLE_SPEED = 8;
const int CIRC_RAD = 10;
const float BASE_BOUNCE_DEG = 45;
const float BASE_BOUNCE_RAD = (BASE_BOUNCE_DEG / 180.0) * M_PI;
const float BASE_SPEED_COMPONENTS = 18;
const float BASE_SPEED_COMPONENTS = 15;
const float BASE_SPEED = sqrt(powf(BASE_SPEED_COMPONENTS, 2) * 2);
typedef enum {M_SINGLE, M_CLIENT, M_SERVER} Mode;
std::string HELP_TEXT = "\nnetpong - A networked pong game for the internet era.\n"
"\n"
"Usage: \n"
"netpong [MODE] [ADDRESS PORT]|[CODE]\n"
"\n"
"MODE: \n"
"-S : Server mode. Starts a server to allow the other player to connect.\n"
"IP address and port must be specified.\n"
"\n"
"-C: Client mode. Connects to a server, using the provided connection code.\n"
"\n"
"If no mode is specified, single player mode is used as default.\n"
"\n"
"CONTROLS:"
"\'W\' and \'S\' control left paddle (AKA client paddle)\n"
"Up and Down arrow keys control right paddle (AKA server paddle)\n";
/* This struct contains a Mode enum, which indicates the type of game we are
playing (Single player, client mode or server mode). The netsock parameter is
a 'Sock' object - Client and Server classes inherit from this object, so this
parameter can be instantiated to either a client or server, depending on the
game type. */
typedef struct {
Mode mode;
Sock* netsock;
} GameType;
/* Simple function to return 1 if a value is positive, and -1 if it is negative */
int signum(int num) {
int retval = 0;
(num > 0) ? retval = 1 : retval = -1;
return retval;
}
raylib::Vector2 changeVelocityAfterCollision(Paddle paddle, Ball ball) {
float paddle_mid_y = (paddle.getRect().y + paddle.getRect().GetHeight()) / 2.0; /* Middle y value of rectangle */
@@ -74,137 +94,68 @@ raylib::Vector2 changeVelocityAfterCollision(Paddle paddle, Ball ball) {
return raylib::Vector2(new_x_vel, new_y_vel);
}
/* This function checks the command-line arguments passed to the program.
It then decides whether the game is in Server or Client mode (or neither), and
instantiates the appropriate object. The (uninitialized) objects are passed to the
function as pointers. It returns a GameType struct, that indicates whether the game
is in server, client or single player mode, and contains the appropriate socket object. */
GameType check_server_client(int argc, char** argv) {
std::string connect_code;
std::vector<std::string> addr_port; /* Vector to store (IPv4) address and port */
GameType type;
if (argc < 2) { /* Game was not started in client or server mode */
type.mode = M_SINGLE;
type.netsock = nullptr;
return type;
/* Checks the number and type of the command-line arguments. Throws an exception
if the args are invalid. DOES NOT PROCESS VALID ARGUMENTS. */
void check_num_args(int argc, char** argv) {
if (argc > 4) {
throw std::invalid_argument("ARGUMENT ERROR: Too many arguments. To view syntax, use -h or --help.");
}
/* GAME STARTED IN CLIENT MODE */
if (strcmp(argv[1],"-C") == 0) {
if (argc < 3) { /* No address was provided */
throw EXCEPT_TOOFEWARGS;
}
connect_code = std::string(argv[2]); /* The connect code is a special string, that contains the server address and port. It is given by the server. */
try {
addr_port = connect_code::decode(connect_code);
Client* client = new Client(4, ES_UDP, addr_port[0].data(), std::stoi(addr_port[1]));
client->create_socket();
/* Send a specific message to the server, and wait for the appropriate response, to know that the server is ready */
client->sendAll("GG");
std::string msg_from_server = client->recvAll();
if (msg_from_server == "U2") {
std::cout << "Connection made. Waiting for server to begin game..." << std::endl;
} else {
throw EXCEPT_WRONGRESPONSE;
if (argc > 1) { // Either server or client mode
if (std::string(argv[1]) == "-S") {
if (argc < 4) { // Server mode but no address and/or port
throw std::invalid_argument("ARGUMENT ERROR: Server mode specified without any address or port.");
}
type.mode = M_CLIENT;
type.netsock = client;
return type;
} catch (int e) {
throw;
} catch (std::exception& e) {
throw;
}
}
/* GAME STARTED IN SERVER MODE */
else if (strcmp(argv[1],"-S") == 0) {
std::string addr;
uint16_t port;
/* No IP address or port specified */
if (argc < 3) {
throw EXCEPT_TOOFEWARGS;
else if (std::string(argv[1]) == "-C") {
if (argc < 3) { // Client mode but no code
throw std::invalid_argument("ARGUMENT ERRROR: Client mode specified without any code.");
}
}
/* IP address but no port */
else if (argc < 4) {
std::cout << "No port specified, using 6500..." << std::endl;
addr = std::string(argv[2]);
port = 6500;
else if (std::string(argv[1]) == "-h" || std::string(argv[1]) == "--help") {
throw std::invalid_argument(HELP_TEXT); // I am abusing the exception mechanism here, so that I can ensure that the caller quits the program after printing the help message.
} else {
addr = std::string(argv[2]);
port = std::stoi(std::string(argv[3]));
throw std::invalid_argument("Unrecognized argument.");
}
/* Check if IP is valid */
if (check_ip_ver(addr.data()) < 0) {
throw EXCEPT_INVALIDIP;
}
std::string code = connect_code::encode(addr, std::to_string(port));
std::cout << "Your code is " << code << std::endl;
/* Create server socket and wait for client to connect */
Server* server = new Server(4, ES_UDP, addr.data(), port);
server->create_socket();
std::cout << "Waiting for connection..." << std::endl;
std::string response = "";
char* temp_response = NULL;
/* Wait for the right client to connect. Since recvAll returns a char*, we need to create a temporary variable to check for NULL. */
do {
temp_response = server->recvAll();
} while (temp_response == NULL);
response = std::string(temp_response);
std::cout << "Connection received from " << server->get_peer_addr() << std::endl;
server->sendAll("U2");
type.mode = M_SERVER;
type.netsock = server;
return type;
}
else {
throw EXCEPT_INVALIDARGS;
}
return;
}
int main(int argc, char** argv) {
/* Check if game was started in server or client mode, and set appropriate variables */
/* GameType struct, to define whether the game is in single or muilti-player mode, and
/* Check the number and validity of command-line arguments. Invalid arguments
will throw an exception. */
try {
check_num_args(argc, argv);
} catch (std::invalid_argument& inv) {
std::cout << inv.what() << std::endl;
return -1;
}
/* From here on, we assume that:
a. The program was started with no arguments (User is prompted in GUI), OR
b. The program was started in server mode, and an additional was given, OR
c. The program was started in client mode, and an additional argument was given. */
/* GameType struct, to define whether the game is in single or multi-player mode, and
to hold the appropriate socket */
GameType type;
try {
type = check_server_client(argc, argv);
} catch(int e) {
if (e == EXCEPT_TOOFEWARGS) {
std::cout << "Started in client mode, but no address was specified." << std::endl;
return -1;
/* Check if game was started in server or client mode, and call the appropriate function to process the arguments.
If game was started in single-player mode (i.e. with no arguments), then the user is prompted in the GUI. */
try { // I put this try-catch block outside the if-statement because the exception handling is the same for both client and server.
if (argc > 1) { // Server or client mode
if (std::string(argv[1]) == "-S") { // Server mode
type = check_server(argv[2], argv[3], IF_CLI);
}
if (std::string(argv[1]) == "-C") { // Client mode
type = check_client(argv[2], IF_CLI);
}
}
if (e == EXCEPT_INVALIDARGS) {
std::cout << "Invalid argument." << std::endl;
return -2;
}
if (e == EXCEPT_INVALIDIP) {
std::cout << "Invalid IP address provided." << std::endl;
return -5;
}
if (e == EXCEPT_WRONGRESPONSE) {
std::cout << "The server didn't respond with the correct message. Are you sure you have used the right server?" << std::endl;
return -6;
}
else {
std::cout << strerror(e) << std::endl;
return -7;
}
} catch(std::invalid_argument& inv) {
std::cout << inv.what() << std::endl;
return -8;
} catch (std::invalid_argument& inv) {
std::cout << inv.what() << std::endl;
return -1;
} catch (int err) {
std::cout << strerror(err) << std::endl;
return -1;
}
/* Initialize window and other variables */
@@ -217,6 +168,154 @@ int main(int argc, char** argv) {
bool game_started = false;
srand(std::time(NULL));
/* If there were no command-line arguments, the user is prompted in the GUI */
if (argc == 1) {
/* Display a drop-down menu, to allow user to pick between Single player, server and client. This section of the code uses the raygui library, and is written in C. */
GuiLoadStyleDark(); // Load the dark theme style
/* Modify the default style, by changing font size and spacing */
int font_size = 25;
int font_spacing = 2;
GuiSetStyle(DEFAULT, TEXT_SIZE, font_size);
GuiSetStyle(DEFAULT, TEXT_SPACING, font_spacing);
/* Set variables to position objects on screen */
int selected_item = 0; // variable to hold the index of the selected item
const char* text_to_display = "Select Game Mode"; // Text to display
/* Size of the label, drop down box and button */
Vector2 label_size = MeasureTextEx(GetFontDefault(), text_to_display, font_size, font_spacing); // Set the size based on the width of the string to print, the font size and the text spacing. I added 1 to font_size and font_spacing, to account for any possible rounding errors, since the function expects floats.
Vector2 box_size = Vector2{label_size.x, HEIGHT / 20};
bool is_being_edited = false; // Indicates whether the drop-down menu is being 'edited' i.e. whether an option is being selected
bool button_pressed = false; // Indicates whether the submit button has been pressed
while (button_pressed == false) {
if (WindowShouldClose()) {
CloseWindow();
return 0;
}
BeginDrawing();
ClearBackground(BLACK);
GuiLabel(Rectangle{(WIDTH/2)-(label_size.x/2), (HEIGHT/8), label_size.x, label_size.y}, text_to_display); // Label to display text on top
if (is_being_edited) {
GuiLock(); // If the drop-down menu is being 'edited', we need to prevent the user from modifying any other aspect of the UI
}
/* Button that allows user to proceed */
button_pressed = GuiButton(Rectangle{(WIDTH/2)-(box_size.x/2), (HEIGHT/2) + (HEIGHT/8), box_size.x, box_size.y}, "Continue");
/* Drop-down menu, that allows user to select game mode */
if (GuiDropdownBox(Rectangle{(WIDTH/2) - (box_size.x/2), (HEIGHT/2) - (HEIGHT/8), box_size.x, box_size.y}, "SINGLE;CLIENT;SERVER", &selected_item, is_being_edited)) { // This function returns != 0 if there was a mouse click inside the dropdown area
is_being_edited = !is_being_edited; // If the dropdown menu was selected, then it is being edited (or not being edited, if it previously was).
}
GuiUnlock();
EndDrawing();
}
/* Single player mode */
if (selected_item == M_SINGLE) {
GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // Enable text wrapping so that the long text, displayed below, will be wrapped
BeginDrawing();
ClearBackground(BLACK);
GuiLabel(Rectangle{(WIDTH/2)-(WIDTH/8), (HEIGHT/2)-(HEIGHT/8), WIDTH/4, HEIGHT/4}, "W and S control left paddle, Up and Down arrow keys control right paddle. Good luck!");
EndDrawing();
Timer timer = timer_init(5);
while (!timer_done(timer));
}
/* Server mode, ask user to input IP address and port */
if (selected_item == M_SERVER) {
button_pressed = false; // Whether submit button is pressed
char* ip_text = (char *)calloc(150, sizeof(char)); // Holds input of IP text box
char* port_text = (char *)calloc(20, sizeof(char)); // Holds input of port text box
const char* ip_label = "Local IP address";
const char* port_label = "Port number (1024 - 65535)";
int port_label_x_size = MeasureTextEx(GetFontDefault(), port_label, font_size, font_spacing).x; // Custom size for port label, because it's long
bool editing_ip = false; // Indicates whether the IP address text box is being edited
bool editing_port = false; // Indicates whether the port text box is being edited
while (button_pressed == false || ((strlen(ip_text) == 0) || (strlen(port_text) == 0))) {
if (WindowShouldClose()) {
CloseWindow();
return 0;
}
BeginDrawing();
ClearBackground(BLACK);
/* Label and text box for IP address */
GuiLabel(Rectangle{(WIDTH/2)-(label_size.x/2), (HEIGHT/2) - (HEIGHT/6) - label_size.y - 10, label_size.x, label_size.y}, ip_label); // Label to display text on top
/* The reason this if statement exists, is largely the same as the reasoning for the drop-down menu. We want to make the text box editable
if it has been clicked. If it is already editable, we want to make it read-only if the user clicks outside the box. This functionality
is mostly handled in the GuiTextBox function. If the text box is in edit mode, this function returns nonzero if the user clicks INSIDE
the box. If the text box is in editable mode, this function returns nonzero if the user clicks OUTSIDE the box. */
if (GuiTextBox(Rectangle{(WIDTH/2) - (box_size.x/2), (HEIGHT/2) - (HEIGHT/6), box_size.x, box_size.y}, ip_text, 100, editing_ip)) {
editing_ip = !editing_ip;
}
/* Label and text box for port. See above for explanation of if statement. */
GuiLabel(Rectangle{(WIDTH/2)-(label_size.x/2), (HEIGHT/2) - label_size.y, port_label_x_size }, port_label); // Label to display text on top
if (GuiTextBox(Rectangle{(WIDTH/2) - (box_size.x/2), (HEIGHT/2), box_size.x, box_size.y}, port_text, 100, editing_port)) {
editing_port = !editing_port;
}
button_pressed = GuiButton(Rectangle{(WIDTH/2) - (box_size.x/2), (HEIGHT/2) + (HEIGHT/6), box_size.x, box_size.y}, "Start Server");
EndDrawing();
}
try {
type = check_server(ip_text, port_text, IF_GUI);
} catch (int e) {
display_and_exit_raygui(std::string(std::strerror(e)) + "\nClosing game...", 2); // The server constructor throws the errno if it cannot create a socket
free(ip_text);
free(port_text);
return -1;
} catch (std::invalid_argument& inv) {
display_and_exit_raygui(std::string(inv.what()) + "\nClosing game...", 2);
free(ip_text);
free(port_text);
return -1;
}
free(ip_text);
free(port_text);
}
if (selected_item == M_CLIENT) {
button_pressed = false; // Whether submit button is pressed
char* code_text = (char *)calloc(150, sizeof(char)); // Holds the connect code
const char* code_label = "Enter code:";
bool editing_code = false; // Indicates whether the port text box is being edited
while (button_pressed == false || ((strlen(code_text) == 0))) {
if (WindowShouldClose()) {
CloseWindow();
return 0;
}
BeginDrawing();
ClearBackground(BLACK);
/* Label and text box for IP address */
GuiLabel(Rectangle{(WIDTH/2)-(label_size.x/2), (HEIGHT/2) - (HEIGHT/6) - label_size.y - 10, label_size.x, label_size.y}, code_label);
if (GuiTextBox(Rectangle{(WIDTH/2) - (box_size.x/2), (HEIGHT/2) - (HEIGHT/6), box_size.x, box_size.y}, code_text, 100, editing_code)) {
editing_code = !editing_code;
}
button_pressed = GuiButton(Rectangle{(WIDTH/2) - (box_size.x/2), (HEIGHT/2) + (HEIGHT/6), box_size.x, box_size.y}, "Connect");
EndDrawing();
}
try {
type = check_client(code_text, IF_GUI);
} catch (int e) {
display_and_exit_raygui(std::string(std::strerror(e)) + "\nClosing game...", 2); // The client constructor throws the errno if it cannot create a socket
return -1;
} catch (std::invalid_argument& inv) {
display_and_exit_raygui(std::string(inv.what()) + "\nClosing game...", 2);
return -1;
}
free(code_text);
}
}
/* Variable to store the response given by the other player */
std::string response;
Serial_Data response_data;
@@ -233,6 +332,7 @@ int main(int argc, char** argv) {
Ball ball = Ball(window.GetWidth()/2, window.GetHeight()/2, CIRC_RAD, BASE_SPEED, 0);
window.BeginDrawing();
window.ClearBackground(BLACK);
pad1.draw();
pad2.draw();
ball.draw();
@@ -251,11 +351,18 @@ int main(int argc, char** argv) {
}
}
/* For client (wait for start message from server) */
/* For client (wait for start or quit message from server): When the peer quits the
game, it sends a serialized struct, containing all zeros, with the last bit turned
on as a flag. We catch this zero bit, as it indicates that the peer quit the game. */
if (type.mode == M_CLIENT) {
do {
response = type.netsock->recvAll();
} while (response[0] != 'S');
} while (response[0] != 'S' && response[0] != 0);
if (response[0] == 0) {
CloseWindow();
std::cout << "Peer unexpectedly quit game." << std::endl;
return -1;
}
game_started = true;
std::cout << "Game has been started by server." << std::endl;
}
@@ -281,7 +388,6 @@ int main(int argc, char** argv) {
uint8_t* response_array = (uint8_t *)(type.netsock->recvAll());
if (response_array != NULL) {
response_data = Serial_deserialize(response_array);
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;

View File

@@ -1,21 +1,25 @@
project('Pong', ['cpp', 'c'], version: '0.1')
add_global_arguments('-g', '-Wall', '-pedantic', '-Wno-unused-function', language : ['cpp', 'c'])
add_global_arguments('-g', '-Wall', '-pedantic', '-Wno-unused-function', '-Wno-narrowing', language : ['cpp', 'c'])
add_global_arguments('-std=c++11', language: ['cpp'])
compiler = meson.get_compiler('cpp')
cmake = import('cmake')
# if we are building a shared library
if get_option('default_library') == 'shared'
raylib = dependency('raylib', required: false) # Try to find dependency with pkg-config
if not raylib.found()
raylib = compiler.find_library('raylib', has_headers: ['raylib.h', 'raymath.h'], required: true) # Try to manually search for the dependency
raylib = compiler.find_library('raylib', has_headers: ['raylib.h', 'raymath.h'], required: false) # 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
# 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
# I we are building a static library
if get_option('default_library') == 'static'
raylib_proj = cmake.subproject('raylib')
raylib = raylib_proj.dependency('raylib')
@@ -25,12 +29,12 @@ endif
ws2_dep = compiler.find_library('ws2_32', required: false)
winmm = compiler.find_library('winmm', required: false)
if build_machine.system() == 'windows'
add_global_arguments('-Wl,--subsystem,windows', '-mwindows', language: ['cpp', 'c']) # Prevent opening console when game is run
add_project_arguments('-Wl,--subsystem,windows', '-mwindows', language: ['cpp', 'c']) # Prevent opening console when game is run
endif
executable('pong',
'main.cpp', 'easysock.cpp', 'sock.cpp','paddle.cpp', 'ball.cpp', 'numeric_base.cpp', 'connect_code.cpp', 'server.cpp', 'client.cpp',
'serialization.c',
executable('pong',
'main.cpp', 'sock.cpp','paddle.cpp', 'ball.cpp', 'numeric_base.cpp', 'connect_code.cpp', 'server.cpp', 'client.cpp', 'check_input.cpp', 'raygui_helpers.cpp', 'display_text.cpp',
'serialization.c', 'timer.c', 'easysock.c',
dependencies: [raylib, ws2_dep, winmm]
)

View File

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

20
raygui_helpers.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "includes/raygui_helpers.hpp"
#include "includes/raygui/raygui.h"
#include "includes/timer.h"
void display_text_raygui(std::string to_disp) {
const char* to_disp_cstr = to_disp.c_str();
Vector2 label_size = MeasureTextEx(GetFontDefault(), to_disp_cstr, GuiGetStyle(DEFAULT, TEXT_SIZE)+1, GuiGetStyle(DEFAULT, TEXT_SPACING)+1); // The '+1' is there to account for any rounding errors
BeginDrawing();
ClearBackground(BLACK);
GuiLabel(Rectangle{(GetScreenWidth()/2) - (label_size.x/2), (GetScreenHeight()/2) - (label_size.y/2), label_size.x, label_size.y}, to_disp_cstr);
EndDrawing();
return;
}
void display_and_exit_raygui(std::string to_disp, int time) {
display_text_raygui(to_disp);
Timer timer = timer_init(time);
while (!timer_done(timer));
return;
}

View File

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

View File

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

View File

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

19
timer.c Normal file
View File

@@ -0,0 +1,19 @@
#include <stdbool.h>
#include "includes/timer.h"
#include "includes/raygui/raygui.h"
Timer timer_init(double lifetime_secs) {
Timer timer;
timer.start_time = GetTime();
timer.lifetime = lifetime_secs;
return timer;
}
bool timer_done(Timer timer) {
return GetTime() - timer.start_time >= timer.lifetime;
}
double timer_get_elapsed(Timer timer) {
return GetTime() - timer.start_time;
}

View File

@@ -1,2 +1,10 @@
3. Try to make the ball go between screens.
8. Have both client and server send a message if the game is quit.
1. Sign Windows executable, to remove 'Unknown Publisher' warnings.
2. Add 'install' target to Meson, to allow the user to install the game. This should also copy the .so files to the right locations.
3. Use free() to free allocated memory.
4. Use the struct to establish a connection, and to start each round (instead of sending strings).
5. Figure out how to build statically-linked Mac binary, and create a build script for packaging it.
6. Communicate the paddle reset position to the peer, after a round.
7. Clean up / refactor the raygui code in main.cpp, that asks user for game mode. Instead of just having a giant blob of code in main.cpp, maybe split it into a function, or move it to another file. It should be easy to split it into a different function, since none of the functions take any specific parameters. The text box function, for example, only takes in the rectangle coordinates, and the text to display. I can move the code to a function, and then pass in any parameters that I need to pass in (I don't think I need to pass many parameters, though).
8. Allow the user to specify which paddle they want to control, in multi-player mode.
9. Try to make the ball go between screens.
10. Change the networking code, so that a single server can connect two clients with each other. The server should provide player 1 with a code, and player 2 can connect with player 1 using that code (essentially like a room).