You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
netpong/main.cpp

276 lines
8.6 KiB
C++

#include <iostream>
#include <cmath>
#include <ctime>
#include "raylib-cpp/raylib-cpp.hpp"
#include "paddle.hpp"
#include "ball.hpp"
#include "easysock.hpp"
#include "math-helpers.hpp"
#include "connect-helpers.hpp"
#include "client.hpp"
#include "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 = 60;
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;
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 an enum that indicates whether the game is in server,
client or single player mode.*/
Mode check_server_client(int argc, char** argv, Server* server, Client* client) {
std::string connect_code;
std::vector<std::string> addr_port; /* Vector to store (IPv4) address and port */
if (argc < 2) { /* Game was not started in client or server mode */
return M_SINGLE;
}
/* 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. */
addr_port = connect_code::decode(connect_code);
try {
client = new Client(4, 'T', addr_port[0].data(), std::stoi(addr_port[1]));
return M_CLIENT;
} 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 = new Server(4, 'T', addr.data(), port);
return M_SERVER;
} 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 */
/* mode - M_CLIENT for client, M_SERVER for SERVER and M_SINGLE for single-player */
Mode mode;
Server server;
Client client;
try {
mode = check_server_client(argc, argv, &server, &client);
} 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;
}
/* 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\t0");
bool game_started = false;
srand(std::time(NULL));
bool in_server_mode = false;
/* 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) {
/* Update paddle velocity */
if (IsKeyPressed(KEY_S)) {
pad1.velocity.y = PADDLE_SPEED; /* Set positive (downward) velocity, since (0,0) is top-left */
}
if (IsKeyPressed(KEY_W)) {
pad1.velocity.y = (-1) * PADDLE_SPEED; /* Set negative (upward) velocity */
}
if (IsKeyReleased(KEY_S) || IsKeyReleased(KEY_W)) {
pad1.velocity.y = 0;
}
if (IsKeyPressed(KEY_UP)) {
if(in_server_mode) {
client.sendAll(std::string("U"));
}
pad2.velocity.y = (-1) * PADDLE_SPEED;
}
if (IsKeyPressed(KEY_DOWN)) {
if (in_server_mode) {
client.sendAll(std::string("D"));
}
pad2.velocity.y = PADDLE_SPEED;
}
if (IsKeyReleased(KEY_UP) || IsKeyReleased(KEY_DOWN)) {
if (in_server_mode) {
client.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();
return 0;
}