Compare commits

...

7 Commits
main ... wip

9 changed files with 1196 additions and 0 deletions

362
source/Spacewar-Client.c Normal file
View File

@ -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:

157
source/Spacewar-Graphics.c Normal file
View File

@ -0,0 +1,157 @@
#include <stdint.h>
#include <stdlib.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,
TTF_Font * font, char * text)
{
SpacewarTitlescreen newTitleScreen;
// Basic state:
newTitleScreen.xScroll = 0;
newTitleScreen.textAlpha = 0;
newTitleScreen.titleAlpha = 0;
// Requirements for drawing:
newTitleScreen.window = window;
newTitleScreen.renderer = renderer;
// Textures:
newTitleScreen.titleTexture = IMG_LoadTexture(renderer, logoTexturePath);
newTitleScreen.starfieldTexture = IMG_LoadTexture(renderer, starfieldTexturePath);
// Text message:
SDL_Color white = {255, 255, 255};
SDL_Surface * fontSurface = TTF_RenderText_Blended(font, text, white);
newTitleScreen.textTexture = SDL_CreateTextureFromSurface(renderer, fontSurface);
SDL_FreeSurface(fontSurface);
// Rects:
newTitleScreen.titleRectangle = calloc(1, sizeof(SDL_Rect));
newTitleScreen.textRectangle = calloc(1, sizeof(SDL_Rect));
newTitleScreen.starfieldRectangle = calloc(1, sizeof(SDL_Rect));
// Set the rects to the size of the textures:
SDL_QueryTexture(newTitleScreen.textTexture, NULL, NULL, NULL, &newTitleScreen.textRectangle->h);
SDL_QueryTexture(newTitleScreen.textTexture, NULL, NULL, &newTitleScreen.textRectangle->w, NULL);
SDL_QueryTexture(newTitleScreen.titleTexture, NULL, NULL, NULL, &newTitleScreen.titleRectangle->h);
SDL_QueryTexture(newTitleScreen.titleTexture, NULL, NULL, &newTitleScreen.titleRectangle->w, NULL);
SDL_QueryTexture(newTitleScreen.starfieldTexture, NULL, NULL, NULL, &newTitleScreen.starfieldRectangle->h);
SDL_QueryTexture(newTitleScreen.starfieldTexture, NULL, NULL, &newTitleScreen.starfieldRectangle->w, NULL);
return newTitleScreen;
}
void drawTitleScreen(SpacewarTitlescreen * titlescreen)
{
// Get the current size of the window:
int width = 0, height = 0;
SDL_GetWindowSize(titlescreen->window, &width, &height);
// Position the elements on-screen:
titlescreen->titleRectangle->x = (width/2) - (titlescreen->titleRectangle->w / 2);
titlescreen->titleRectangle->y = (height/2) - titlescreen->titleRectangle->h;
titlescreen->textRectangle->x = (width/2) - (titlescreen->textRectangle->w / 2);
titlescreen->textRectangle->y = (height/2) + (titlescreen->textRectangle->h * 2);
// Set the renderer colour to black and clear the screen:
SDL_SetRenderDrawColor(titlescreen->renderer, 0, 0, 0, 255);
SDL_RenderClear(titlescreen->renderer);
// Set the correct position to begin the starfield, and scroll it back for the next frame:
titlescreen->starfieldRectangle->x = 0 - titlescreen->xScroll++;
titlescreen->starfieldRectangle->y = 0;
// Draw the starfield by tiling the starfield texture:
while (titlescreen->starfieldRectangle->x <= (width + titlescreen->starfieldRectangle->w))
{
// Go down, covering a column of the screen:
while(titlescreen->starfieldRectangle->y <= (height + titlescreen->starfieldRectangle->h))
{
SDL_RenderCopy(titlescreen->renderer, titlescreen->starfieldTexture, NULL,
titlescreen->starfieldRectangle);
titlescreen->starfieldRectangle->y += titlescreen->starfieldRectangle->h;
}
// Back to the top, move over one texture width:
titlescreen->starfieldRectangle->y = 0;
titlescreen->starfieldRectangle->x += titlescreen->starfieldRectangle->w;
}
// Reset the xScroll if it goes farther than a texture width away:
if (titlescreen->xScroll == titlescreen->starfieldRectangle->w + 1)
{
titlescreen->xScroll = 0;
}
// Set the opacity of the logo so we can fade it in:
if (titlescreen->titleAlpha < 254)
{
titlescreen->titleAlpha += 10;
}
if (titlescreen->titleAlpha >= 254)
{
titlescreen->titleAlpha = 254;
}
SDL_SetTextureAlphaMod(titlescreen->titleTexture, titlescreen->titleAlpha);
// Set the opacity of the text so we can fade it in after we fade in the logo:
if (titlescreen->textAlpha < 254 && titlescreen->titleAlpha == 254)
{
titlescreen->textAlpha += 10;
}
if (titlescreen->textAlpha >= 254)
{
titlescreen->textAlpha = 254;
}
SDL_SetTextureAlphaMod(titlescreen->textTexture, titlescreen->textAlpha);
// Display the logo and text:
SDL_RenderCopy(titlescreen->renderer, titlescreen->titleTexture, NULL, titlescreen->titleRectangle);
SDL_RenderCopy(titlescreen->renderer, titlescreen->textTexture, NULL, titlescreen->textRectangle);
// Display to the renderer:
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); */
/* } */

View File

@ -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/>.

View File

@ -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/>.

100
source/Spacewar-Physics.c Normal file
View File

@ -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, &currentShip->gravity);
gravityMagnitude = normalizeXYVector(&currentShip->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(&currentShip->gravity, gravityAcceleration);
// Apply Inputs:
// Rotate the engine vector if needed:
if (state->playerInputs[shipIndex].turningClockwise == 1)
{
rotateXYVector(&currentShip->engine, 2.5);
}
if (state->playerInputs[shipIndex].turningAnticlockwise == 1)
{
rotateXYVector(&currentShip->engine, -2.5);
}
// Apply Gravity and Velocity to Position:
if (state->playerInputs[shipIndex].accelerating == 1)
{
addXYVector(&currentShip->velocity, &currentShip->engine);
}
addXYVector(&currentShip->velocity, &currentShip->gravity);
addXYVector(&currentShip->position, &currentShip->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/>.

62
source/Spacewar-Physics.h Normal file
View File

@ -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/>.

294
source/Spacewar-Server.c Normal file
View File

@ -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/>.

59
source/Spacewar-Server.h Normal file
View File

@ -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/>.

67
source/xyVector.h Normal file
View File

@ -0,0 +1,67 @@
#ifndef XYVECTOR_H
#define XYVECTOR_H
#include <math.h>
// A 2D vector:
typedef struct xyVector
{
double xComponent;
double yComponent;
} xyVector;
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;
}
// Calculate the vector from point A to point B:
static inline void xyVectorBetweenPoints(long ax, long ay, long bx, long by, xyVector * vector)
{
vector->xComponent = bx - ax;
vector->yComponent = by - ay;
}
// Normalize a vector, returning the magnitude:
static inline double normalizeXYVector(xyVector * vector)
{
double magnitude = sqrt(pow(vector->xComponent, 2) + pow(vector->yComponent, 2));
if(magnitude != 0)
{
vector->xComponent /= magnitude;
vector->yComponent /= magnitude;
}
return magnitude;
}
// Rotate XY vector by a given number of degrees:
static inline void rotateXYVector(xyVector * vector, double degrees)
{
double xComponent = vector->xComponent, yComponent = vector->yComponent;
vector->xComponent = (cos(degrees * 0.01745329) * xComponent) - (sin(degrees * 0.01745329) * yComponent);
vector->yComponent = (sin(degrees * 0.01745329) * xComponent) + (cos(degrees * 0.01745329) * yComponent);
}
// Add vector B to vector A:
static inline void addXYVector(xyVector * vectorA, xyVector * vectorB)
{
vectorA->xComponent += vectorB->xComponent;
vectorA->yComponent += vectorB->yComponent;
}
// Add vector B to vector A, scaled for units per frame:
static inline void addXYVectorDeltaScaled(xyVector * vectorA, xyVector * vectorB, double deltaTime)
{
vectorA->xComponent += vectorB->xComponent * (0.001 * deltaTime) * 60;
vectorA->yComponent += vectorB->yComponent * (0.001 * deltaTime) * 60;
}
// Multiply a vector by a scalar constant:
static inline void multiplyXYVector(xyVector * vector, double scalar)
{
vector->xComponent *= scalar;
vector->yComponent *= scalar;
}
#endif