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.
524 lines
21 KiB
C++
524 lines
21 KiB
C++
#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>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <ctime>
|
|
#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/easysock.hpp"
|
|
#include "includes/client.hpp"
|
|
#include "includes/server.hpp"
|
|
#include "includes/exception_consts.hpp"
|
|
#include "includes/check_input.hpp"
|
|
#include "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);
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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, 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 EXCEPT_WRONGRESPONSE;
|
|
}
|
|
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;
|
|
|
|
/* 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.
|
|
TODO - Check that the client actually sends 'GG'. */
|
|
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 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_INVALIDIP) {
|
|
std::cout << "Invalid IP address provided." << std::endl;
|
|
return -5;
|
|
}
|
|
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 -8;
|
|
}
|
|
|
|
/* 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));
|
|
|
|
/* 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) {
|
|
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(100, 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))) {
|
|
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();
|
|
}
|
|
|
|
type = check_server(ip_text, port_text);
|
|
free(ip_text);
|
|
free(port_text);
|
|
}
|
|
|
|
if (selected_item == M_CLIENT) {
|
|
button_pressed = false; // Whether submit button is pressed
|
|
char* code_text = (char *)calloc(100, 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))) {
|
|
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();
|
|
}
|
|
|
|
type = check_client(code_text);
|
|
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 message from server) */
|
|
if (type.mode == M_CLIENT) {
|
|
do {
|
|
response = type.netsock->recvAll();
|
|
} while (response[0] != 'S');
|
|
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;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
}
|