Began implementing game logic and re-implementing commands

- Reimplemented /MOVE and /EXIT
- The server is now multi-threaded
- Input and output is now queued
This commit is contained in:
Barry Kane 2022-05-05 19:45:27 +01:00
parent 0b3a72beff
commit 151f3002b8
6 changed files with 271 additions and 39 deletions

View File

@ -279,7 +279,7 @@ int main(int argc, char **argv)
pthread_cancel(sendingThread); pthread_cancel(sendingThread);
// Close the session and socket: // Close the session and socket:
gnutls_bye(tlsSession, GNUTLS_SHUT_RDWR); gnutls_bye(tlsSession, GNUTLS_SHUT_WR);
close(socketFileDesc); close(socketFileDesc);
// Free the structs: // Free the structs:
@ -302,3 +302,4 @@ int main(int argc, char **argv)
// Say goodbye: // Say goodbye:
slowPrint("\nThank you for choosing Silverkin Industries, valued customer!\n", characterDelay); slowPrint("\nThank you for choosing Silverkin Industries, valued customer!\n", characterDelay);
} }

View File

@ -8,12 +8,14 @@
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
#include <ncurses.h> #include <ncurses.h>
#include <pthread.h>
#include <sys/types.h> #include <sys/types.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#include "misc/lists.h" #include "misc/lists.h"
#include "misc/gamelogic.h"
#include "misc/constants.h" #include "misc/constants.h"
#include "misc/playerdata.h" #include "misc/playerdata.h"
#include "misc/texteffects.h" #include "misc/texteffects.h"
@ -27,6 +29,7 @@ int main()
int socketFileDesc, connectionFileDesc, length, clientsAmount, int socketFileDesc, connectionFileDesc, length, clientsAmount,
socketCheck, activityCheck, returnVal; socketCheck, activityCheck, returnVal;
fd_set connectedClients; fd_set connectedClients;
pthread_t gameLogicThread;
int clientSockets[PLAYERCOUNT]; int clientSockets[PLAYERCOUNT];
userMessage sendBuffer, receiveBuffer; userMessage sendBuffer, receiveBuffer;
playerInfo connectedPlayers[PLAYERCOUNT]; playerInfo connectedPlayers[PLAYERCOUNT];
@ -35,11 +38,19 @@ int main()
inputMessageQueue * inputQueue = createInputMessageQueue(); inputMessageQueue * inputQueue = createInputMessageQueue();
outputMessageQueue * outputQueue = createOutputMessageQueue(); outputMessageQueue * outputQueue = createOutputMessageQueue();
// Initialize test areas:
areaNode * areas = createAreaList(createArea("Spawn - North", "A large area, mostly empty, as if the designer hadn't bothered to put anything in it, just yet."));
addAreaNodeToList(areas, createArea("Spawn - South", "A strange, white void. You feel rather uncomfortable."));
addAreaNodeToList(areas, createArea("Temple of Emacs", "A beautifully ornate statue of GNU is above you on a pedestal. Inscribed into the pillar, over and over, is the phrase \"M-x exalt\", in delicate gold letters. You can't help but be awestruck."));
createPath(getAreaFromList(areas, 0), getAreaFromList(areas, 1), "To South Spawn", "To North Spawn");
createPath(getAreaFromList(areas, 2), getAreaFromList(areas, 1), "Back to South Spawn", "Path to Enlightenment.");
// Initialize playerdata: // Initialize playerdata:
for (int index = 0; index < PLAYERCOUNT; index++) for (int index = 0; index < PLAYERCOUNT; index++)
{ {
sprintf(testString, "UNNAMED %d", index); sprintf(testString, "UNNAMED %d", index);
strcpy(connectedPlayers[index].playerName, testString); strcpy(connectedPlayers[index].playerName, testString);
connectedPlayers[index].currentArea = getAreaFromList(areas, 0);
} }
// Give an intro: Display the Silverkin Industries logo and splash text. // Give an intro: Display the Silverkin Industries logo and splash text.
@ -116,6 +127,16 @@ int main()
} }
slowPrint("\tTLS Sessions Initialization is:\t\033[32;40mGREEN.\033[0m\n", 5000); slowPrint("\tTLS Sessions Initialization is:\t\033[32;40mGREEN.\033[0m\n", 5000);
// Prepare the game logic thread:
gameLogicParameters * gameLogicThreadParameters = malloc(sizeof(gameLogicParameters));
gameLogicThreadParameters->connectedPlayers = connectedPlayers;
gameLogicThreadParameters->playerCount = &clientsAmount;
gameLogicThreadParameters->outputQueue = outputQueue;
gameLogicThreadParameters->inputQueue = inputQueue;
pthread_create(&gameLogicThread, NULL, &gameLogicLoop, gameLogicThreadParameters);
struct timeval timeout = {0, 500};
while(keepRunning) while(keepRunning)
{ {
// Clear the set of file descriptors and add the master socket: // Clear the set of file descriptors and add the master socket:
@ -142,7 +163,7 @@ int main()
} }
// See if a connection is ready to be interacted with: // See if a connection is ready to be interacted with:
activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, NULL); activityCheck = select((clientsAmount + 1), &connectedClients, NULL, NULL, &timeout);
// Check if select() worked: // Check if select() worked:
if ((activityCheck < 0) && (errno != EINTR)) if ((activityCheck < 0) && (errno != EINTR))
@ -188,9 +209,10 @@ int main()
if(FD_ISSET(socketCheck, &connectedClients)) if(FD_ISSET(socketCheck, &connectedClients))
{ {
if(messageReceive(tlssessions[index], &receiveBuffer) == -10) int returnVal = messageReceive(tlssessions[index], &receiveBuffer);
if(returnVal == -10 || returnVal == 0)
{ {
gnutls_bye(tlssessions[index], GNUTLS_SHUT_RDWR); gnutls_bye(tlssessions[index], GNUTLS_SHUT_WR);
gnutls_deinit(tlssessions[index]); gnutls_deinit(tlssessions[index]);
shutdown(clientSockets[index], 2); shutdown(clientSockets[index], 2);
close(clientSockets[index]); close(clientSockets[index]);
@ -209,24 +231,47 @@ int main()
} }
} }
// TEMPORARY: MOVE INPUT MESSAGES TO OUTPUT MESSAGES: // TEMPORARY: MOVE INPUT MESSAGES TO OUTPUT MESSAGES:
while(inputQueue->currentLength > 0) /* while(inputQueue->currentLength > 0) */
{ /* { */
inputMessage * message = peekInputMessage(inputQueue); /* inputMessage * message = peekInputMessage(inputQueue); */
strncpy(message->content->senderName, message->sender->playerName, 32); /* strncpy(message->content->senderName, message->sender->playerName, 32); */
userInputSanatize(message->content->messageContent, MAX); /* userInputSanatize(message->content->messageContent, MAX); */
if(message->content->messageContent[0] != '\n') /* if(message->content->messageContent[0] != '\n') */
{ /* { */
queueOutputMessage(outputQueue, *message->content); /* queueOutputMessage(outputQueue, *message->content); */
} /* } */
dequeueInputMessage(inputQueue); /* dequeueInputMessage(inputQueue); */
} /* } */
while(outputQueue->currentLength > 0)
while(outputQueue->currentLength != 0)
{ {
while(outputQueue->lock);
outputQueue->lock = true;
outputMessage * message = peekOutputMessage(outputQueue); outputMessage * message = peekOutputMessage(outputQueue);
outputQueue->lock = false;
if(message->targets[0] == NULL)
{
for (int index = 0; index < PLAYERCOUNT; index++) for (int index = 0; index < PLAYERCOUNT; index++)
{ {
messageSend(tlssessions[index], message->content); messageSend(tlssessions[index], message->content);
} }
}
else
{
int targetIndex = 0;
for(int index = 0; index < PLAYERCOUNT; index++)
{
if(message->targets[targetIndex] == NULL)
{
break;
}
if(&connectedPlayers[index] == message->targets[targetIndex])
{
targetIndex++;
messageSend(tlssessions[index], message->content);
}
}
}
dequeueOutputMessage(outputQueue); dequeueOutputMessage(outputQueue);
} }
} }

95
src/misc/gamelogic.c Normal file
View File

@ -0,0 +1,95 @@
// gamelogic.c: Contains function definitons for dealing with the game's logic.
// Barry Kane, 2022.
#include <string.h>
#include "constants.h"
#include "gamelogic.h"
#include "playerdata.h"
#include "inputoutput.h"
// =======================
// -=[ Main Game Loop ]=-:
// =======================
// Thread function which runs the main game loop, given the needed parameters:
void * gameLogicLoop(void * parameters)
{
gameLogicParameters * threadParameters = parameters;
inputMessage * currentInput = NULL;
bool keepRunning = true;
while(keepRunning)
{
if(threadParameters->inputQueue->currentLength != 0)
{
while(threadParameters->inputQueue->lock == true)
{
threadParameters->inputQueue->lock = true;
}
currentInput = peekInputMessage(threadParameters->inputQueue);
userInputSanatize(currentInput->content->messageContent, MAX);
if(currentInput->content->messageContent[0] == '/')
{
// TODO: Implement Command Queue.
// For now, basic intepretation will do.
if(strncmp(&currentInput->content->messageContent[1], "EXIT", 4) == 0)
{
userMessage * exitMessage = malloc(sizeof(userMessage));
exitMessage->senderName[0] = '\0';
exitMessage->messageContent[0] = '\0';
queueTargetedOutputMessage(threadParameters->outputQueue, exitMessage, &currentInput->sender, 1);
free(exitMessage);
}
if(strncmp(&currentInput->content->messageContent[1], "MOVE", 4) == 0)
{
userMessage * moveMessage = malloc(sizeof(userMessage));
char requestedPath[32];
strncpy(requestedPath, &currentInput->content->messageContent[6], 32);
userInputSanatize(requestedPath, 32);
// Remove newlines:
for (int index = 0; index < 32; index++)
{
if (requestedPath[index] == '\n')
{
requestedPath[index] = '\0';
}
}
requestedPath[31] = '\0';
if(movePlayerToArea(currentInput->sender, requestedPath) == 0)
{
strcpy(moveMessage->senderName, "\0");
strcpy(moveMessage->messageContent, currentInput->sender->currentArea->areaDescription);
queueTargetedOutputMessage(threadParameters->outputQueue, moveMessage, &currentInput->sender, 1);
}
free(moveMessage);
}
}
else
{
strncpy(currentInput->content->senderName, currentInput->sender->playerName, 32);
// Create an array of players in the same area to receive the message:
playerInfo ** recipients = malloc(sizeof(playerInfo*) * *threadParameters->playerCount);
for(int index = 0; index < *threadParameters->playerCount; index++)
{
recipients[index] = NULL;
}
int recipientCount = 0;
for(int playerIndex = 0; playerIndex < *threadParameters->playerCount; playerIndex++)
{
if(threadParameters->connectedPlayers[playerIndex].currentArea == currentInput->sender->currentArea)
{
recipients[recipientCount] = &threadParameters->connectedPlayers[playerIndex];
recipientCount++;
}
}
if(currentInput->content->messageContent[0] != '\n')
{
queueTargetedOutputMessage(threadParameters->outputQueue, currentInput->content, recipients, recipientCount);
}
free(recipients);
}
currentInput = NULL;
threadParameters->inputQueue->lock = false;
dequeueInputMessage(threadParameters->inputQueue);
}
}
return NULL;
}

26
src/misc/gamelogic.h Normal file
View File

@ -0,0 +1,26 @@
// gamelogic.h: Header file contatning function prototypes and datastructures
// for dealing with the game's logic.
// Barry Kane, 2022.
#ifndef GAMELOGIC_H
#define GAMELOGIC_H
#include "constants.h"
#include "playerdata.h"
#include "inputoutput.h"
// =======================
// -=[ Main Game Loop ]=-:
// =======================
// A datastructure containing the needed parameters for a main game loop:
typedef struct gameLogicParameters
{
int * playerCount;
playerInfo * connectedPlayers;
inputMessageQueue * inputQueue;
outputMessageQueue * outputQueue;
} gameLogicParameters;
// Thread function which runs the main game loop, given the needed parameters:
void * gameLogicLoop(void * parameters);
#endif

View File

@ -57,17 +57,21 @@ outputMessageQueue * createOutputMessageQueue(void)
int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue) int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue)
{ {
// Copy the message into a new output message: // Copy the message into a new output message:
outputMessage * outputMessage = malloc(sizeof(outputMessage)); outputMessage * newOutputMessage = malloc(sizeof(outputMessage));
// Allocate the internal userMessage to store the message: // Allocate the internal userMessage to store the message:
outputMessage->content = malloc(sizeof(userMessage)); newOutputMessage->content = malloc(sizeof(userMessage));
// Allocate the internal strings to store the message:
//outputMessage->content->senderName = malloc(sizeof(char)*32);
//outputMessage->content->messageContent = malloc(sizeof(char)*MAX);
// Copy the userMessage to the internal userMessage: // Copy the userMessage to the internal userMessage:
strncpy(outputMessage->content->senderName, messageToQueue.senderName, 32); strncpy(newOutputMessage->content->senderName, messageToQueue.senderName, 32);
strncpy(outputMessage->content->messageContent, messageToQueue.messageContent, MAX); strncpy(newOutputMessage->content->messageContent, messageToQueue.messageContent, MAX);
// We have no targets, NULL sends to all players in an area: // We have no targets, NULL sends to all players in an area:
outputMessage->targets[0] = NULL; newOutputMessage->targets[0] = NULL;
// Wait for the queue to unlock: // Wait for the queue to unlock:
while (queue->lock); while (queue->lock);
@ -87,24 +91,85 @@ int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue)
// If the queue is empty, set the first message as both the front and back of the queue: // If the queue is empty, set the first message as both the front and back of the queue:
if(queue->front == NULL) if(queue->front == NULL)
{ {
queue->front = outputMessage; queue->front = newOutputMessage;
queue->back = outputMessage; queue->back = newOutputMessage;
queue->currentLength++; queue->currentLength++;
// Unlock the queue: // Unlock the queue:
queue->lock = false; queue->lock = false;
return 0; return 0;
} }
else else
{ {
queue->back->next = outputMessage; queue->back->next = newOutputMessage;
queue->back = outputMessage; queue->back = newOutputMessage;
queue->currentLength++; queue->currentLength++;
// Unlock the queue: // Unlock the queue:
queue->lock = false; queue->lock = false;
return 0;
}
}
}
int queueTargetedOutputMessage(outputMessageQueue * queue,
userMessage * messageToQueue, playerInfo ** targets, int numberOfTargets)
{
// Copy the message into a new output message:
outputMessage * newOutputMessage = malloc(sizeof(outputMessage));
// Allocate the internal userMessage to store the message:
newOutputMessage->content = malloc(sizeof(userMessage));
// Set the appropriate recipients:
for(int index = 0; index < numberOfTargets && index < PLAYERCOUNT; index++)
{
newOutputMessage->targets[index] = targets[index];
}
for(int index = numberOfTargets; index < PLAYERCOUNT; index++)
{
newOutputMessage->targets[index] = NULL;
}
// Copy the userMessage to the internal userMessage:
strncpy(newOutputMessage->content->senderName, messageToQueue->senderName, 32);
strncpy(newOutputMessage->content->messageContent, messageToQueue->messageContent, MAX);
// Wait for the queue to unlock:
while (queue->lock);
// Lock the queue:
queue->lock = true;
// Check that we're not overflowing the queue:
if ((queue->currentLength + 1) > MAXQUEUELENGTH)
{
// Unlock the queue:
queue->lock = false;
return -1;
}
else
{
// If the queue is empty, set the first message as both the front and back of the queue:
if(queue->front == NULL)
{
queue->front = newOutputMessage;
queue->back = newOutputMessage;
queue->currentLength++;
// Unlock the queue:
queue->lock = false;
return 0;
}
else
{
queue->back->next = newOutputMessage;
queue->back = newOutputMessage;
queue->currentLength++;
// Unlock the queue:
queue->lock = false;
return 0; return 0;
} }
} }

View File

@ -17,9 +17,9 @@ typedef struct userMessage
char messageContent[MAX]; char messageContent[MAX];
} userMessage; } userMessage;
// ================= // ==================
// -=[Message I/O]=- // -=[Message I/O]=-:
// ================= // ==================
// Sends a message to a given TLS session, wraps the calls to gnutls_write: // Sends a message to a given TLS session, wraps the calls to gnutls_write:
int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend); int messageSend(gnutls_session_t receivingSession, userMessage * messageToSend);
@ -52,8 +52,8 @@ outputMessageQueue * createOutputMessageQueue(void);
// Enqueue a userMessage to an outputMessageQueue: // Enqueue a userMessage to an outputMessageQueue:
int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue); int queueOutputMessage(outputMessageQueue * queue, userMessage messageToQueue);
// int queueOutputMessage(outputMessageQueue * queue, userMessage * messageToQueue, int queueTargetedOutputMessage(outputMessageQueue * queue, userMessage * messageToQueue,
// playerInfo * targets, int numberOfTargets); playerInfo ** targets, int numberOfTargets);
// Dequeue the front outputMessage from an outputMessageQueue: // Dequeue the front outputMessage from an outputMessageQueue:
int dequeueOutputMessage(outputMessageQueue * queue); int dequeueOutputMessage(outputMessageQueue * queue);
@ -93,9 +93,9 @@ int dequeueInputMessage(inputMessageQueue * queue);
// Return the front inputMessage from an inputMessageQueue: // Return the front inputMessage from an inputMessageQueue:
inputMessage * peekInputMessage(inputMessageQueue * queue); inputMessage * peekInputMessage(inputMessageQueue * queue);
// ====================== // =======================
// -=[Input Sanitation]=- // -=[Input Sanitation]=-:
// ====================== // =======================
// Sanatize user input to ensure it's okay to send to the server: // Sanatize user input to ensure it's okay to send to the server:
void userInputSanatize(char * inputString, int length); void userInputSanatize(char * inputString, int length);