323 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #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/sign.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\t0");
 | |
| 	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;
 | |
| }
 |