Compare commits

3 Commits

33 changed files with 272 additions and 7027 deletions

1
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

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

View File

@@ -1,41 +0,0 @@
## Netpong - A Pong game for the internet era
__Netpong__ is a network-enabled Pong game, written in C++. It enables two players to play against each other, provided an IP address and a port. It also supports a single-player mode.
## How it works
The game has only one runtime dependency: The [raylib](https://www.raylib.com/) graphics 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. 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/
meson compile -C build -Ddefault_library=static
```
## Running
- To run in single-player mode:
- Run the application with no arguments: `build/pong`
- Left paddle is controlled with `W` and `S` keys, right paddle is controlled with `Up` and `Down` arrow keys.
- To run in multi-player mode:
- One player runs the application in Server mode, specifying their IP address and a port: `build/pong -S <ip_address> <port>`
- The other player connects to the first player by running in Client mode, specifying the first player's IP address and port: `build/pong -C <ip_address> <port>`.
- The server controls the left paddle by default (WIP to allow the user to modify this), and the client controls the right paddle.
## TODO
See todo.txt.

View File

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

View File

@@ -1,21 +1,10 @@
#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 {
@@ -56,67 +45,11 @@ namespace connect_code {
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) {
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);
/* 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);
}
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);
@@ -128,58 +61,22 @@ 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."); // There must be an underscore, to separate the address part from the port part
throw std::invalid_argument("Invalid code entered.");
}
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 32) */
std::string port = result[1]; /* Port (in base 32) */
std::string address = result[0]; /* Address (in base 16) */
std::string port = result[1]; /* Port (in base 16) */
std::vector<std::string> ret_val;
/* 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 */
/* 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);
/* Create a vector containing the address and the port, which will be returned */
std::vector<std::string> ret_val;
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,35 +0,0 @@
#!/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"

View File

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

View File

@@ -1,13 +0,0 @@
#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,9 +1,8 @@
#include "includes/easysock.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include "includes/easysock.hpp"
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cerrno>
#include <fcntl.h>
#ifndef _WIN32
@@ -52,23 +51,22 @@ SOCKET create_socket(int network, char transport) {
int newSock = socket(domain,type,0);
/* Set REUSEADDR flag for TCP, allowing program to be run twice */
if (transport == ES_TCP) {
/* 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));
}
return newSock;
}
int create_addr(int network, const char* address, int port,struct sockaddr_storage* dest) {
int create_addr(int network, const char* address, int port,struct sockaddr* 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((struct sockaddr *)dest,&listen_address,sizeof(listen_address));
memcpy(dest,&listen_address,sizeof(listen_address));
return 0;
} else if (network == 6) {
@@ -76,7 +74,7 @@ int create_addr(int network, const char* address, int port,struct sockaddr_stora
listen_ipv6.sin6_family = AF_INET6;
listen_ipv6.sin6_port = htons(port);
inet_pton(AF_INET6,address,&listen_ipv6.sin6_addr);
memcpy((struct sockaddr_in6 *)dest,&listen_ipv6,sizeof(listen_ipv6));
memcpy(dest,&listen_ipv6,sizeof(listen_ipv6));
return 0;
} else {
@@ -85,7 +83,7 @@ int create_addr(int network, const char* address, int port,struct sockaddr_stora
}
SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr_storage* addr_struct) {
SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr* addr_struct) {
int socket = create_socket(network,transport);
if (socket < 0) {
return (-1 * errno);
@@ -104,14 +102,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,(struct sockaddr *)addr_struct,(socklen_t)addrlen);
int i = bind (socket,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_storage* remote_addr_struct) {
SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr* remote_addr_struct) {
struct addrinfo hints; /* Used to tell getaddrinfo what kind of address we want */
struct addrinfo* results; /* Used by getaddrinfo to store the addresses */
@@ -131,7 +129,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 = (struct sockaddr_storage *)results->ai_addr;
remote_addr_struct = results->ai_addr;
network = inet_to_int(results->ai_family);
} else {
create_addr(network,address,port,remote_addr_struct);
@@ -189,29 +187,6 @@ 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;

View File

@@ -1,30 +0,0 @@
#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(char protocol, const char* address, int port) : Sock(protocol, address, port) {}
Client(int ip_ver, char protocol, const char* address, int port) : Sock(ip_ver, protocol, address, port) {}
void create_socket() override;

View File

@@ -1,10 +0,0 @@
#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,18 +1,12 @@
#ifdef __cplusplus
extern "C" {
#endif
#ifndef EASYSOCK_H_
#define EASYSOCK_H_
#ifndef EASYSOCK_HPP_
#define EASYSOCK_HPP_
#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
#if defined(__unix) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
#ifdef linux
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
@@ -50,7 +44,7 @@ and dest is a pointer to the sockaddr struct that will be filled in.
The function returns with -202 if the network parameter contained neither '4'
nor '6'. */
int create_addr(int network, const char* address, int port,struct sockaddr_storage* dest);
int create_addr(int network, const char* address, int port,struct sockaddr* dest);
@@ -60,7 +54,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_storage* addr_struct);
SOCKET create_local (int network, char transport, const char* address, int port,struct sockaddr* addr_struct);
/* This function utilizes the same functions as 'create_local' but _connects_ to the
@@ -69,19 +63,13 @@ 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_storage* remote_addr_struct);
SOCKET create_remote (int network,char transport, const char* address,int port,struct sockaddr* remote_addr_struct);
/* check_ip_ver - This function checks if the given string is an IPv4 address (returns 4),
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. */
@@ -104,6 +92,3 @@ int sock_quit(void);
int sock_close(SOCKET);
#endif
#ifdef __cplusplus
}
#endif

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,589 +0,0 @@
//////////////////////////////////////////////////////////////////////////////////
// //
// 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

@@ -1,11 +0,0 @@
#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

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

View File

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

8
includes/sign.hpp Normal file
View File

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

View File

@@ -1,30 +0,0 @@
#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

408
main.cpp
View File

@@ -14,28 +14,20 @@
#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;
@@ -46,33 +38,21 @@ 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 = 15;
const float BASE_SPEED_COMPONENTS = 18;
const float BASE_SPEED = sqrt(powf(BASE_SPEED_COMPONENTS, 2) * 2);
std::string HELP_TEXT = "\nnetpong - A networked pong game for the internet era.\n"
"\n"
"Usage: \n"
"netpong [MODE] [ADDRESS PORT]|[CODE]\n"
"\n"
"MODE: \n"
"-S : Server mode. Starts a server to allow the other player to connect.\n"
"IP address and port must be specified.\n"
"\n"
"-C: Client mode. Connects to a server, using the provided connection code.\n"
"\n"
"If no mode is specified, single player mode is used as default.\n"
"\n"
"CONTROLS:"
"\'W\' and \'S\' control left paddle (AKA client paddle)\n"
"Up and Down arrow keys control right paddle (AKA server paddle)\n";
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. */
/* 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;
}
typedef struct {
Mode mode;
Sock* netsock;
} GameType;
raylib::Vector2 changeVelocityAfterCollision(Paddle paddle, Ball ball) {
float paddle_mid_y = (paddle.getRect().y + paddle.getRect().GetHeight()) / 2.0; /* Middle y value of rectangle */
@@ -94,228 +74,149 @@ raylib::Vector2 changeVelocityAfterCollision(Paddle paddle, Ball ball) {
return raylib::Vector2(new_x_vel, new_y_vel);
}
/* 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.");
/* 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;
}
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.");
/* GAME STARTED IN CLIENT MODE */
if (strcmp(argv[1],"-C") == 0) {
if (argc < 3) { /* No address was provided */
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.");
}
}
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.
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 std::invalid_argument("Unrecognized argument.");
throw EXCEPT_WRONGRESPONSE;
}
type.mode = M_CLIENT;
type.netsock = client;
return type;
} catch (int e) {
throw;
} catch (std::exception& e) {
throw;
}
}
return;
/* 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;
}
/* 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 {
addr = std::string(argv[2]);
port = std::stoi(std::string(argv[3]));
}
/* 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;
}
}
int main(int argc, char** argv) {
/* 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;
}
/* Check if game was started in server or client mode, and set appropriate variables */
/* 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
/* GameType struct, to define whether the game is in single or muilti-player mode, and
to hold the appropriate socket */
GameType type;
/* 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);
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;
}
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;
}
} catch (std::invalid_argument& inv) {
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 -1;
} catch (int err) {
std::cout << strerror(err) << std::endl;
return -1;
return -8;
}
/* Initialize window and other variables */
SetTraceLogLevel(LOG_NONE);
raylib::Window window = raylib::Window(WIDTH, HEIGHT, "Pong");
window.ClearBackground(BLACK);
SetTargetFPS(60);
SetTargetFPS(10);
SetExitKey(KEY_Q);
std::string points_str = std::string("0\t\t0");
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;
@@ -332,7 +233,6 @@ 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();
@@ -351,18 +251,11 @@ int main(int argc, char** argv) {
}
}
/* 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. */
/* For client (wait for start message from server) */
if (type.mode == M_CLIENT) {
do {
response = type.netsock->recvAll();
} while (response[0] != 'S' && response[0] != 0);
if (response[0] == 0) {
CloseWindow();
std::cout << "Peer unexpectedly quit game." << std::endl;
return -1;
}
} while (response[0] != 'S');
game_started = true;
std::cout << "Game has been started by server." << std::endl;
}
@@ -371,34 +264,33 @@ int main(int argc, char** argv) {
if (game_started) {
/* Serialize the data that we need to send, and then send it to the peer paddle */
if (type.mode == M_SERVER) {
/* Serial_create_data creates a Serial_Data struct from our values.
Paddle 2 is controlled by the server, Paddle 1, by the client.*/
to_send_data = Serial_create_data(pad2.getRect().x, pad2.getRect().y, ball.pos.x, ball.pos.y, false);
/* Serial_create_data creates a Serial_Data struct from our values, and Serial_serialize serializes it Paddle 2 is controled by the server, Paddle 1, by the client.*/
to_send_data = Serial_create_data(pad2.getRect().x, pad2.getRect().y, ball.pos.x, ball.pos.y);
}
else if (type.mode == M_CLIENT) {
/* The _server_ is the authoritative peer for the ball position, so the client sends (0, 0) as the ball position instead of actually sending a position */
to_send_data = Serial_create_data(pad1.getRect().x, pad1.getRect().y, 0, 0, false);
to_send_data = Serial_create_data(pad1.getRect().x, pad1.getRect().y, 0, 0);
}
/* Only send and receive data if the game is not in single player mode */
if (type.mode != M_SINGLE) {
/* Serial_serialize serializes the struct into a byte_array. Since sendAll accepts a string, we have to convert this byte array into a string. */
type.netsock->sendAll((char *)Serial_serialize(to_send_data), sizeof(Serial_Data) + 1);
type.netsock->sendAll((char *)Serial_serialize(to_send_data), 9);
/* Create Serial_data struct from the response of the server. Since recvAll returns a char*, we need to convert it to a byte array */
uint8_t* response_array = (uint8_t *)(type.netsock->recvAll());
if (response_array != NULL) {
response_data = Serial_deserialize(response_array);
} else {
/* If the response is NULL, that means it timed-out. In this case, there's no value to print */
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 {
std::cout << "NOTHING RECEIVED" << std::endl;
}
}
/* Check to see if peer has quit the game */
if (response_data.should_quit == true) {
std::cout << "Peer unexpectedly quit game." << std::endl;
break; // Break out of main game loop
}
/* When updating the paddle positions, update the peer paddle's positions based on the vector set earlier */
std::string to_send = "";
/* Update paddle velocity */
/* Left paddle (controlled by client) - I use type.mode != M_SERVER, because I also want the single player
mode to be able to control the paddle. Therefore, the only mode that _can't_ control the paddle is the server
@@ -435,8 +327,7 @@ int main(int argc, char** argv) {
pad2.velocity.y = 0;
}
/* Why did I use 'type.mode != M_CLIENT'? - The client should set the ball position solely based
on the data sent by the server. It doesn't have to do any calculations of its own. */
/* The client should set the ball position based on the data sent by the server. It doesn't have to do any calculations of its own. */
if (type.mode != M_CLIENT) {
/* Update ball velocity based on collision detection */
if (pad1.getRect().CheckCollision(ball.pos, ball.radius)) { /* Collision with paddle 1 */
@@ -475,8 +366,7 @@ int main(int argc, char** argv) {
ball.vel.y = ball.vel.y * -1;
}
/* Update positions based on velocities - Client only updates pad1 (and receives data for pad2),
server updates pad2 and ball (and receives data for pad1) */
/* Update positions based on velocities - Client only updates pad1, server updates pad2 and ball */
if (type.mode != M_CLIENT) {
ball.updatePosition();
pad2.updatePosition();
@@ -501,14 +391,8 @@ int main(int argc, char** argv) {
window.EndDrawing();
}
/* If the game has been quit, ask the peer to quit as well */
if (type.mode != M_SINGLE) {
to_send_data = Serial_create_data(0, 0, 0, 0, true);
type.netsock->sendAll((char *)Serial_serialize(to_send_data), sizeof(Serial_Data) + 1);
sock_quit();
}
window.Close();
sock_quit();
return 0;
}

View File

@@ -1,40 +1,11 @@
project('Pong', ['cpp', 'c'], version: '0.1')
add_global_arguments('-g', '-Wall', '-pedantic', '-Wno-unused-function', '-Wno-narrowing', language : ['cpp', 'c'])
add_global_arguments('-std=c++11', language: ['cpp'])
compiler = meson.get_compiler('cpp')
cmake = import('cmake')
project('Pong', ['cpp', 'c'])
add_global_arguments('-g', '-Wall', '-pedantic', '-Wno-unused-function', '-Werror', language : ['cpp', 'c'])
# 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: 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
endif
# I we are building a static library
if get_option('default_library') == 'static'
raylib_proj = cmake.subproject('raylib')
raylib = raylib_proj.dependency('raylib')
endif
#For Windows only
ws2_dep = compiler.find_library('ws2_32', required: false)
winmm = compiler.find_library('winmm', required: false)
if build_machine.system() == 'windows'
add_project_arguments('-Wl,--subsystem,windows', '-mwindows', language: ['cpp', 'c']) # Prevent opening console when game is run
endif
raylib = dependency('raylib', native: true)
static_lib_path = ''
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]
'main.cpp', 'easysock.cpp', 'sock.cpp','paddle.cpp', 'ball.cpp', 'numeric_base.cpp', 'connect_code.cpp', 'server.cpp', 'client.cpp',
'serialization.c',
dependencies: raylib,
)

View File

@@ -1,22 +1,21 @@
#include "includes/numeric_base.hpp"
#include <string>
#include <cmath>
#include <cctype>
#include <algorithm>
std::string possible_chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
unsigned int to_decimal(std::string num, int from_base) {
int to_decimal(std::string num, int from_base) {
char current_char = 0;
int index = 0;
unsigned int value = 0;
int value = 0;
/* 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 the position of the character in the number. */
compute the value using */
for (int i=0; i < (int)num.length(); i++) {
current_char = num.at(i);
index = possible_chars.find(toupper(current_char)); // Convert the character to upper-case, so that the earliest match is detected
index = possible_chars.find(current_char);
value += pow(from_base, num.length() - i - 1) * index;
}
@@ -24,19 +23,14 @@ unsigned int to_decimal(std::string num, int from_base) {
}
/* Convert the given value from base 10 to the given base */
std::string from_decimal(unsigned int num, int to_base) {
std::string from_decimal(int num, int to_base) {
std::string return_val;
int val = 0;
/* 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());
@@ -46,7 +40,7 @@ std::string from_decimal(unsigned int num, int to_base) {
/* Convert the given value from 'from_base', to 'to_base' */
std::string base_convert(std::string num, int from_base, int to_base) {
unsigned int temp = to_decimal(num, from_base);
int temp = to_decimal(num, from_base);
std::string result = from_decimal(temp, to_base);
return result;

View File

@@ -1,20 +0,0 @@
#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

@@ -1,8 +1,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
#ifdef linux
#include <arpa/inet.h>
#endif
#ifdef _WIN32
@@ -12,32 +11,29 @@
/* Takes in float values, casts them to uint16_t and creates a Serial_Data struct */
Serial_Data Serial_create_data(float pad_x, float pad_y, float ball_x, float ball_y, bool should_quit) {
Serial_Data Serial_create_data(float pad_x, float pad_y, float ball_x, float ball_y) {
Serial_Data data;
data.pad_x = (uint16_t)pad_x;
data.pad_y = (uint16_t)pad_y;
data.ball_x = (uint16_t)ball_x;
data.ball_y = (uint16_t)ball_y;
data.should_quit = should_quit;
return data;
}
/* Serializes a 'Data' struct into a byte array, converted to network-byte order */
uint8_t* Serial_serialize(Serial_Data data) {
/* Create a pointer that can fit the entire struct */
uint8_t* serialized = malloc(sizeof(Serial_Data) + 1);
uint8_t* serialized = malloc(4 * sizeof(uint16_t) + 1);
uint8_t* pad_x_ptr;
uint8_t* pad_y_ptr;
uint8_t* ball_x_ptr;
uint8_t* ball_y_ptr;
uint8_t* should_quit_ptr;
memset(serialized, 0, sizeof(Serial_Data) + 1); // Zero out the memory
memset(serialized, 0, 4 * sizeof(uint16_t) + 1); // Zero out the memory
pad_x_ptr = serialized;
pad_y_ptr = pad_x_ptr + sizeof(uint16_t);
ball_x_ptr = pad_y_ptr + sizeof(uint16_t);
ball_y_ptr = ball_x_ptr + sizeof(uint16_t);
should_quit_ptr = ball_y_ptr + sizeof(uint16_t);
*((uint16_t *)pad_x_ptr) = data.pad_x;
*((uint16_t *)pad_x_ptr) = htons(*((uint16_t *)pad_x_ptr));
@@ -50,9 +46,7 @@ uint8_t* Serial_serialize(Serial_Data data) {
*((uint16_t *)ball_y_ptr) = data.ball_y;
*((uint16_t *)ball_y_ptr) = htons(*((uint16_t *)ball_y_ptr));
*((bool *)should_quit_ptr) = data.should_quit;
*(should_quit_ptr + sizeof(bool)) = '\0';
*(ball_y_ptr + sizeof(uint16_t)) = '\0';
return serialized;
}
@@ -65,7 +59,6 @@ Serial_Data Serial_deserialize(uint8_t* serialized) {
uint8_t* pad_y_ptr = serialized + sizeof(uint16_t);
uint8_t* ball_x_ptr = pad_y_ptr + sizeof(uint16_t);
uint8_t* ball_y_ptr = ball_x_ptr + sizeof(uint16_t);
uint8_t* should_quit_ptr = ball_y_ptr + sizeof(uint16_t);
/* Dereference (and cast) the pointers, and store them into the struct */
deserialized.pad_x = *((uint16_t *)pad_x_ptr);
@@ -80,8 +73,6 @@ Serial_Data Serial_deserialize(uint8_t* serialized) {
deserialized.ball_y = *((uint16_t *)ball_y_ptr);
deserialized.ball_y = ntohs(deserialized.ball_y);
deserialized.should_quit = *((bool *)should_quit_ptr);
return deserialized;
}

View File

@@ -1,4 +1,4 @@
#if defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__))
#ifdef linux
#include <sys/socket.h>
#endif
#ifdef _WIN32
@@ -7,8 +7,9 @@
#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() {
@@ -43,14 +44,11 @@ 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 casted struct to host network-byte, and convert it to a dotted decimal */
/* 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 - 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);
/* FOR IPv6 */
peer_addr = "IPV6 NOT SUPPORTED YET";
}
return to_return;
@@ -72,11 +70,8 @@ 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 - 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);
/* FOR IPv6 */
peer_addr = "IPV6 NOT SUPPORTED YET";
}
return to_return;
@@ -89,7 +84,7 @@ is thrown as an exception. */
void Server::wait_for_peer() {
if (this->protocol == ES_TCP) {
this->other_socket = accept(this->sock_fd, (struct sockaddr *)dest, &addrlen);
this->other_socket = accept(this->sock_fd, dest, &addrlen);
if (this->other_socket < 0) {
throw errno;
}
@@ -101,7 +96,8 @@ called immediately after the constructor. If the socket is TCP, it also sets the
socket to listen for incoming connections. This function throws an exception if
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().*/
'catching' the thrown exception and using strerror().
This function also sets a timeout of 100ms for UDP sockets */
void Server::create_socket() {
Sock::create_socket();

View File

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

19
timer.c
View File

@@ -1,19 +0,0 @@
#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,10 +1,2 @@
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).
3. Try to make the ball go between screens.
8. Have both client and server send a message if the game is quit.