Refactored Spacewar to a proper server/client infrastructure with support for up to 32 players.
Next steps: implement client-side prediction, pinging and ponging, and tweak physics. Squashed commit of the following: commit6cc8ea4084
Author: Barra Ó Catháin <barra@ocathain.ie> Date: Sat Oct 21 18:21:37 2023 +0100 Current Working Tree on <2023-10-21> commit1a7348dbd0
Author: Barra Ó Catháin <barra@ocathain.ie> Date: Mon Oct 16 23:43:21 2023 +0100 Current Working Tree on <2023-10-16> commit11b568550c
Author: Barry Kane <barra@ocathain.ie> Date: Sun Oct 15 23:54:02 2023 +0100 Current Working Tree on <2023-10-15> commitbc446d457b
Author: Barry Kane <barra@ocathain.ie> Date: Sun Oct 15 01:47:23 2023 +0100 Current Working Tree On <2023-10-15> commit17401d3d02
Author: Barry Kane <barra@ocathain.ie> Date: Sat Oct 14 00:27:03 2023 +0100 Current Working Tree On <2023-10-13> Signed-off-by: Barry Kane <barra@ocathain.ie> commit1e00257754
Author: Barra Ó Catháin <barra@ocathain.ie> Date: Fri Oct 13 14:44:24 2023 +0100 Current Working Tree on <2023-10-13> commitce76166c3f
Author: Barra Ó Catháin <barra@ocathain.ie> Date: Sun Oct 8 22:22:21 2023 +0100 Current Working Tree on <2023-10-08>
This commit is contained in:
parent
9349afbadc
commit
51aeaf6c24
|
@ -1,328 +0,0 @@
|
|||
// Spacewar Client, Barra Ó Catháin.
|
||||
// ===================================
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <SDL2/SDL_timer.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include "xyVector.h"
|
||||
#include "spacewarPlayer.h"
|
||||
#include "spacewarGraphics.h"
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
SDL_Event event;
|
||||
bool quit = false, rotatingClockwise = false, rotatingAnticlockwise = false, accelerating = false;
|
||||
int width = 0, height = 0;
|
||||
uint32_t rendererFlags = SDL_RENDERER_ACCELERATED;
|
||||
uint64_t thisFrameTime = SDL_GetPerformanceCounter(), lastFrameTime = 0;
|
||||
long positionX = 512, positionY = 512, starPositionX = 0, starPositionY = 0;
|
||||
xyVector positionVector = {512, 512}, velocityVector = {1, 0}, gravityVector = {0, 0},
|
||||
engineVector = {0.04, 0}, upVector = {0, 0.1}, starPosition = {0, 0};
|
||||
|
||||
// Create the socket:
|
||||
int receiveSocket = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (receiveSocket < 0)
|
||||
{
|
||||
fprintf(stderr, "\tSocket Creation is:\t\033[33;40mRED.\033[0m Aborting launch.\n");
|
||||
exit(0);
|
||||
}
|
||||
printf("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n");
|
||||
|
||||
// Make the socket timeout:
|
||||
struct timeval read_timeout;
|
||||
read_timeout.tv_sec = 0;
|
||||
read_timeout.tv_usec = 8;
|
||||
setsockopt(receiveSocket, SOL_SOCKET, SO_RCVTIMEO, &read_timeout, sizeof(read_timeout));
|
||||
|
||||
// Create and fill the information needed to bind to the socket:
|
||||
struct sockaddr_in serverAddress;
|
||||
memset(&serverAddress, 0, sizeof(serverAddress));
|
||||
serverAddress.sin_family = AF_INET; // IPv4
|
||||
serverAddress.sin_addr.s_addr = INADDR_ANY;
|
||||
serverAddress.sin_port = htons(12000);
|
||||
|
||||
// Bind to the socket:
|
||||
if (bind(receiveSocket, (const struct sockaddr *)&serverAddress, sizeof(serverAddress)) < 0)
|
||||
{
|
||||
perror("bind failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Create the socket:
|
||||
int sendSocket = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sendSocket < 0)
|
||||
{
|
||||
fprintf(stderr, "\tSocket Creation is:\t\033[33;40mRED.\033[0m Aborting launch.\n");
|
||||
exit(0);
|
||||
}
|
||||
printf("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n");
|
||||
|
||||
|
||||
// Create and fill the information needed to bind to the socket:
|
||||
struct sockaddr_in sendAddress;
|
||||
memset(&sendAddress, 0, sizeof(sendAddress));
|
||||
sendAddress.sin_family = AF_INET; // IPv4
|
||||
sendAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
sendAddress.sin_port = htons(12001);
|
||||
|
||||
// Get the initial
|
||||
ship shipA;
|
||||
ship shipB;
|
||||
|
||||
// Initialize the SDL library, video, sound, and input:
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
|
||||
{
|
||||
printf("SDL Initialization Error: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
// Initialize image loading:
|
||||
IMG_Init(IMG_INIT_PNG);
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");
|
||||
|
||||
// Initialize font support:
|
||||
TTF_Init();
|
||||
|
||||
// Create an SDL window and rendering context in that window:
|
||||
SDL_Window * window = SDL_CreateWindow("SDL_TEST", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 450, 0);
|
||||
SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, rendererFlags);
|
||||
SDL_SetWindowTitle(window, "Spacewar!");
|
||||
|
||||
// Load in all of our textures:
|
||||
SDL_Texture * idleTexture, * acceleratingTexture, * clockwiseTexture, * anticlockwiseTexture, * currentTexture,
|
||||
* acceleratingTexture2, * blackHoleTexture;
|
||||
|
||||
idleTexture = IMG_LoadTexture(renderer, "./Images/Ship-Idle.png");
|
||||
clockwiseTexture = IMG_LoadTexture(renderer, "./Images/Ship-Clockwise.png");
|
||||
acceleratingTexture = IMG_LoadTexture(renderer, "./Images/Ship-Accelerating.png");
|
||||
anticlockwiseTexture = IMG_LoadTexture(renderer, "./Images/Ship-Anticlockwise.png");
|
||||
acceleratingTexture2 = IMG_LoadTexture(renderer, "./Images/Ship-Accelerating-Frame-2.png");
|
||||
|
||||
// Load the starfield texture:
|
||||
SDL_Texture * starfieldTexture = IMG_LoadTexture(renderer, "./Images/Starfield.png");
|
||||
SDL_Rect starfieldRect;
|
||||
SDL_QueryTexture(starfieldTexture, NULL, NULL, NULL, &starfieldRect.h);
|
||||
SDL_QueryTexture(starfieldTexture, NULL, NULL, &starfieldRect.w, NULL);
|
||||
|
||||
blackHoleTexture = IMG_LoadTexture(renderer, "./Images/Black-Hole.png");
|
||||
SDL_Rect blackHoleRectangle;
|
||||
blackHoleRectangle.x = 0;
|
||||
blackHoleRectangle.y = 0;
|
||||
SDL_QueryTexture(blackHoleTexture, NULL, NULL, NULL, &blackHoleRectangle.h);
|
||||
SDL_QueryTexture(blackHoleTexture, NULL, NULL, &blackHoleRectangle.w, NULL);
|
||||
|
||||
|
||||
// Enable resizing the window:
|
||||
SDL_SetWindowResizable(window, SDL_TRUE);
|
||||
ship Temp;
|
||||
playerController playerOne;
|
||||
playerOne.number = 1;
|
||||
bool shipAUpdated, shipBUpdated;
|
||||
|
||||
TTF_Font * font = TTF_OpenFont("./Robtronika.ttf", 12);
|
||||
SDL_Color white = {255, 255, 255};
|
||||
int keyCount = 0;
|
||||
const uint8_t * keyboardState = SDL_GetKeyboardState(&keyCount);
|
||||
|
||||
// Prep the titlescreen struct:
|
||||
SpacewarTitlescreen titlescreen = prepareTitleScreen(window, renderer, "./Images/Starfield.png",
|
||||
"./Images/Title.png", font,
|
||||
"Press Enter or Button 0 on your joystick to connect!");
|
||||
|
||||
// Load all joysticks:
|
||||
int joystickListLength = SDL_NumJoysticks();
|
||||
SDL_Joystick ** joysticksList = calloc(joystickListLength, sizeof(SDL_Joystick*));
|
||||
|
||||
for(int index = 0; index < SDL_NumJoysticks(); index++)
|
||||
{
|
||||
joysticksList[index] = SDL_JoystickOpen(index);
|
||||
}
|
||||
|
||||
// Choose a player joystick:
|
||||
printf("Please press button zero on the controller you wish to use, or enter to play keyboard only.\n");
|
||||
|
||||
int joystickIndex = 0;
|
||||
bool inputSelected = false;
|
||||
|
||||
// Render the title text:
|
||||
while(!inputSelected)
|
||||
{
|
||||
// Draw the title screen according to the titlescreen struct:
|
||||
drawTitleScreen(&titlescreen);
|
||||
|
||||
SDL_PumpEvents();
|
||||
SDL_GetKeyboardState(&keyCount);
|
||||
if(keyboardState[SDL_SCANCODE_RETURN] == 1)
|
||||
{
|
||||
joysticksList[joystickIndex] = NULL;
|
||||
inputSelected = true;
|
||||
}
|
||||
joystickIndex++;
|
||||
if(joystickIndex >= joystickListLength)
|
||||
{
|
||||
joystickIndex = 0;
|
||||
}
|
||||
if(SDL_JoystickGetButton(joysticksList[joystickIndex], 0) == 1)
|
||||
{
|
||||
inputSelected = true;
|
||||
}
|
||||
|
||||
// Delay enough so that we run at 30 frames in the menu:
|
||||
SDL_Delay(1000 / 30);
|
||||
}
|
||||
|
||||
// Load joystick
|
||||
playerOne.joystick = joysticksList[joystickIndex];
|
||||
if (playerOne.joystick == NULL )
|
||||
{
|
||||
printf( "Warning: Unable to open game controller! SDL Error: %s\n", SDL_GetError() );
|
||||
}
|
||||
playerOne.haptic = SDL_HapticOpenFromJoystick(playerOne.joystick);
|
||||
SDL_HapticRumbleInit(playerOne.haptic);
|
||||
|
||||
while (!quit)
|
||||
{
|
||||
while(!(shipAUpdated && shipBUpdated))
|
||||
{
|
||||
// Receive data from the socket:
|
||||
recvfrom(receiveSocket, &Temp, sizeof(ship), 0, NULL, NULL);
|
||||
if(Temp.number == 0)
|
||||
{
|
||||
shipA = Temp;
|
||||
shipAUpdated = true;
|
||||
}
|
||||
if(Temp.number == 1)
|
||||
{
|
||||
shipB = Temp;
|
||||
shipBUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the user wants to quit:
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
{
|
||||
quit = true;
|
||||
break;
|
||||
}
|
||||
case SDL_KEYDOWN:
|
||||
{
|
||||
switch (event.key.keysym.sym)
|
||||
{
|
||||
case SDLK_LEFT:
|
||||
{
|
||||
playerOne.turningAnticlockwise = true;
|
||||
break;
|
||||
}
|
||||
case SDLK_RIGHT:
|
||||
{
|
||||
playerOne.turningClockwise = true;
|
||||
break;
|
||||
}
|
||||
case SDLK_UP:
|
||||
{
|
||||
playerOne.accelerating = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_KEYUP:
|
||||
{
|
||||
switch (event.key.keysym.sym)
|
||||
{
|
||||
case SDLK_LEFT:
|
||||
{
|
||||
playerOne.turningAnticlockwise = false;
|
||||
break;
|
||||
}
|
||||
case SDLK_RIGHT:
|
||||
{
|
||||
playerOne.turningClockwise = false;
|
||||
break;
|
||||
}
|
||||
case SDLK_UP:
|
||||
{
|
||||
playerOne.accelerating = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
sendto(sendSocket, &playerOne, sizeof(playerOne), 0, (const struct sockaddr *)&sendAddress, sizeof(sendAddress));
|
||||
// Store the window's current width and height:
|
||||
SDL_GetWindowSize(window, &width, &height);
|
||||
|
||||
// Set the texture to idle:
|
||||
currentTexture = idleTexture;
|
||||
|
||||
// Calculate the position of the sprites:
|
||||
shipB.rectangle.x = (width/2) - 16 - (shipB.velocity.xComponent * 15);
|
||||
shipB.rectangle.y = (height/2) - 16 - (shipB.velocity.yComponent * 15);
|
||||
|
||||
shipA.rectangle.x = (long)((((shipA.position.xComponent - shipB.position.xComponent) - 32) + width/2) - (shipB.velocity.xComponent * 15));
|
||||
shipA.rectangle.y = (long)((((shipA.position.yComponent - shipB.position.yComponent) - 32) + height/2) - (shipB.velocity.yComponent * 15));
|
||||
|
||||
// Set the colour to black:
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
|
||||
// Clear the screen, filling it with black:
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
// Draw the starfield:
|
||||
starfieldRect.x = -900 - (long)shipB.position.xComponent % 800 - (shipB.velocity.xComponent * 15);
|
||||
starfieldRect.y = -900 - (long)shipB.position.yComponent % 800 - (shipB.velocity.yComponent * 15);
|
||||
while(starfieldRect.x <= (width + 800))
|
||||
{
|
||||
while(starfieldRect.y <= (height + 800))
|
||||
{
|
||||
SDL_RenderCopy(renderer, starfieldTexture, NULL, &starfieldRect);
|
||||
starfieldRect.y += 800;
|
||||
}
|
||||
starfieldRect.y = -900 - (long)shipB.position.yComponent % 800 - (shipB.velocity.yComponent * 15);
|
||||
starfieldRect.x += 800;
|
||||
}
|
||||
|
||||
// Draw the ship:
|
||||
SDL_RenderCopyEx(renderer, currentTexture, NULL, &shipA.rectangle,
|
||||
angleBetweenVectors(&shipA.engine, &upVector) + 90, NULL, 0);
|
||||
SDL_RenderCopyEx(renderer, currentTexture, NULL, &shipB.rectangle,
|
||||
angleBetweenVectors(&shipB.engine, &upVector) + 90, NULL, 0);
|
||||
|
||||
// Calculate the position of the black hole on screen and render it:
|
||||
blackHoleRectangle.x = ((long)(starPositionX - shipB.position.xComponent - (blackHoleRectangle.w / 2)) + width/2)
|
||||
- (shipB.velocity.xComponent * 15);
|
||||
blackHoleRectangle.y = ((long)(starPositionY - shipB.position.yComponent - (blackHoleRectangle.h / 2)) + height/2)
|
||||
- (shipB.velocity.yComponent * 15);
|
||||
SDL_RenderCopy(renderer, blackHoleTexture, NULL, &blackHoleRectangle);
|
||||
|
||||
// Present the rendered graphics:
|
||||
SDL_RenderPresent(renderer);
|
||||
shipAUpdated = false;
|
||||
shipBUpdated = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// ========================================================================================================
|
||||
// Local Variables:
|
||||
// compile-command: "gcc `sdl2-config --libs --cflags` Spacewar-Client.c spacewarPlayer.c spacewarGraphics.c -lSDL2_image -lSDL2_ttf -lm -o 'Spacewar Client!'"
|
||||
// End:
|
363
Spacewar.c
363
Spacewar.c
|
@ -1,363 +0,0 @@
|
|||
// Spacewar, Barra Ó Catháin.
|
||||
// ===================================
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <SDL2/SDL_timer.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <math.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include "xyVector.h"
|
||||
#include "spacewarPlayer.h"
|
||||
#include "spacewarGraphics.h"
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
SDL_Event event;
|
||||
int width = 0, height = 0;
|
||||
uint32_t rendererFlags = SDL_RENDERER_ACCELERATED;
|
||||
uint64_t thisFrameTime = SDL_GetPerformanceCounter(), lastFrameTime = 0;
|
||||
long starPositionX = 0, starPositionY = 0;
|
||||
double deltaTime = 0, frameAccumulator = 0;
|
||||
bool quit = false, rotatingClockwise = false, rotatingAnticlockwise = false, accelerating = false;
|
||||
xyVector engineVector = {0.85, 0}, upVector = {0, 0.1}, starPosition = {0, 0};
|
||||
|
||||
// Create the socket:
|
||||
int sendSocket = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sendSocket < 0)
|
||||
{
|
||||
fprintf(stderr, "\tSocket Creation is:\t\033[33;40mRED.\033[0m Aborting launch.\n");
|
||||
exit(0);
|
||||
}
|
||||
printf("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n");
|
||||
|
||||
// Create and fill the information needed to bind to the socket:
|
||||
struct sockaddr_in sendAddress;
|
||||
sendAddress.sin_family = AF_INET; // IPv4
|
||||
sendAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
sendAddress.sin_port = htons(12000);
|
||||
|
||||
int receiveSocket = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (receiveSocket < 0)
|
||||
{
|
||||
fprintf(stderr, "\tSocket Creation is:\t\033[33;40mRED.\033[0m Aborting launch.\n");
|
||||
exit(0);
|
||||
}
|
||||
printf("\tSocket Creation is:\t\033[32;40mGREEN.\033[0m\n");
|
||||
|
||||
// Make the socket timeout:
|
||||
struct timeval readTimeout;
|
||||
readTimeout.tv_sec = 0;
|
||||
readTimeout.tv_usec = 800;
|
||||
setsockopt(receiveSocket, SOL_SOCKET, SO_RCVTIMEO, &readTimeout, sizeof(readTimeout));
|
||||
|
||||
// Create and fill the information needed to bind to the socket:
|
||||
struct sockaddr_in receiveAddress;
|
||||
memset(&receiveAddress, 0, sizeof(receiveAddress));
|
||||
receiveAddress.sin_family = AF_INET; // IPv4
|
||||
receiveAddress.sin_addr.s_addr = INADDR_ANY;
|
||||
receiveAddress.sin_port = htons(12001);
|
||||
|
||||
// Bind to the socket:
|
||||
if (bind(receiveSocket, (const struct sockaddr *)&receiveAddress, sizeof(receiveAddress)) < 0)
|
||||
{
|
||||
perror("bind failed");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ship shipA = createShip(32, 32, 512, 512, 1, 0, 0);
|
||||
ship shipB = createShip(32, 32, -512, -512, 0, 1, 1);
|
||||
|
||||
// Initialize the SDL library, video, sound, and input:
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
|
||||
{
|
||||
printf("SDL Initialization Error: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
// Initialize image loading:
|
||||
IMG_Init(IMG_INIT_PNG);
|
||||
|
||||
// Initialize font support:
|
||||
TTF_Init();
|
||||
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "2");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
|
||||
playerController playerOne = createShipPlayerController(&shipA);
|
||||
playerController playerTwo = createShipPlayerController(&shipB);
|
||||
|
||||
// Create an SDL window and rendering context in that window:
|
||||
SDL_Window * window = SDL_CreateWindow("SDL_TEST", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 700, 700, 0);
|
||||
SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, rendererFlags);
|
||||
|
||||
// Set some properties for the window:
|
||||
SDL_SetWindowResizable(window, SDL_TRUE);
|
||||
SDL_SetWindowTitle(window, "Spacewar!");
|
||||
|
||||
int keyCount = 0;
|
||||
const uint8_t * keyboardState = SDL_GetKeyboardState(&keyCount);
|
||||
|
||||
SDL_Texture * starfieldTexture = IMG_LoadTexture(renderer, "./Images/Starfield.png");
|
||||
SDL_Rect starfieldRect;
|
||||
SDL_QueryTexture(starfieldTexture, NULL, NULL, NULL, &starfieldRect.h);
|
||||
SDL_QueryTexture(starfieldTexture, NULL, NULL, &starfieldRect.w, NULL);
|
||||
|
||||
TTF_Font * font = TTF_OpenFont("./Robtronika.ttf", 12);
|
||||
SDL_Color white = {255, 255, 255};
|
||||
|
||||
// Check for joysticks:
|
||||
if (SDL_NumJoysticks() < 1 )
|
||||
{
|
||||
playerOne.joystick = NULL;
|
||||
bool inputSelected = false;
|
||||
|
||||
// Prep the titlescreen struct:
|
||||
SpacewarTitlescreen titlescreen = prepareTitleScreen(window, renderer, "./Images/Starfield.png",
|
||||
"./Images/Title.png", font, "Press Enter to play.");
|
||||
|
||||
// Render the title text:
|
||||
while(!inputSelected)
|
||||
{
|
||||
// Draw the title screen according to the titlescreen struct:
|
||||
drawTitleScreen(&titlescreen);
|
||||
|
||||
// Wait until they press enter:
|
||||
SDL_PumpEvents();
|
||||
SDL_GetKeyboardState(&keyCount);
|
||||
if(keyboardState[SDL_SCANCODE_RETURN] == 1)
|
||||
{
|
||||
inputSelected = true;
|
||||
}
|
||||
|
||||
// Delay enough so that we run at 30 frames in the menu:
|
||||
SDL_Delay(1000 / 30);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Prep the titlescreen struct:
|
||||
SpacewarTitlescreen titlescreen = prepareTitleScreen(window, renderer, "./Images/Starfield.png",
|
||||
"./Images/Title.png", font,
|
||||
"Press Enter or Button 0 on your joystick to play.");
|
||||
|
||||
// Load all joysticks:
|
||||
int joystickListLength = SDL_NumJoysticks();
|
||||
SDL_Joystick ** joysticksList = calloc(joystickListLength, sizeof(SDL_Joystick*));
|
||||
|
||||
for(int index = 0; index < SDL_NumJoysticks(); index++)
|
||||
{
|
||||
joysticksList[index] = SDL_JoystickOpen(index);
|
||||
}
|
||||
|
||||
// Choose a player joystick:
|
||||
printf("Please press button zero on the controller you wish to use, or enter to play keyboard only.\n");
|
||||
|
||||
int joystickIndex = 0;
|
||||
bool inputSelected = false;
|
||||
|
||||
// Render the title text:
|
||||
while(!inputSelected)
|
||||
{
|
||||
// Draw the title screen according to the titlescreen struct:
|
||||
drawTitleScreen(&titlescreen);
|
||||
|
||||
SDL_PumpEvents();
|
||||
SDL_GetKeyboardState(&keyCount);
|
||||
if(keyboardState[SDL_SCANCODE_RETURN] == 1)
|
||||
{
|
||||
joysticksList[joystickIndex] = NULL;
|
||||
inputSelected = true;
|
||||
}
|
||||
joystickIndex++;
|
||||
if(joystickIndex >= joystickListLength)
|
||||
{
|
||||
joystickIndex = 0;
|
||||
}
|
||||
if(SDL_JoystickGetButton(joysticksList[joystickIndex], 0) == 1)
|
||||
{
|
||||
inputSelected = true;
|
||||
}
|
||||
|
||||
// Delay enough so that we run at 30 frames in the menu:
|
||||
SDL_Delay(1000 / 30);
|
||||
}
|
||||
|
||||
// Load joystick
|
||||
playerOne.joystick = joysticksList[joystickIndex];
|
||||
if (playerOne.joystick == NULL )
|
||||
{
|
||||
printf( "Warning: Unable to open game controller! SDL Error: %s\n", SDL_GetError() );
|
||||
}
|
||||
playerOne.haptic = SDL_HapticOpenFromJoystick(playerOne.joystick);
|
||||
SDL_HapticRumbleInit(playerOne.haptic);
|
||||
}
|
||||
|
||||
|
||||
// Load in all of our textures:
|
||||
SDL_Texture * blackHoleTexture, * idleTexture, * acceleratingTexture, * clockwiseTexture, * anticlockwiseTexture,
|
||||
* currentTexture, * acceleratingTexture2, *blackHolePointerTexture;
|
||||
|
||||
idleTexture = IMG_LoadTexture(renderer, "./Images/Ship-Idle.png");
|
||||
blackHoleTexture = IMG_LoadTexture(renderer, "./Images/Black-Hole.png");
|
||||
clockwiseTexture = IMG_LoadTexture(renderer, "./Images/Ship-Clockwise.png");
|
||||
acceleratingTexture = IMG_LoadTexture(renderer, "./Images/Ship-Accelerating.png");
|
||||
anticlockwiseTexture = IMG_LoadTexture(renderer, "./Images/Ship-Anticlockwise.png");
|
||||
blackHolePointerTexture = IMG_LoadTexture(renderer, "./Images/Black-Hole-Pointer.png");
|
||||
acceleratingTexture2 = IMG_LoadTexture(renderer, "./Images/Ship-Accelerating-Frame-2.png");
|
||||
currentTexture = acceleratingTexture;
|
||||
|
||||
SDL_Rect blackHoleRectangle;
|
||||
blackHoleRectangle.x = 0;
|
||||
blackHoleRectangle.y = 0;
|
||||
SDL_QueryTexture(blackHoleTexture, NULL, NULL, NULL, &blackHoleRectangle.h);
|
||||
SDL_QueryTexture(blackHoleTexture, NULL, NULL, &blackHoleRectangle.w, NULL);
|
||||
|
||||
SDL_Rect blackHolePointerRectangle;
|
||||
blackHolePointerRectangle.x = 0;
|
||||
blackHolePointerRectangle.y = 0;
|
||||
blackHolePointerRectangle.w = 128;
|
||||
blackHolePointerRectangle.h = 128;
|
||||
lastFrameTime = SDL_GetPerformanceCounter();
|
||||
thisFrameTime = SDL_GetPerformanceCounter();
|
||||
|
||||
while (!quit)
|
||||
{
|
||||
lastFrameTime = thisFrameTime;
|
||||
thisFrameTime = SDL_GetPerformanceCounter();
|
||||
deltaTime = (double)(((thisFrameTime - lastFrameTime) * 1000) / (double)SDL_GetPerformanceFrequency());
|
||||
|
||||
sendto(sendSocket, &shipA, sizeof(ship), 0, (const struct sockaddr *)&sendAddress, sizeof(sendAddress));
|
||||
sendto(sendSocket, &shipB, sizeof(ship), 0, (const struct sockaddr *)&sendAddress, sizeof(sendAddress));
|
||||
// Store the window's current width and height:
|
||||
SDL_GetWindowSize(window, &width, &height);
|
||||
|
||||
// Check input:
|
||||
while (SDL_PollEvent(&event))
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
{
|
||||
quit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap the positions if the ship goes interstellar:
|
||||
if(shipA.position.xComponent > 4096)
|
||||
{
|
||||
shipA.position.xComponent = -2000;
|
||||
}
|
||||
else if(shipA.position.xComponent < -4096)
|
||||
{
|
||||
shipA.position.xComponent = 2000;
|
||||
}
|
||||
if(shipA.position.yComponent > 4096)
|
||||
{
|
||||
shipA.position.yComponent = -2000;
|
||||
}
|
||||
else if(shipA.position.yComponent < -4096)
|
||||
{
|
||||
shipA.position.yComponent = 2000;
|
||||
}
|
||||
|
||||
if(shipB.position.xComponent > 4096)
|
||||
{
|
||||
shipB.position.xComponent = -2000;
|
||||
shipB.velocity.xComponent *= 0.9;
|
||||
}
|
||||
else if(shipB.position.xComponent < -4096)
|
||||
{
|
||||
shipB.position.xComponent = 2000;
|
||||
shipB.velocity.xComponent *= 0.9;
|
||||
}
|
||||
if(shipB.position.yComponent > 4096)
|
||||
{
|
||||
shipB.position.yComponent = -2000;
|
||||
shipB.velocity.yComponent *= 0.9;
|
||||
}
|
||||
else if(shipB.position.yComponent < -4096)
|
||||
{
|
||||
shipB.position.yComponent = 2000;
|
||||
shipB.velocity.yComponent *= 0.9;
|
||||
}
|
||||
|
||||
// Get the needed input:
|
||||
getPlayerInput(&playerOne);
|
||||
takeNetworkInput(&playerTwo, receiveSocket);
|
||||
|
||||
// Do the needed input:
|
||||
doShipInput(&playerOne, &shipA, starPosition, deltaTime);
|
||||
doShipInput(&playerTwo, &shipB, starPosition, deltaTime);
|
||||
|
||||
shipA.rectangle.x = (width/2) - 16 - (shipA.velocity.xComponent * 15);
|
||||
shipA.rectangle.y = (height/2) - 16 - (shipA.velocity.yComponent * 15);
|
||||
|
||||
shipB.rectangle.x = (long)((((shipB.position.xComponent - shipA.position.xComponent) - 32) + width/2) - (shipA.velocity.xComponent * 15));
|
||||
shipB.rectangle.y = (long)((((shipB.position.yComponent - shipA.position.yComponent) - 32) + height/2) - (shipA.velocity.yComponent * 15));
|
||||
|
||||
|
||||
// Set the colour to black:
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
|
||||
// Clear the screen, filling it with black:
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
starfieldRect.x = -900 - (long)shipA.position.xComponent % 800 - (shipA.velocity.xComponent * 15);
|
||||
starfieldRect.y = -900 - (long)shipA.position.yComponent % 800 - (shipA.velocity.yComponent * 15);
|
||||
while(starfieldRect.x <= (width + 800))
|
||||
{
|
||||
while(starfieldRect.y <= (height + 800))
|
||||
{
|
||||
SDL_RenderCopy(renderer, starfieldTexture, NULL, &starfieldRect);
|
||||
starfieldRect.y += 800;
|
||||
}
|
||||
starfieldRect.y = -900 - (long)shipA.position.yComponent % 800 - (shipA.velocity.yComponent * 15);
|
||||
starfieldRect.x += 800;
|
||||
}
|
||||
|
||||
// Draw the ship:
|
||||
SDL_RenderCopyEx(renderer, currentTexture, NULL, &shipA.rectangle,
|
||||
angleBetweenVectors(&shipA.engine, &upVector) + 90, NULL, 0);
|
||||
SDL_RenderCopyEx(renderer, currentTexture, NULL, &shipB.rectangle,
|
||||
angleBetweenVectors(&shipB.engine, &upVector) + 90, NULL, 0);
|
||||
|
||||
// Draw the black hole:
|
||||
blackHoleRectangle.x = ((long)(starPositionX - shipA.position.xComponent - (blackHoleRectangle.w / 2)) + width/2)
|
||||
- (shipA.velocity.xComponent * 15);
|
||||
blackHoleRectangle.y = ((long)(starPositionY - shipA.position.yComponent - (blackHoleRectangle.h / 2)) + height/2)
|
||||
- (shipA.velocity.yComponent * 15);
|
||||
SDL_RenderCopy(renderer, blackHoleTexture, NULL, &blackHoleRectangle);
|
||||
|
||||
// Set the colour to yellow:
|
||||
SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
|
||||
|
||||
// Draw a line representing the velocity:
|
||||
SDL_RenderDrawLine(renderer, width/2 - (shipA.velocity.xComponent * 15),
|
||||
height/2 - (shipA.velocity.yComponent * 15),
|
||||
(long)((width/2) + shipA.velocity.xComponent * 15) - (shipA.velocity.xComponent * 15),
|
||||
(long)((height/2) + shipA.velocity.yComponent * 15) - (shipA.velocity.yComponent * 15));
|
||||
|
||||
// Set the colour to blue:
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
|
||||
|
||||
// Draw a line representing the direction of the star:
|
||||
blackHolePointerRectangle.x = width/2 - (shipA.velocity.xComponent * 15) - (blackHolePointerRectangle.w / 2);
|
||||
blackHolePointerRectangle.y = height/2 - (shipA.velocity.yComponent * 15) - (blackHolePointerRectangle.h / 2);
|
||||
SDL_RenderCopyEx(renderer, blackHolePointerTexture, NULL, &blackHolePointerRectangle,
|
||||
angleBetweenVectors(&shipA.gravity, &upVector) + 90, NULL, 0);
|
||||
|
||||
// Present the rendered graphics:
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
// ========================================================================================================
|
||||
// Local Variables:
|
||||
// compile-command: "gcc `sdl2-config --libs --cflags` Spacewar.c spacewarPlayer.c spacewarGraphics.c -lSDL2_image -lSDL2_ttf -lm -o 'Spacewar!'"
|
||||
// End:
|
|
@ -0,0 +1,362 @@
|
|||
// =========================================
|
||||
// | Spacewar-Client.c |
|
||||
// | Copyright (C) 2023, Barra Ó Catháin |
|
||||
// | See end of file for copyright notice. |
|
||||
// =========================================
|
||||
#include <unistd.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <SDL2/SDL_timer.h>
|
||||
#include "Spacewar-Messages.h"
|
||||
#include "Spacewar-Graphics.h"
|
||||
#include "Spacewar-Server.h"
|
||||
|
||||
const char * messageStrings[] = {"HELLO", "GOODBYE", "PING", "PONG", "SECRET"};
|
||||
typedef struct SpacewarNetworkConfig
|
||||
{
|
||||
SpacewarClientInput input;
|
||||
SpacewarState * state;
|
||||
} SpacewarNetworkConfig;
|
||||
|
||||
void * runNetworkThread (void * parameters)
|
||||
{
|
||||
SpacewarNetworkConfig * configuration = (SpacewarNetworkConfig *)parameters;
|
||||
int udpSocket = 0;
|
||||
udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
|
||||
// Configure a timeout for receiving:
|
||||
struct timeval receiveTimeout;
|
||||
receiveTimeout.tv_sec = 0;
|
||||
receiveTimeout.tv_usec = 1000;
|
||||
setsockopt(udpSocket, SOL_SOCKET, SO_RCVTIMEO, &receiveTimeout, sizeof(struct timeval));
|
||||
|
||||
// Point at the server:
|
||||
struct sockaddr_in serverAddress;
|
||||
serverAddress.sin_family = AF_INET;
|
||||
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
serverAddress.sin_port = htons(5200);
|
||||
|
||||
// A structure to store the most recent state from the network:
|
||||
SpacewarState * updatedState = calloc(1, sizeof(SpacewarState));
|
||||
|
||||
while (true)
|
||||
{
|
||||
sendto(udpSocket, &configuration->input, sizeof(SpacewarClientInput), 0,
|
||||
(struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in));
|
||||
recvfrom(udpSocket, updatedState, sizeof(SpacewarState), 0, NULL, NULL);
|
||||
memcpy(configuration->state, updatedState, sizeof(SpacewarState));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv)
|
||||
{
|
||||
xyVector upVector = {0, 0.1};
|
||||
|
||||
// Initialize the SDL library, video, sound, and input:
|
||||
if (SDL_Init(SDL_INIT_EVERYTHING) != 0)
|
||||
{
|
||||
printf("SDL Initialization Error: %s\n", SDL_GetError());
|
||||
}
|
||||
|
||||
// Initialize image loading:
|
||||
IMG_Init(IMG_INIT_PNG);
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
|
||||
|
||||
// Initialize font support:
|
||||
TTF_Init();
|
||||
|
||||
// Create an SDL window and rendering context in that window:
|
||||
SDL_Window * window = SDL_CreateWindow("SDL_TEST", SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED, 640, 360, 0);
|
||||
uint32_t rendererFlags = SDL_RENDERER_ACCELERATED;
|
||||
SDL_Renderer * renderer = SDL_CreateRenderer(window, -1, rendererFlags);
|
||||
SDL_SetWindowTitle(window, "Spacewar!");
|
||||
SDL_SetWindowResizable(window, SDL_TRUE);
|
||||
|
||||
// Set up keyboard input:
|
||||
int keyCount = 0;
|
||||
const uint8_t * keyboardState = SDL_GetKeyboardState(&keyCount);
|
||||
|
||||
// Prep the titlescreen struct:
|
||||
bool titlescreenInput = false;
|
||||
TTF_Font * font = TTF_OpenFont("../Robtronika.ttf", 12);
|
||||
SpacewarTitlescreen titlescreen = prepareTitleScreen(window, renderer,
|
||||
"../Images/Starfield.png",
|
||||
"../Images/Title.png", font,
|
||||
"Press Enter or Button 0.");
|
||||
// Set up event handling:
|
||||
SDL_Event event;
|
||||
bool keepRunning = true;
|
||||
bool runServer = false;
|
||||
|
||||
// Display the titlescreen until we get an input:
|
||||
while (!titlescreenInput && keepRunning)
|
||||
{
|
||||
// Update events and input:
|
||||
SDL_PumpEvents();
|
||||
SDL_GetKeyboardState(&keyCount);
|
||||
|
||||
// Check windowing system events:
|
||||
while (SDL_PollEvent(&event) != 0)
|
||||
{
|
||||
switch (event.type)
|
||||
{
|
||||
case SDL_QUIT:
|
||||
{
|
||||
keepRunning = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if Enter was pressed:
|
||||
if(keyboardState[SDL_SCANCODE_RETURN] == 1)
|
||||
{
|
||||
titlescreenInput = true;
|
||||
}
|
||||
|
||||
// Check if Space was pressed:
|
||||
if(keyboardState[SDL_SCANCODE_SPACE] == 1 && !runServer)
|
||||
{
|
||||
runServer = true;
|
||||
printf("Running server!\n");
|
||||
}
|
||||
|
||||
// Draw the title screen:
|
||||
drawTitleScreen(&titlescreen);
|
||||
|
||||
// Delay enough so that we run at 60 frames in the menu:
|
||||
SDL_Delay(1000 / 60);
|
||||
}
|
||||
|
||||
if (runServer)
|
||||
{
|
||||
SpacewarServerConfiguration serverConfig = {5200};
|
||||
pthread_t serverThread;
|
||||
pthread_create(&serverThread, NULL, runSpacewarServer, &serverConfig);
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
// Give me a socket, and make sure it's working:
|
||||
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (serverSocket == -1)
|
||||
{
|
||||
printf("Socket creation failed.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Create an address struct to point at the server:
|
||||
struct sockaddr_in serverAddress;
|
||||
serverAddress.sin_family = AF_INET;
|
||||
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
|
||||
serverAddress.sin_port = htons(5200);
|
||||
|
||||
// Connect to the server:
|
||||
if (connect(serverSocket, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in)) != 0)
|
||||
{
|
||||
fprintf(stderr, "Connecting to the server failed.\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
printf("Connected.\n");
|
||||
|
||||
bool playerNumberSet, secretKeySet;
|
||||
uint8_t playerNumber;
|
||||
SpacewarNetworkConfig networkConfiguration;
|
||||
SpacewarMessage message;
|
||||
|
||||
while (!playerNumberSet || !secretKeySet)
|
||||
{
|
||||
recv(serverSocket, &message, sizeof(SpacewarMessage), 0);
|
||||
switch (message.type)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
playerNumberSet = true;
|
||||
playerNumber = message.content;
|
||||
networkConfiguration.input.playerNumber = message.content;
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
secretKeySet = true;
|
||||
networkConfiguration.input.secret = message.content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SpacewarState * state = calloc(1, sizeof(SpacewarState));
|
||||
networkConfiguration.state = state;
|
||||
|
||||
// Spawn network thread:
|
||||
pthread_t networkThread;
|
||||
pthread_create(&networkThread, NULL, runNetworkThread, &networkConfiguration);
|
||||
|
||||
// Spawn client-side-prediction thread:
|
||||
if (!runServer)
|
||||
{
|
||||
pthread_t clientSidePredictionThread;
|
||||
}
|
||||
|
||||
// Load in all of our textures:
|
||||
SDL_Texture * blackHoleTexture, * idleTexture, * acceleratingTexture, * clockwiseTexture,
|
||||
* anticlockwiseTexture, * currentTexture, * acceleratingTexture2, *blackHolePointerTexture;
|
||||
|
||||
SDL_Rect starfieldRect;
|
||||
SDL_Texture * starfieldTexture = IMG_LoadTexture(renderer, "../Images/Starfield.png");
|
||||
SDL_QueryTexture(starfieldTexture, NULL, NULL, &starfieldRect.w, &starfieldRect.h);
|
||||
|
||||
idleTexture = IMG_LoadTexture(renderer, "../Images/Ship-Idle.png");
|
||||
blackHoleTexture = IMG_LoadTexture(renderer, "../Images/Black-Hole.png");
|
||||
clockwiseTexture = IMG_LoadTexture(renderer, "../Images/Ship-Clockwise.png");
|
||||
acceleratingTexture = IMG_LoadTexture(renderer, "../Images/Ship-Accelerating.png");
|
||||
anticlockwiseTexture = IMG_LoadTexture(renderer, "../Images/Ship-Anticlockwise.png");
|
||||
blackHolePointerTexture = IMG_LoadTexture(renderer, "../Images/Black-Hole-Pointer.png");
|
||||
acceleratingTexture2 = IMG_LoadTexture(renderer, "../Images/Ship-Accelerating-Frame-2.png");
|
||||
currentTexture = acceleratingTexture;
|
||||
|
||||
SDL_Rect blackHoleRectangle;
|
||||
blackHoleRectangle.x = 0;
|
||||
blackHoleRectangle.y = 0;
|
||||
SDL_QueryTexture(blackHoleTexture, NULL, NULL,
|
||||
&blackHoleRectangle.w, &blackHoleRectangle.h);
|
||||
|
||||
|
||||
SDL_Rect blackHolePointerRectangle;
|
||||
blackHolePointerRectangle.x = 0;
|
||||
blackHolePointerRectangle.y = 0;
|
||||
blackHolePointerRectangle.w = 128;
|
||||
blackHolePointerRectangle.h = 128;
|
||||
|
||||
SDL_Rect shipRectangles[32];
|
||||
for (int index = 0; index < 32; index++)
|
||||
{
|
||||
shipRectangles[index].w = 32;
|
||||
shipRectangles[index].h = 32;
|
||||
}
|
||||
|
||||
SDL_Rect rendererSize;
|
||||
int width, height;
|
||||
while (true)
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
SDL_GetKeyboardState(&keyCount);
|
||||
//SDL_GetWindowSize(window, &width, &height);
|
||||
|
||||
// Do input:
|
||||
networkConfiguration.input.input.turningClockwise = keyboardState[SDL_SCANCODE_RIGHT];
|
||||
networkConfiguration.input.input.turningAnticlockwise = keyboardState[SDL_SCANCODE_LEFT];
|
||||
networkConfiguration.input.input.accelerating = keyboardState[SDL_SCANCODE_UP];
|
||||
|
||||
if (keyboardState[SDL_SCANCODE_PAGEUP] == 1)
|
||||
{
|
||||
float scaleX, scaleY;
|
||||
SDL_RenderGetScale(renderer, &scaleX, &scaleY);
|
||||
SDL_RenderSetScale(renderer, scaleX + 0.01, scaleY + 0.01);
|
||||
}
|
||||
|
||||
if (keyboardState[SDL_SCANCODE_PAGEDOWN] == 1)
|
||||
{
|
||||
float scaleX, scaleY;
|
||||
SDL_RenderGetScale(renderer, &scaleX, &scaleY);
|
||||
SDL_RenderSetScale(renderer, scaleX - 0.01, scaleY - 0.01);
|
||||
}
|
||||
|
||||
SDL_RenderGetViewport(renderer, &rendererSize);
|
||||
width = rendererSize.w;
|
||||
height = rendererSize.h;
|
||||
|
||||
// Clear the screen:
|
||||
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
// Draw the starfield:
|
||||
starfieldRect.x = -900 - (long)state->playerStates[playerNumber].position.xComponent % 800;
|
||||
starfieldRect.y = -900 - (long)state->playerStates[playerNumber].position.yComponent % 800;
|
||||
|
||||
while(starfieldRect.x <= (width + 800))
|
||||
{
|
||||
while(starfieldRect.y <= (height + 800))
|
||||
{
|
||||
SDL_RenderCopy(renderer, starfieldTexture, NULL, &starfieldRect);
|
||||
starfieldRect.y += 800;
|
||||
}
|
||||
starfieldRect.y = -900 - (long)state->playerStates[playerNumber].position.yComponent % 800;
|
||||
starfieldRect.x += 800;
|
||||
}
|
||||
|
||||
// Draw the black hole:
|
||||
blackHoleRectangle.x = ((long)(0 - state->playerStates[playerNumber].position.xComponent
|
||||
- (blackHoleRectangle.w / 2)) + width/2);
|
||||
blackHoleRectangle.y = ((long)(0 - state->playerStates[playerNumber].position.yComponent
|
||||
- (blackHoleRectangle.h / 2)) + height/2);
|
||||
|
||||
SDL_RenderCopy(renderer, blackHoleTexture, NULL, &blackHoleRectangle);
|
||||
|
||||
// Draw the black hole directional indicator:
|
||||
blackHolePointerRectangle.x = width/2 - (blackHolePointerRectangle.w / 2);
|
||||
blackHolePointerRectangle.y = height/2 - (blackHolePointerRectangle.h / 2);
|
||||
SDL_RenderCopyEx(renderer, blackHolePointerTexture, NULL, &blackHolePointerRectangle,
|
||||
angleBetweenVectors(&state->playerStates[playerNumber].gravity, &upVector) + 90,
|
||||
NULL, 0);
|
||||
|
||||
// Draw the player's ship:
|
||||
shipRectangles[playerNumber].x = (width/2) - 16;
|
||||
shipRectangles[playerNumber].y = (height/2) - 16;
|
||||
|
||||
SDL_RenderCopyEx(renderer, currentTexture, NULL, &shipRectangles[playerNumber],
|
||||
angleBetweenVectors(&state->playerStates[playerNumber].engine, &upVector) + 90,
|
||||
NULL, 0);
|
||||
|
||||
// Draw every other ship:
|
||||
for (int index = 0; index < 32; index++)
|
||||
{
|
||||
if (index == playerNumber)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (state->playerStates[playerNumber].inPlay == true)
|
||||
{
|
||||
shipRectangles[index].x = ((long)(state->playerStates[index].position.xComponent -
|
||||
state->playerStates[playerNumber].position.xComponent) -
|
||||
32 + (width/2));
|
||||
shipRectangles[index].y = ((long)(state->playerStates[index].position.yComponent -
|
||||
state->playerStates[playerNumber].position.yComponent) -
|
||||
32 + (height/2));
|
||||
|
||||
SDL_RenderCopyEx(renderer, currentTexture, NULL, &shipRectangles[index],
|
||||
angleBetweenVectors(&state->playerStates[index].engine, &upVector) + 90,
|
||||
NULL, 0);
|
||||
}
|
||||
}
|
||||
// Present to the screen:
|
||||
SDL_RenderPresent(renderer);
|
||||
SDL_Delay(1000 / 144);
|
||||
}
|
||||
pthread_join(networkThread, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
// =======================================================
|
||||
// | End of Spacewar-Client.c, copyright notice follows. |
|
||||
// =======================================================
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Local Variables:
|
||||
// compile-command: "gcc `sdl2-config --libs --cflags` Spacewar-Client.c Spacewar-Graphics.c Spacewar-Server.c Spacewar-Physics.c -lSDL2_image -lSDL2_ttf -lm -o 'Spacewar-Client'"
|
||||
// End:
|
|
@ -1,10 +1,11 @@
|
|||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <SDL2/SDL_timer.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "spacewarGraphics.h"
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_ttf.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
#include <SDL2/SDL_timer.h>
|
||||
|
||||
#include "Spacewar-Graphics.h"
|
||||
|
||||
SpacewarTitlescreen prepareTitleScreen(SDL_Window * window, SDL_Renderer * renderer,
|
||||
char * starfieldTexturePath, char * logoTexturePath,
|
||||
|
@ -120,4 +121,37 @@ void drawTitleScreen(SpacewarTitlescreen * titlescreen)
|
|||
SDL_RenderPresent(titlescreen->renderer);
|
||||
}
|
||||
|
||||
/* void drawMenuScreen(SpacewarMenuscreen * menuscreen) */
|
||||
/* { */
|
||||
/* // Get the current size of the window: */
|
||||
/* int width = 0, height = 0; */
|
||||
/* SDL_GetWindowSize(menuscreen->window, &width, &height); */
|
||||
|
||||
/* // Set the renderer colour to black and clear the screen: */
|
||||
/* SDL_SetRenderDrawColor(menuscreen->renderer, 0, 0, 0, 255); */
|
||||
/* SDL_RenderClear(menuscreen->renderer); */
|
||||
|
||||
/* // Set the correct position to begin the starfield, and scroll it back for the next frame: */
|
||||
/* menuscreen->starfieldRectangle->x = 0 - menuscreen->xScroll++; */
|
||||
/* menuscreen->starfieldRectangle->y = 0; */
|
||||
|
||||
/* // Draw the starfield by tiling the starfield texture: */
|
||||
/* while (menuscreen->starfieldRectangle->x <= (width + menuscreen->starfieldRectangle->w)) */
|
||||
/* { */
|
||||
/* // Go down, covering a column of the screen: */
|
||||
/* while(menuscreen->starfieldRectangle->y <= (height + menuscreen->starfieldRectangle->h)) */
|
||||
/* { */
|
||||
/* SDL_RenderCopy(menuscreen->renderer, menuscreen->starfieldTexture, NULL, */
|
||||
/* menuscreen->starfieldRectangle); */
|
||||
/* menuscreen->starfieldRectangle->y += menuscreen->starfieldRectangle->h; */
|
||||
/* } */
|
||||
|
||||
/* // Back to the top, move over one texture width: */
|
||||
/* menuscreen->starfieldRectangle->y = 0; */
|
||||
/* menuscreen->starfieldRectangle->x += menuscreen->starfieldRectangle->w; */
|
||||
/* } */
|
||||
|
||||
/* // Display to the renderer: */
|
||||
/* SDL_RenderPresent(menuscreen->renderer); */
|
||||
/* } */
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
// =========================================
|
||||
// | Spacewar-Graphics.h |
|
||||
// | Copyright (C) 2023, Barra Ó Catháin |
|
||||
// | See end of file for copyright notice. |
|
||||
// =========================================
|
||||
#ifndef SPACEWAR_GRAPHICS_H
|
||||
#define SPACEWAR_GRAPHICS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
typedef struct SpacewarTitlescreen
|
||||
{
|
||||
SDL_Window * window;
|
||||
SDL_Renderer * renderer;
|
||||
uint16_t xScroll, titleAlpha, textAlpha;
|
||||
SDL_Texture * titleTexture, * textTexture, * starfieldTexture;
|
||||
SDL_Rect * titleRectangle, * textRectangle, * starfieldRectangle;
|
||||
} SpacewarTitlescreen;
|
||||
|
||||
typedef struct SpacewarMenuscreen
|
||||
{
|
||||
SDL_Window * window;
|
||||
SDL_Renderer * renderer;
|
||||
uint16_t xScroll;
|
||||
SDL_Texture * starfieldTexture;
|
||||
} SpacewarMenuscreen;
|
||||
|
||||
SpacewarTitlescreen prepareTitleScreen(SDL_Window * window, SDL_Renderer * renderer,
|
||||
char * starfieldTexturePath, char * logoTexturePath,
|
||||
TTF_Font * font, char * text);
|
||||
|
||||
SpacewarMenuscreen prepareMenuscreenFromTitle(SpacewarTitlescreen * titlescreen);
|
||||
|
||||
void drawTitleScreen(SpacewarTitlescreen * titlescreen);
|
||||
|
||||
void drawMenuScreen(SpacewarMenuscreen * menuscreen);
|
||||
|
||||
#endif
|
||||
// =========================================================
|
||||
// | End of Spacewar-Graphics.h, copyright notice follows. |
|
||||
// =========================================================
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,40 @@
|
|||
// =========================================
|
||||
// | Spacewar-Messages.h |
|
||||
// | Copyright (C) 2023, Barra Ó Catháin |
|
||||
// | See end of file for copyright notice. |
|
||||
// =========================================
|
||||
#ifndef SPACEWAR_MESSAGES_H
|
||||
#define SPACEWAR_MESSAGES_H
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct SpacewarMessage
|
||||
{
|
||||
uint8_t type;
|
||||
uint32_t content;
|
||||
} SpacewarMessage;
|
||||
|
||||
/* Message Types:
|
||||
0 - HELLO: Contents sent to client indicate a given player number.
|
||||
1 - GOODBYE: No contents, end the connection.
|
||||
2 - PING: Contents indicate the missed amount of pongs.
|
||||
3 - PONG: No contents.
|
||||
4 - SECRET: Contents indicate the secret key that must be sent with UDP packets to the server.
|
||||
*/
|
||||
|
||||
#endif
|
||||
// =========================================================
|
||||
// | End of Spacewar-Messages.h, copyright notice follows. |
|
||||
// =========================================================
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,100 @@
|
|||
// =========================================
|
||||
// | Spacewar-Physics.c |
|
||||
// | Copyright (C) 2023, Barra Ó Catháin |
|
||||
// | See end of file for copyright notice. |
|
||||
// =========================================
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include "Spacewar-Physics.h"
|
||||
|
||||
void doPhysicsTick(SpacewarState * state)
|
||||
{
|
||||
double gravityMagnitude, gravityAcceleration, velocityMagnitude;
|
||||
for (int shipIndex = 0; shipIndex < 32; shipIndex++)
|
||||
{
|
||||
SpacewarShipState * currentShip = &state->playerStates[shipIndex];
|
||||
if (currentShip->inPlay)
|
||||
{
|
||||
// Calculate Gravity:
|
||||
xyVectorBetweenPoints(currentShip->position.xComponent, currentShip->position.yComponent,
|
||||
0.0, 0.0, ¤tShip->gravity);
|
||||
gravityMagnitude = normalizeXYVector(¤tShip->gravity);
|
||||
gravityAcceleration = 0;
|
||||
|
||||
// Some maths that felt okay:
|
||||
if (gravityMagnitude >= 116)
|
||||
{
|
||||
gravityAcceleration = (45000 / pow(gravityMagnitude, 2)) * 6.67;
|
||||
}
|
||||
// We're pactually in the black hole; teleport:
|
||||
else
|
||||
{
|
||||
currentShip->position.xComponent = (double)(random() % 7500);
|
||||
currentShip->position.yComponent = (double)(random() % 7500);
|
||||
currentShip->velocity.xComponent = 0;
|
||||
currentShip->velocity.yComponent = 0;
|
||||
}
|
||||
|
||||
multiplyXYVector(¤tShip->gravity, gravityAcceleration);
|
||||
|
||||
// Apply Inputs:
|
||||
|
||||
// Rotate the engine vector if needed:
|
||||
if (state->playerInputs[shipIndex].turningClockwise == 1)
|
||||
{
|
||||
rotateXYVector(¤tShip->engine, 2.5);
|
||||
}
|
||||
if (state->playerInputs[shipIndex].turningAnticlockwise == 1)
|
||||
{
|
||||
rotateXYVector(¤tShip->engine, -2.5);
|
||||
}
|
||||
|
||||
// Apply Gravity and Velocity to Position:
|
||||
if (state->playerInputs[shipIndex].accelerating == 1)
|
||||
{
|
||||
addXYVector(¤tShip->velocity, ¤tShip->engine);
|
||||
}
|
||||
addXYVector(¤tShip->velocity, ¤tShip->gravity);
|
||||
addXYVector(¤tShip->position, ¤tShip->velocity);
|
||||
|
||||
// Wrap position to game field:
|
||||
if (currentShip->position.xComponent > 8000.0)
|
||||
{
|
||||
state->playerStates[shipIndex].position.xComponent = -7999.0;
|
||||
state->playerStates[shipIndex].velocity.xComponent *= 0.9;
|
||||
}
|
||||
if (currentShip->position.xComponent < -8000.0)
|
||||
{
|
||||
state->playerStates[shipIndex].position.xComponent = 7999.0;
|
||||
state->playerStates[shipIndex].velocity.xComponent *= 0.9;
|
||||
}
|
||||
if (currentShip->position.yComponent > 8000.0)
|
||||
{
|
||||
state->playerStates[shipIndex].position.yComponent = -7999.0;
|
||||
state->playerStates[shipIndex].velocity.yComponent *= 0.9;
|
||||
}
|
||||
if (currentShip->position.yComponent < -8000.0)
|
||||
{
|
||||
state->playerStates[shipIndex].position.yComponent = 7999.0;
|
||||
state->playerStates[shipIndex].velocity.yComponent *= 0.9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// ========================================================
|
||||
// | End of Spacewar-Physics.c, copyright notice follows. |
|
||||
// ========================================================
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,62 @@
|
|||
// =========================================
|
||||
// | Spacewar-Physics.h |
|
||||
// | Copyright (C) 2023, Barra Ó Catháin |
|
||||
// | See end of file for copyright notice. |
|
||||
// =========================================
|
||||
#ifndef SPACEWAR_PHYSICS
|
||||
#define SPACEWAR_PHYSICS
|
||||
#include <stdint.h>
|
||||
#include "xyVector.h"
|
||||
#include "Spacewar-Server.h"
|
||||
|
||||
typedef struct SpacewarShipState
|
||||
{
|
||||
bool inPlay;
|
||||
xyVector engine;
|
||||
xyVector gravity;
|
||||
xyVector position;
|
||||
xyVector velocity;
|
||||
} SpacewarShipState;
|
||||
|
||||
|
||||
typedef struct SpacewarShipInput
|
||||
{
|
||||
double turningAmount, acceleratingAmount;
|
||||
uint8_t turningClockwise, turningAnticlockwise, accelerating;
|
||||
} SpacewarShipInput;
|
||||
|
||||
typedef struct SpacewarClientInput
|
||||
{
|
||||
uint8_t playerNumber;
|
||||
uint32_t secret;
|
||||
SpacewarShipInput input;
|
||||
} SpacewarClientInput;
|
||||
|
||||
typedef struct SpacewarState
|
||||
{
|
||||
uint64_t tickNumber;
|
||||
struct timeval timestamp;
|
||||
SpacewarShipState playerStates[32];
|
||||
SpacewarShipInput playerInputs[32];
|
||||
} SpacewarState;
|
||||
|
||||
// Does a single step of the physics:
|
||||
void doPhysicsTick(SpacewarState * state);
|
||||
|
||||
#endif
|
||||
// ========================================================
|
||||
// | End of Spacewar-Physics.h, copyright notice follows. |
|
||||
// ========================================================
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,294 @@
|
|||
// =========================================
|
||||
// | Spacewar Server.c |
|
||||
// | Copyright (C) 2023, Barra Ó Catháin |
|
||||
// | See end of file for copyright notice. |
|
||||
// =========================================
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/types.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include "Spacewar-Messages.h"
|
||||
#include "Spacewar-Physics.h"
|
||||
|
||||
SpacewarConnection * getConnectionBySocket(SpacewarConnection * connections, size_t connectionCount, int socket)
|
||||
{
|
||||
for (size_t connectionIndex = 0; connectionIndex < connectionCount; connectionIndex++)
|
||||
{
|
||||
if (connections[connectionIndex].clientSocket == socket)
|
||||
{
|
||||
return &connections[connectionIndex];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void sendCurrentState(SpacewarState * state, SpacewarConnection * connections, int udpSocket)
|
||||
{
|
||||
for (int connectionIndex = 0; connectionIndex < 32; connectionIndex++)
|
||||
{
|
||||
if (connections[connectionIndex].active)
|
||||
{
|
||||
sendto(udpSocket, state, sizeof(SpacewarState), 0,
|
||||
(struct sockaddr *)&connections[connectionIndex].clientAddress, sizeof(struct sockaddr_in));
|
||||
}
|
||||
}
|
||||
}
|
||||
void * runServerPhysics(void * parameters)
|
||||
{
|
||||
SpacewarServerSharedState * sharedState = (SpacewarServerSharedState *)parameters;
|
||||
for(int index = 0; index < 32; index++)
|
||||
{
|
||||
sharedState->physicsState->playerStates[index].engine.yComponent = 0.1;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
doPhysicsTick(sharedState->physicsState);
|
||||
sendCurrentState(sharedState->physicsState, sharedState->connections, sharedState->udpSocket);
|
||||
usleep(15625);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void * runInputReceiver(void * parameters)
|
||||
{
|
||||
SpacewarServerSharedState * sharedState = (SpacewarServerSharedState *)parameters;
|
||||
int bytesRead;
|
||||
socklen_t socketAddressLength;
|
||||
struct sockaddr_in clientAddress;
|
||||
|
||||
SpacewarClientInput input;
|
||||
|
||||
while (true)
|
||||
{
|
||||
bytesRead = recvfrom(sharedState->udpSocket, &input, sizeof(SpacewarClientInput), 0,
|
||||
(struct sockaddr *)&clientAddress, &socketAddressLength);
|
||||
if (bytesRead == sizeof(SpacewarClientInput))
|
||||
{
|
||||
if (input.playerNumber < 32)
|
||||
{
|
||||
if (input.secret == sharedState->connections[input.playerNumber].playerSecret)
|
||||
{
|
||||
sharedState->physicsState->playerStates[input.playerNumber].inPlay = true;
|
||||
memcpy(&sharedState->connections[input.playerNumber].clientAddress,
|
||||
&clientAddress, sizeof(struct sockaddr_in));
|
||||
memcpy(&sharedState->physicsState->playerInputs[input.playerNumber], &input.input,
|
||||
sizeof(SpacewarShipInput));
|
||||
}
|
||||
}
|
||||
}
|
||||
bzero(&input, sizeof(SpacewarClientInput));
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Adds a new player to a physics simulation. Returns a randomly generated secret key:
|
||||
uint32_t addPlayer(SpacewarConnection * connection, int playerNumber, SpacewarState * state)
|
||||
{
|
||||
connection->playerSecret = rand();
|
||||
state->playerStates[playerNumber].inPlay = false;
|
||||
|
||||
return connection->playerSecret;
|
||||
}
|
||||
|
||||
// Creates a Spacewar server, intended to be ran by the standalone server or forked by the game client:
|
||||
void * runSpacewarServer(void * configuration)
|
||||
{
|
||||
SpacewarServerConfiguration * serverConfig = (SpacewarServerConfiguration *)configuration;
|
||||
printf("Starting Server.\n");
|
||||
|
||||
// Create our network listeners:
|
||||
int masterSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (masterSocket < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to create socket.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
setsockopt(masterSocket, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
|
||||
setsockopt(masterSocket, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int));
|
||||
|
||||
// Create a structure to bind the listening socket:
|
||||
struct sockaddr_in listeningAddress;
|
||||
memset(&listeningAddress, 0, sizeof(listeningAddress));
|
||||
listeningAddress.sin_family = AF_INET; // IPv4
|
||||
listeningAddress.sin_addr.s_addr = INADDR_ANY;
|
||||
listeningAddress.sin_port = htons(serverConfig->port);
|
||||
|
||||
// Bind to the listening socket:
|
||||
if (bind(masterSocket, (const struct sockaddr *)&listeningAddress, sizeof(listeningAddress)) < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to bind socket.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Begin listening on the master socket:
|
||||
listen(masterSocket, 32);
|
||||
|
||||
// Create an epoll descriptor to keep track of clients:
|
||||
int recievedEventCount = 0;
|
||||
struct epoll_event receivedEvents[32];
|
||||
int epollDescriptor = epoll_create(1);
|
||||
|
||||
// Add the master socket to the epoll set:
|
||||
struct epoll_event requestedEvents;
|
||||
requestedEvents.events = EPOLLIN | EPOLLET;
|
||||
requestedEvents.data.fd = masterSocket;
|
||||
epoll_ctl(epollDescriptor, EPOLL_CTL_ADD, masterSocket, &requestedEvents);
|
||||
|
||||
// Create a set of connection structs to store the current connection information:
|
||||
SpacewarConnection * connectedClients = calloc(32, sizeof(SpacewarConnection));
|
||||
for(int index = 0; index < 32; index++)
|
||||
{
|
||||
connectedClients[index].active = false;
|
||||
}
|
||||
|
||||
// Create a UDP socket:
|
||||
int udpSocket = 0;
|
||||
if ((udpSocket = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
|
||||
{
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Create a struct to bind the UDP socket to a port:
|
||||
struct sockaddr_in serverAddress;
|
||||
memset(&serverAddress, 0, sizeof(serverAddress));
|
||||
serverAddress.sin_family = AF_INET;
|
||||
serverAddress.sin_port = htons(5200);
|
||||
serverAddress.sin_addr.s_addr = INADDR_ANY;
|
||||
|
||||
// Bind it:
|
||||
bind(udpSocket, (struct sockaddr *)&serverAddress, sizeof(struct sockaddr_in));
|
||||
|
||||
// Setup the server threads:
|
||||
pthread_t physicsThread, inputThread;
|
||||
SpacewarState * currentState = calloc(1, sizeof(SpacewarState));
|
||||
SpacewarServerSharedState threadState;
|
||||
threadState.udpSocket = udpSocket;
|
||||
threadState.physicsState = currentState;
|
||||
threadState.connections = connectedClients;
|
||||
|
||||
// Begin the simulation:
|
||||
pthread_create(&inputThread, NULL, runInputReceiver, &threadState);
|
||||
pthread_create(&physicsThread, NULL, runServerPhysics, &threadState);
|
||||
|
||||
// Manage clients and sending packets back and forth:
|
||||
while (true)
|
||||
{
|
||||
int receivedEventCount = epoll_wait(epollDescriptor, receivedEvents, 32, -1);
|
||||
for (int eventIndex = 0; eventIndex < receivedEventCount; eventIndex++)
|
||||
{
|
||||
// If there's activity on the master socket, there's a new connection:
|
||||
if (receivedEvents[eventIndex].data.fd == masterSocket)
|
||||
{
|
||||
struct sockaddr_in clientAddress;
|
||||
int newClientSocket = accept(masterSocket, NULL, NULL);
|
||||
|
||||
// Check that the socket is functional:
|
||||
if (newClientSocket < 0)
|
||||
{
|
||||
fprintf(stderr, "Failed to accept client connection.\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Register the new client in the epoll set:
|
||||
requestedEvents.events = EPOLLIN | EPOLLET;
|
||||
requestedEvents.data.fd = newClientSocket;
|
||||
epoll_ctl(epollDescriptor, EPOLL_CTL_ADD, newClientSocket, &requestedEvents);
|
||||
|
||||
for (int index = 0; index < 32; index++)
|
||||
{
|
||||
if (connectedClients[index].active == false)
|
||||
{
|
||||
// Configure the new connection:
|
||||
connectedClients[index].active = true;
|
||||
connectedClients[index].missedPongs = false;
|
||||
connectedClients[index].clientSocket = newClientSocket;
|
||||
|
||||
// Send the HELLO packet to the player:
|
||||
SpacewarMessage helloMessage;
|
||||
helloMessage.type = 0;
|
||||
helloMessage.content = index;
|
||||
send(newClientSocket, &helloMessage, sizeof(SpacewarMessage), 0);
|
||||
|
||||
// Add the player to the simulation:
|
||||
uint32_t secret = addPlayer(&connectedClients[index], index, currentState);
|
||||
|
||||
// Send the SECRET packet to the player:
|
||||
helloMessage.type = 4;
|
||||
helloMessage.content = secret;
|
||||
send(newClientSocket, &helloMessage, sizeof(SpacewarMessage), 0);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Otherwise, we've been sent a packet from one of the connected clients:
|
||||
else
|
||||
{
|
||||
SpacewarConnection * client = getConnectionBySocket(connectedClients, 32,
|
||||
receivedEvents->data.fd);
|
||||
|
||||
SpacewarMessage receivedMessage;
|
||||
size_t bytesRead = recv(client->clientSocket, &receivedMessage,
|
||||
sizeof(SpacewarMessage), 0);
|
||||
|
||||
if (bytesRead == 0)
|
||||
{
|
||||
// Send a goodbye message:
|
||||
SpacewarMessage goodbyeMessage;
|
||||
goodbyeMessage.type = 1;
|
||||
goodbyeMessage.content = 0;
|
||||
send(client->clientSocket, &goodbyeMessage, sizeof(SpacewarMessage), 0);
|
||||
|
||||
// Remove the socket from the epoll interest set:
|
||||
epoll_ctl(epollDescriptor, EPOLL_CTL_DEL, client->clientSocket, NULL);
|
||||
|
||||
// Remove the player from the simulation:
|
||||
//removePlayer(&connectedClients[index], currentState);
|
||||
|
||||
// Shutdown the socket:
|
||||
shutdown(client->clientSocket, SHUT_RDWR);
|
||||
|
||||
// Deactivate the connection:
|
||||
client->active = false;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
switch (receivedMessage.content)
|
||||
{
|
||||
// Handle message contents:
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
// | End of Spacewar-Server.c, copyright notice follows. |
|
||||
// =======================================================
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@ -0,0 +1,59 @@
|
|||
// =========================================
|
||||
// | Spacewar-Server.h |
|
||||
// | Copyright (C) 2023, Barra Ó Catháin |
|
||||
// | See end of file for copyright notice. |
|
||||
// =========================================
|
||||
#ifndef SPACEWAR_SERVER
|
||||
#define SPACEWAR_SERVER
|
||||
#include <sys/types.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include "Spacewar-Physics.h"
|
||||
|
||||
typedef struct SpacewarState SpacewarState;
|
||||
|
||||
typedef struct SpacewarConnection
|
||||
{
|
||||
bool active;
|
||||
int clientSocket;
|
||||
uint8_t missedPongs;
|
||||
uint32_t playerSecret;
|
||||
struct sockaddr_in clientAddress;
|
||||
} SpacewarConnection;
|
||||
|
||||
|
||||
typedef struct SpacewarServerConfiguration
|
||||
{
|
||||
uint16_t port;
|
||||
} SpacewarServerConfiguration;
|
||||
|
||||
|
||||
typedef struct SpacewarServerSharedState
|
||||
{
|
||||
int udpSocket;
|
||||
SpacewarState * physicsState;
|
||||
SpacewarConnection * connections;
|
||||
} SpacewarServerSharedState;
|
||||
|
||||
// Creates a spacewar server, intended to be ran by the standalone server or forked by the game client:
|
||||
void * runSpacewarServer(void * configuration);
|
||||
|
||||
#endif
|
||||
// =======================================================
|
||||
// | End of Spacewar-Server.h, copyright notice follows. |
|
||||
// =======================================================
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@ -13,7 +13,6 @@ static inline double angleBetweenVectors(xyVector * vectorA, xyVector * vectorB)
|
|||
{
|
||||
double dotProduct = (vectorA->xComponent * vectorB->xComponent) + (vectorA->yComponent * vectorB->yComponent);
|
||||
double determinant = (vectorA->xComponent * vectorB->yComponent) - (vectorA->yComponent * vectorB->xComponent);
|
||||
|
||||
return atan2(dotProduct, determinant) / 0.01745329;
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
#ifndef SPACEWAR_GRAPHICS_H
|
||||
#define SPACEWAR_GRAPHICS_H
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
typedef struct SpacewarTitlescreen
|
||||
{
|
||||
SDL_Window * window;
|
||||
SDL_Renderer * renderer;
|
||||
uint16_t xScroll, titleAlpha, textAlpha;
|
||||
SDL_Texture * titleTexture, * textTexture, * starfieldTexture;
|
||||
SDL_Rect * titleRectangle, * textRectangle, * starfieldRectangle;
|
||||
} SpacewarTitlescreen;
|
||||
|
||||
SpacewarTitlescreen prepareTitleScreen(SDL_Window * window, SDL_Renderer * renderer,
|
||||
char * starfieldTexturePath, char * logoTexturePath,
|
||||
TTF_Font * font, char * text);
|
||||
|
||||
void drawTitleScreen(SpacewarTitlescreen * titlescreen);
|
||||
|
||||
static inline void DrawCircle(SDL_Renderer * renderer, int32_t centreX, int32_t centreY, int32_t radius)
|
||||
{
|
||||
const int32_t diameter = (radius * 2);
|
||||
|
||||
int32_t x = (radius - 1);
|
||||
int32_t y = 0;
|
||||
int32_t tx = 1;
|
||||
int32_t ty = 1;
|
||||
int32_t error = (tx - diameter);
|
||||
|
||||
while (x >= y)
|
||||
{
|
||||
// Each of the following renders an octant of the circle
|
||||
SDL_RenderDrawPoint(renderer, centreX + x, centreY - y);
|
||||
SDL_RenderDrawPoint(renderer, centreX + x, centreY + y);
|
||||
SDL_RenderDrawPoint(renderer, centreX - x, centreY - y);
|
||||
SDL_RenderDrawPoint(renderer, centreX - x, centreY + y);
|
||||
SDL_RenderDrawPoint(renderer, centreX + y, centreY - x);
|
||||
SDL_RenderDrawPoint(renderer, centreX + y, centreY + x);
|
||||
SDL_RenderDrawPoint(renderer, centreX - y, centreY - x);
|
||||
SDL_RenderDrawPoint(renderer, centreX - y, centreY + x);
|
||||
|
||||
if (error <= 0)
|
||||
{
|
||||
++y;
|
||||
error += ty;
|
||||
ty += 2;
|
||||
}
|
||||
|
||||
if (error > 0)
|
||||
{
|
||||
--x;
|
||||
tx += 2;
|
||||
error += (tx - diameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
102
spacewarPlayer.c
102
spacewarPlayer.c
|
@ -1,102 +0,0 @@
|
|||
// spacewarPlayer.c: Contains function definitions for player interaction.
|
||||
// Barra Ó Catháin, 2023
|
||||
// =======================================================================
|
||||
#include <stdbool.h>
|
||||
#include <SDL2/SDL.h>
|
||||
#include <sys/types.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include "xyVector.h"
|
||||
#include "spacewarPlayer.h"
|
||||
|
||||
void takeNetworkInput(playerController * controller, int descriptor)
|
||||
{
|
||||
recvfrom(descriptor, controller, sizeof(playerController), 0, NULL, NULL);
|
||||
}
|
||||
|
||||
void getPlayerInput(playerController * controller)
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
const uint8_t * keyboardState = SDL_GetKeyboardState(NULL);
|
||||
if(keyboardState[SDL_SCANCODE_UP] == 1)
|
||||
{
|
||||
controller->accelerating = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
controller->accelerating = false;
|
||||
}
|
||||
if(keyboardState[SDL_SCANCODE_LEFT] == 1)
|
||||
{
|
||||
controller->turningAnticlockwise = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
controller->turningAnticlockwise = false;;
|
||||
}
|
||||
if(keyboardState[SDL_SCANCODE_RIGHT] == 1)
|
||||
{
|
||||
controller->turningClockwise = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
controller->turningClockwise = false;
|
||||
}
|
||||
if(controller->joystick != NULL)
|
||||
{
|
||||
controller->turningAmount = SDL_JoystickGetAxis(controller->joystick, 0);
|
||||
controller->acceleratingAmount = SDL_JoystickGetAxis(controller->joystick, 5);
|
||||
}
|
||||
}
|
||||
|
||||
void doShipInput(playerController * controller, ship * ship, xyVector starPosition, double deltaTime)
|
||||
{
|
||||
if(controller->number == ship->number)
|
||||
{
|
||||
// Calculate the gravity for the ships:
|
||||
calculateGravity(&starPosition, ship);
|
||||
|
||||
// Rotate the engine vector if needed:
|
||||
if (controller->turningClockwise)
|
||||
{
|
||||
rotateXYVector(&ship->engine, 0.25 * deltaTime);
|
||||
}
|
||||
else if (controller->turningAmount > 2500)
|
||||
{
|
||||
double rotationalSpeed = (controller->turningAmount / 20000);
|
||||
rotateXYVector(&ship->engine, 0.25 * deltaTime * rotationalSpeed);
|
||||
}
|
||||
|
||||
if (controller->turningAnticlockwise)
|
||||
{
|
||||
rotateXYVector(&ship->engine, -0.25 * deltaTime);
|
||||
}
|
||||
else if (controller->turningAmount < -2500)
|
||||
{
|
||||
double rotationalSpeed = (controller->turningAmount / 20000);
|
||||
rotateXYVector(&ship->engine, 0.25 * deltaTime * rotationalSpeed);
|
||||
}
|
||||
|
||||
// Calculate the new current velocity:
|
||||
addXYVectorDeltaScaled(&ship->velocity, &ship->gravity, deltaTime);
|
||||
|
||||
if (controller->acceleratingAmount > 2500)
|
||||
{
|
||||
xyVector temporary = ship->engine;
|
||||
multiplyXYVector(&ship->engine, controller->acceleratingAmount/ 32748);
|
||||
SDL_HapticRumblePlay(controller->haptic, (float)controller->acceleratingAmount / 32768, 20);
|
||||
addXYVectorDeltaScaled(&ship->velocity, &ship->engine, deltaTime);
|
||||
ship->engine = temporary;
|
||||
}
|
||||
|
||||
else if (controller->accelerating)
|
||||
{
|
||||
addXYVectorDeltaScaled(&ship->velocity, &ship->engine, deltaTime);
|
||||
}
|
||||
|
||||
// Calculate the new position:
|
||||
addXYVectorDeltaScaled(&ship->position, &ship->velocity, deltaTime);
|
||||
}
|
||||
}
|
110
spacewarPlayer.h
110
spacewarPlayer.h
|
@ -1,110 +0,0 @@
|
|||
#ifndef SPACEWARPLAYER_H
|
||||
#define SPACEWARPLAYER_H
|
||||
#include "xyVector.h"
|
||||
|
||||
// A struct storing the needed data to draw a ship:
|
||||
typedef struct ship
|
||||
{
|
||||
int number;
|
||||
xyVector engine;
|
||||
xyVector gravity;
|
||||
xyVector position;
|
||||
xyVector velocity;
|
||||
SDL_Rect rectangle;
|
||||
} ship;
|
||||
|
||||
// A struct to store the input state for one player:
|
||||
typedef struct playerController
|
||||
{
|
||||
SDL_Joystick * joystick;
|
||||
SDL_Haptic * haptic;
|
||||
int number;
|
||||
double turningAmount, acceleratingAmount;
|
||||
bool turningClockwise, turningAnticlockwise, accelerating;
|
||||
} playerController;
|
||||
|
||||
static inline void calculateGravity(xyVector * starPosition, ship * shipUnderGravity)
|
||||
{
|
||||
// Calculate the vector between the star and ship:
|
||||
xyVectorBetweenPoints(shipUnderGravity->position.xComponent, shipUnderGravity->position.yComponent,
|
||||
starPosition->xComponent, starPosition->yComponent, &shipUnderGravity->gravity);
|
||||
|
||||
// Make it into a unit vector:
|
||||
double gravityMagnitude = normalizeXYVector(&shipUnderGravity->gravity);
|
||||
double gravityAcceleration = 0;
|
||||
|
||||
// Calculate the gravity between the star and ship:
|
||||
if(gravityMagnitude != 0)
|
||||
{
|
||||
if(gravityMagnitude >= 116)
|
||||
{
|
||||
gravityAcceleration = pow(2, (3000 / (pow(gravityMagnitude, 2)))) / 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
shipUnderGravity->position.xComponent = random() % 4000;
|
||||
shipUnderGravity->position.yComponent = random() % 4000;
|
||||
shipUnderGravity->velocity.xComponent = 0;
|
||||
shipUnderGravity->velocity.yComponent = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gravityAcceleration = 1;
|
||||
}
|
||||
|
||||
if(gravityAcceleration < 0.01)
|
||||
{
|
||||
gravityAcceleration = 0.01;
|
||||
}
|
||||
|
||||
// Scale the vector:
|
||||
multiplyXYVector(&shipUnderGravity->gravity, gravityAcceleration);
|
||||
}
|
||||
|
||||
static inline playerController createShipPlayerController(ship * ship)
|
||||
{
|
||||
playerController newController;
|
||||
newController.number = ship->number;
|
||||
newController.joystick = NULL;
|
||||
return newController;
|
||||
}
|
||||
|
||||
// Create a ship with the given parameters:
|
||||
static inline ship createShip(int width, int height, double positionX, double positionY, double velocityX, double velocityY, int number)
|
||||
{
|
||||
ship newShip;
|
||||
|
||||
// Player number:
|
||||
newShip.number = number;
|
||||
|
||||
// Rectangle to show the ship in:
|
||||
newShip.rectangle.w = width;
|
||||
newShip.rectangle.h = height;
|
||||
|
||||
// Position:
|
||||
newShip.position.xComponent = positionX;
|
||||
newShip.position.yComponent = positionY;
|
||||
|
||||
// Velocity:
|
||||
newShip.velocity.xComponent = velocityX;
|
||||
newShip.velocity.yComponent = velocityY;
|
||||
|
||||
// Gravity:
|
||||
newShip.gravity.xComponent = 0;
|
||||
newShip.gravity.yComponent = 0;
|
||||
|
||||
// Engine:
|
||||
newShip.engine.yComponent = 0;
|
||||
newShip.engine.xComponent = 0.1;
|
||||
return newShip;
|
||||
}
|
||||
|
||||
// Function prototypes:
|
||||
void doShipInput(playerController * controller, ship * ship, xyVector starPosition, double deltaTime);
|
||||
|
||||
void takeNetworkInput(playerController * controller, int descriptor);
|
||||
|
||||
void getPlayerInput(playerController * controller);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue