# if defined(_WIN32)
# define NOGDI // All GDI defines and routines
# define NOUSER // All USER defines and routines
# define WIN32_LEAN_AND_MEAN
# include <windows.h> // or any library that uses Windows.h
# endif
# if defined(_WIN32) // raylib uses these names as function parameters
# undef near
# undef far
# endif
# include <iostream>
# 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/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 "netpong-serialization/includes/serialization.h"
# include "includes/timer.h"
/* Global variables used to instantiate structs */
const int WIDTH = 1500 ;
const int HEIGHT = 600 ;
const int RECT_H = HEIGHT / 3 ;
const int RECT_W = 30 ;
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 = sqrt ( powf ( BASE_SPEED_COMPONENTS , 2 ) * 2 ) ;
std : : string HELP_TEXT = " \n netpong - A networked pong game for the internet era. \n "
" \n "
" Usage: \n "
" netpong [MODE] [ADDRESS PORT]|[CODE] \n "
" \n "
" MODE: \n "
" -S : Server mode. Starts a server to allow the other player to connect. \n "
" IP address and port must be specified. \n "
" \n "
" -C: Client mode. Connects to a server, using the provided connection code. \n "
" \n "
" If no mode is specified, single player mode is used as default. \n "
" \n "
" CONTROLS: "
" \' W \' and \' S \' control left paddle (AKA client paddle) \n "
" Up and Down arrow keys control right paddle (AKA server paddle) \n " ;
/* Simple function to return 1 if a value is positive, and -1 if it is negative */
int signum ( int num ) {
int retval = 0 ;
( 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 */
float ball_y = ball . pos . y ; /* Y co-ordinate of ball */
float offset = paddle_mid_y - ball_y ; /* Subtracting the ball coordinate will give us a value between -paddle_mid_y (represents bottom of paddle) and +paddle_mid_y (represents top of paddle) */
offset / = ( paddle . getRect ( ) . GetHeight ( ) ) ; /* Normalize the value, by dividing it by its maximum magnitude. It is now a value between -1 and 1. */
offset * = 0.8 + ( float ) ( std : : rand ( ) ) / ( float ) ( RAND_MAX / ( 1.2 - 0.8 ) ) ; // Generate a random float from 0.8 to 1.2
float bounce_angle = offset * BASE_BOUNCE_RAD ; /* Calculate the actual bounce angle from the base bounce angle. */
/* Calculate new velocities as multiples of the original velocity. I use sine and cosine, because when the ball hits the paddle
perpendicular to it ( bounce angle is 0 ) , the y_velocity should be 0 ( i . e . It should bounce straight back ) . The sin function does
this for us . A similar reasoning was employed for the use of cosine */
float new_x_vel = abs ( BASE_SPEED * cosf ( bounce_angle ) ) * ( - 1 * signum ( ball . vel . x ) ) ; /* Reverse the sign of the x-velocity */
float new_y_vel = abs ( BASE_SPEED * sinf ( bounce_angle ) ) * signum ( ball . vel . y ) ; /* Keep the sign of the y-velocity */
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. " ) ;
}
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. " ) ;
}
}
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.
} else {
throw std : : invalid_argument ( " Unrecognized argument. " ) ;
}
}
return ;
}
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 ;
}
/* 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 ;
/* 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 ) ;
}
}
} 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 */
SetTraceLogLevel ( LOG_NONE ) ;
raylib : : Window window = raylib : : Window ( WIDTH , HEIGHT , " Pong " ) ;
window . ClearBackground ( BLACK ) ;
SetTargetFPS ( 60 ) ;
SetExitKey ( KEY_Q ) ;
std : : string points_str = std : : string ( " 0 \t \t 0 " ) ;
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 ) {
type . mode = M_SINGLE ;
type . netsock = NULL ;
GuiSetStyle ( DEFAULT , TEXT_WRAP_MODE , TEXT_WRAP_WORD ) ; // Enable text wrapping so that the long text, displayed below, will be wrapped
BeginDrawing ( ) ;
ClearBackground ( BLACK ) ;
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 ) ) + " \n Closing 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 ( ) ) + " \n Closing 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 ) ) + " \n Closing 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 ( ) ) + " \n Closing game... " , 2 ) ;
return - 1 ;
}
free ( code_text ) ;
}
}
/* Variable to store the response given by the other player */
std : : string response ;
Serial_Data response_data ;
/* Vector to store peer paddle position */
raylib : : Vector2 peer_pos ;
/* Byte array to hold the result of serializing a struct (in order to send it through a socket) */
Serial_Data to_send_data ;
std : : string to_send_string ;
/* Instantiate Paddle and Ball objects */
Paddle pad1 = Paddle ( 10 , ( HEIGHT / 2 ) - ( RECT_H / 2 ) , RECT_W , RECT_H ) ;
Paddle pad2 = Paddle ( window . GetWidth ( ) - RECT_W - 10 , ( HEIGHT / 2 ) - ( RECT_H / 2 ) , RECT_W , RECT_H ) ;
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 ( ) ;
window . EndDrawing ( ) ;
/* Main loop */
while ( ! window . ShouldClose ( ) ) {
if ( ! game_started ) {
/* For the server, or if game is being played in single-player mode */
if ( ( type . mode = = M_SERVER | | type . mode = = M_SINGLE ) & & IsKeyDown ( KEY_SPACE ) ) {
game_started = true ;
/* Send a start message to the client */
if ( type . mode = = M_SERVER ) {
type . netsock - > sendAll ( " S " ) ;
}
}
/* 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 ' & & 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 ;
}
}
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 ) ;
}
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 ) ;
}
/* 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 ) ;
/* 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 */
std : : cout < < " NOTHING RECEIVED " < < std : : endl ;
}
free ( response_array ) ;
}
/* Check to see if peer has quit the game */
if ( response_data . should_quit = = true ) {
std : : cout < < " Peer unexpectedly quit game. " < < std : : endl ;
break ; // Break out of main game loop
}
/* Left paddle (controlled by client) - I use type.mode != M_SERVER, because I also want the single player
mode to be able to control the paddle . Therefore , the only mode that _can ' t_ control the paddle is the server
mode . */
/* Down motion */
if ( IsKeyPressed ( KEY_S ) & & type . mode ! = M_SERVER ) {
pad1 . velocity . y = PADDLE_SPEED ; /* Set positive (downward) velocity, since (0,0) is top-left */
}
/* Up motion */
if ( IsKeyPressed ( KEY_W ) & & type . mode ! = M_SERVER ) {
pad1 . velocity . y = ( - 1 ) * PADDLE_SPEED ; /* Set negative (upward) velocity */
}
/* Stop */
if ( ( ( IsKeyReleased ( KEY_S ) | | IsKeyReleased ( KEY_W ) ) ) & & ( type . mode ! = M_SERVER ) ) {
pad1 . velocity . y = 0 ;
}
/* Right paddle - controlled by server - See above for why I used '!= M_CLIENT' instead of '== M_SERVER' */
/* Down */
if ( IsKeyPressed ( KEY_DOWN ) & & type . mode ! = M_CLIENT ) {
pad2 . velocity . y = PADDLE_SPEED ;
}
/* Up */
if ( IsKeyPressed ( KEY_UP ) & & type . mode ! = M_CLIENT ) {
pad2 . velocity . y = ( - 1 ) * PADDLE_SPEED ;
}
/* Stop */
if ( ( IsKeyReleased ( KEY_UP ) | | IsKeyReleased ( KEY_DOWN ) ) & & type . mode ! = M_CLIENT ) {
pad2 . velocity . y = 0 ;
}
/* Why did I use 'type.mode != M_CLIENT'? - The client should set the ball position solely based
on the data sent by the server . It doesn ' t have to do any calculations of its own . */
if ( type . mode ! = M_CLIENT ) {
/* Update ball velocity based on collision detection */
if ( pad1 . getRect ( ) . CheckCollision ( ball . pos , ball . radius ) ) { /* Collision with paddle 1 */
ball . pos . x = pad1 . getRect ( ) . x + pad1 . getRect ( ) . GetWidth ( ) + ball . radius + 1 ; /* Ensuring that the ball doesn't get stuck inside the paddle */
ball . vel = changeVelocityAfterCollision ( pad1 , ball ) ;
}
if ( pad2 . getRect ( ) . CheckCollision ( ball . pos , ball . radius ) ) { /* Collision with paddle 2 */
ball . pos . x = pad2 . getRect ( ) . x - ball . radius - 1 ;
ball . vel = changeVelocityAfterCollision ( pad2 , ball ) ;
}
} else {
ball . setPosition ( raylib : : Vector2 ( response_data . ball_x , response_data . ball_y ) ) ;
}
if ( ball . pos . x + ball . radius > = window . GetWidth ( ) ) { /* Collision with right wall */
pad1 . incrementPoints ( ) ;
game_started = false ;
ball . reset ( ) ;
pad1 . reset ( ) ;
pad2 . reset ( ) ;
}
if ( ball . pos . x - ball . radius < = 0 ) { /* Collision with left wall */
pad2 . incrementPoints ( ) ;
game_started = false ;
ball . reset ( ) ;
pad1 . reset ( ) ;
pad2 . reset ( ) ;
}
if ( ball . pos . y - ball . radius < = 0 ) { /* Collision with top wall */
ball . pos . y = ball . radius + 1 ;
ball . vel . y = ball . vel . y * - 1 ;
}
if ( ball . pos . y + ball . radius > = window . GetHeight ( ) ) { /* Collision with bottom wall */
ball . pos . y = HEIGHT - ball . radius - 1 ;
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 ) */
if ( type . mode ! = M_CLIENT ) {
ball . updatePosition ( ) ;
pad2 . updatePosition ( ) ;
} else {
pad2 . setPosition ( response_data . pad_x , response_data . pad_y ) ;
}
if ( type . mode ! = M_SERVER ) {
pad1 . updatePosition ( ) ;
} else {
pad1 . setPosition ( response_data . pad_x , response_data . pad_y ) ;
}
}
/* Draw objects */
window . BeginDrawing ( ) ;
window . ClearBackground ( BLACK ) ;
points_str = std : : to_string ( pad1 . getPoints ( ) ) + " \t \t " + std : : to_string ( pad2 . getPoints ( ) ) ;
raylib : : Text : : Draw ( points_str , ( WIDTH / 2 ) - 30 , HEIGHT / 10 , 30 , raylib : : Color : : White ( ) ) ;
pad1 . draw ( ) ;
pad2 . draw ( ) ;
ball . draw ( ) ;
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 ( ) ;
return 0 ;
}