# include <iostream>
# include <cmath>
# include <ctime>
# include "includes/raylib-cpp/raylib-cpp.hpp"
# include "includes/paddle.hpp"
# include "includes/ball.hpp"
# include "includes/easysock.hpp"
# include "includes/math-helpers.hpp"
# include "includes/connect-helpers.hpp"
# include "includes/client.hpp"
# include "includes/server.hpp"
/* 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 = 18 ;
const float BASE_SPEED = sqrt ( powf ( BASE_SPEED_COMPONENTS , 2 ) * 2 ) ;
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 ;
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 ) ;
}
/* 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 ;
}
/* GAME STARTED IN CLIENT MODE */
if ( strcmp ( argv [ 1 ] , " -C " ) = = 0 ) {
if ( argc < 3 ) { /* No address was provided */
throw EXCEPT_TOOFEWARGS ;
}
connect_code = std : : string ( argv [ 2 ] ) ; /* The connect code is a special string, that contains the server address and port. It is given by the server. */
try {
addr_port = connect_code : : decode ( connect_code ) ;
Client * client = new Client ( 4 , ' T ' , addr_port [ 0 ] . data ( ) , std : : stoi ( addr_port [ 1 ] ) ) ;
type . mode = M_CLIENT ;
type . netsock = client ;
return type ;
} catch ( int e ) {
throw ;
} catch ( std : : exception & e ) {
throw ;
}
}
/* GAME STARTED IN SERVER MODE */
else if ( strcmp ( argv [ 1 ] , " -S " ) = = 0 ) {
std : : string addr ;
uint16_t port ;
/* No IP address or port specified */
if ( argc < 3 ) {
throw EXCEPT_TOOFEWARGS ;
}
/* 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 ;
try {
Server * server = new Server ( 4 , ' T ' , addr . data ( ) , port ) ;
type . mode = M_SERVER ;
type . netsock = server ;
return type ;
} catch ( int e ) {
throw ;
}
catch ( std : : exception & e ) {
throw ;
}
}
else {
throw EXCEPT_INVALIDARGS ;
}
}
int main ( int argc , char * * argv ) {
/* Check if game was started in server or client mode, and set appropriate variables */
/* GameType struct, to define whether the game is in single or muilti-player mode, and
to hold the appropriate socket */
GameType type ;
try {
type = check_server_client ( argc , argv ) ;
} catch ( int e ) {
if ( e = = EXCEPT_TOOFEWARGS ) {
std : : cout < < " Started in client mode, but no address was specified. " < < std : : endl ;
return - 1 ;
}
if ( e = = EXCEPT_INVALIDARGS ) {
std : : cout < < " Invalid argument. " < < std : : endl ;
return - 2 ;
}
if ( e = = EXCEPT_CONNREFUSED ) {
std : : cout < < " Connection refused. Wrong IP address or port specified. " < < std : : endl ;
return - 3 ;
}
if ( e = = EXCEPT_ADDRNOTAVAIL ) {
std : : cout < < " Unable to use requested address for server. " < < std : : endl ;
return - 4 ;
}
if ( e = = EXCEPT_INVALIDIP ) {
std : : cout < < " Invalid IP address provided. " < < std : : endl ;
return - 5 ;
}
} catch ( std : : invalid_argument & inv ) {
std : : cout < < inv . what ( ) < < std : : endl ;
return - 6 ;
}
# ifdef DEBUG
type . mode = M_CLIENT ;
# endif
/* Initialize window and other variables */
SetTraceLogLevel ( LOG_INFO ) ;
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 ) ) ;
/* 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 ( ) ;
pad1 . draw ( ) ;
pad2 . draw ( ) ;
ball . draw ( ) ;
window . EndDrawing ( ) ;
/* Main loop */
while ( ! window . ShouldClose ( ) ) {
if ( ! game_started ) {
if ( IsKeyDown ( KEY_SPACE ) ) {
game_started = true ;
}
}
if ( game_started ) {
/* Receive response from the other machine, if the game is in multiplayer mode */
std : : string response = " " ;
if ( type . mode ! = M_SINGLE ) {
# ifdef DEBUG
response = " " ;
# else
std : : string response = type . netsock - > recvAll ( ) ;
# endif
}
std : : string to_send = " " ;
/* Update paddle velocity */
/* Left paddle (controlled by client) */
/* Up motion */
if ( IsKeyPressed ( KEY_S ) | | response = = " D1 " ) {
pad1 . velocity . y = PADDLE_SPEED ; /* Set positive (downward) velocity, since (0,0) is top-left */
if ( type . mode = = M_CLIENT ) { /* If this machine controls this paddle, Send the key press to the other machine */
to_send = " D1 " ;
}
}
/* Down motion */
if ( IsKeyPressed ( KEY_W ) | | response = = " U1 " ) {
pad1 . velocity . y = ( - 1 ) * PADDLE_SPEED ; /* Set negative (upward) velocity */
if ( type . mode = = M_CLIENT ) {
to_send = " U1 " ;
}
}
/* Stop */
if ( ( IsKeyReleased ( KEY_S ) | | IsKeyReleased ( KEY_W ) ) | | ( response = = " S1 " ) ) {
pad1 . velocity . y = 0 ;
if ( type . mode = = M_CLIENT ) {
to_send = " S1 " ;
}
}
/* Right paddle - controlled by server*/
if ( IsKeyPressed ( KEY_DOWN ) ) {
if ( type . mode = = M_SERVER ) {
type . netsock - > sendAll ( std : : string ( " D " ) ) ;
}
pad2 . velocity . y = PADDLE_SPEED ;
}
if ( IsKeyPressed ( KEY_UP ) ) {
if ( type . mode = = M_SERVER ) {
type . netsock - > sendAll ( std : : string ( " U " ) ) ;
}
pad2 . velocity . y = ( - 1 ) * PADDLE_SPEED ;
}
if ( IsKeyReleased ( KEY_UP ) | | IsKeyReleased ( KEY_DOWN ) ) {
if ( type . mode = = M_SERVER ) {
type . netsock - > sendAll ( std : : string ( " S " ) ) ;
}
pad2 . velocity . y = 0 ;
}
/* 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 ) ;
}
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 */
pad1 . updatePosition ( ) ;
pad2 . updatePosition ( ) ;
ball . updatePosition ( ) ;
}
/* 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 ( ) ;
}
window . Close ( ) ;
sock_quit ( ) ;
return 0 ;
}